@unifiedcommerce/cli 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/api-key.d.ts +34 -0
- package/dist/commands/api-key.d.ts.map +1 -0
- package/dist/commands/api-key.js +202 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const apiKeyCreateCommand: import("citty").CommandDef<{
|
|
2
|
+
scope: {
|
|
3
|
+
type: "string";
|
|
4
|
+
description: string;
|
|
5
|
+
required: true;
|
|
6
|
+
};
|
|
7
|
+
config: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
default: string;
|
|
11
|
+
};
|
|
12
|
+
name: {
|
|
13
|
+
type: "string";
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
"save-env": {
|
|
17
|
+
type: "boolean";
|
|
18
|
+
description: string;
|
|
19
|
+
default: false;
|
|
20
|
+
};
|
|
21
|
+
"env-var": {
|
|
22
|
+
type: "string";
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
export declare const apiKeyListCommand: import("citty").CommandDef<{
|
|
27
|
+
config: {
|
|
28
|
+
type: "string";
|
|
29
|
+
description: string;
|
|
30
|
+
default: string;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
export declare const apiKeyCommand: import("citty").CommandDef<import("citty").ArgsDef>;
|
|
34
|
+
//# sourceMappingURL=api-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.d.ts","sourceRoot":"","sources":["../../src/commands/api-key.ts"],"names":[],"mappings":"AAgGA,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;EAyH9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;EA+B5B,CAAC;AAEH,eAAO,MAAM,aAAa,qDASxB,CAAC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import { writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
4
|
+
/**
|
|
5
|
+
* Boots the commerce engine in-process and returns the auth instance.
|
|
6
|
+
* Uses dynamic import to avoid compile-time dependency on @unifiedcommerce/core.
|
|
7
|
+
*/
|
|
8
|
+
async function bootEngine(configPath) {
|
|
9
|
+
const absPath = resolve(process.cwd(), configPath);
|
|
10
|
+
const configModule = await import(absPath);
|
|
11
|
+
const config = await configModule.default;
|
|
12
|
+
// Dynamic import — resolved at runtime from the consumer's node_modules
|
|
13
|
+
const corePkg = "@unifiedcommerce/core";
|
|
14
|
+
const { createServer } = await import(/* webpackIgnore: true */ corePkg);
|
|
15
|
+
const { app, kernel } = await createServer(config);
|
|
16
|
+
const auth = kernel.auth;
|
|
17
|
+
return { config, auth, app, kernel };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Finds or creates an admin user for key ownership.
|
|
21
|
+
*/
|
|
22
|
+
async function findOrCreateAdminUser(app, config) {
|
|
23
|
+
// Try to create an admin user via the auth API
|
|
24
|
+
const email = "admin@local.dev";
|
|
25
|
+
const password = "admin-setup-" + Date.now();
|
|
26
|
+
const res = await app.request("http://localhost/api/auth/sign-up/email", {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: { "content-type": "application/json", origin: "http://localhost" },
|
|
29
|
+
body: JSON.stringify({ email, password, name: "Admin" }),
|
|
30
|
+
});
|
|
31
|
+
if (res.ok) {
|
|
32
|
+
const data = (await res.json());
|
|
33
|
+
if (data?.user?.id)
|
|
34
|
+
return data.user.id;
|
|
35
|
+
}
|
|
36
|
+
// If signup fails (user exists), try sign-in
|
|
37
|
+
const signInRes = await app.request("http://localhost/api/auth/sign-in/email", {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: {
|
|
40
|
+
"content-type": "application/json",
|
|
41
|
+
origin: "http://localhost",
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({ email, password }),
|
|
44
|
+
});
|
|
45
|
+
if (signInRes.ok) {
|
|
46
|
+
const data = (await signInRes.json());
|
|
47
|
+
if (data?.user?.id)
|
|
48
|
+
return data.user.id;
|
|
49
|
+
}
|
|
50
|
+
throw new Error("Could not find or create an admin user. Create one first via /api/auth/sign-up/email");
|
|
51
|
+
}
|
|
52
|
+
export const apiKeyCreateCommand = defineCommand({
|
|
53
|
+
meta: {
|
|
54
|
+
name: "create",
|
|
55
|
+
description: "Create an API key for a predefined scope",
|
|
56
|
+
},
|
|
57
|
+
args: {
|
|
58
|
+
scope: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Scope name (defined in commerce.config.ts auth.apiKeyScopes)",
|
|
61
|
+
required: true,
|
|
62
|
+
},
|
|
63
|
+
config: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "Path to commerce.config.ts",
|
|
66
|
+
default: "./commerce.config.ts",
|
|
67
|
+
},
|
|
68
|
+
name: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "Key name (default: scope name)",
|
|
71
|
+
},
|
|
72
|
+
"save-env": {
|
|
73
|
+
type: "boolean",
|
|
74
|
+
description: "Save key to .env.local",
|
|
75
|
+
default: false,
|
|
76
|
+
},
|
|
77
|
+
"env-var": {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Env variable name (used with --save-env)",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
async run({ args }) {
|
|
83
|
+
const { config, auth, app } = await bootEngine(args.config);
|
|
84
|
+
const scopes = config.auth?.apiKeyScopes;
|
|
85
|
+
if (!scopes || Object.keys(scopes).length === 0) {
|
|
86
|
+
console.error("No apiKeyScopes defined in commerce.config.ts");
|
|
87
|
+
console.error("");
|
|
88
|
+
console.error("Add scopes to your config:");
|
|
89
|
+
console.error(" auth: {");
|
|
90
|
+
console.error(" apiKeyScopes: {");
|
|
91
|
+
console.error(' storefront: { prefix: "uc_pub_", permissions: { catalog: ["read"] }, ... }');
|
|
92
|
+
console.error(" }");
|
|
93
|
+
console.error(" }");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const scopeDef = scopes[args.scope];
|
|
97
|
+
if (!scopeDef) {
|
|
98
|
+
console.error(`Unknown scope: "${args.scope}"`);
|
|
99
|
+
console.error("");
|
|
100
|
+
console.error("Available scopes:");
|
|
101
|
+
for (const [name, def] of Object.entries(scopes)) {
|
|
102
|
+
console.error(` ${name} — ${def.description}`);
|
|
103
|
+
}
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
console.log(`Creating ${args.scope} API key...`);
|
|
107
|
+
// Find or create an admin user for key ownership
|
|
108
|
+
const userId = await findOrCreateAdminUser(app, config);
|
|
109
|
+
const result = await auth.api.createApiKey({
|
|
110
|
+
body: {
|
|
111
|
+
configId: args.scope,
|
|
112
|
+
userId,
|
|
113
|
+
name: args.name ?? `${args.scope}-key`,
|
|
114
|
+
permissions: scopeDef.permissions,
|
|
115
|
+
...(scopeDef.rateLimit
|
|
116
|
+
? {
|
|
117
|
+
rateLimitEnabled: true,
|
|
118
|
+
rateLimitMax: scopeDef.rateLimit.maxRequests,
|
|
119
|
+
rateLimitTimeWindow: scopeDef.rateLimit.timeWindow,
|
|
120
|
+
}
|
|
121
|
+
: {}),
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
console.log("");
|
|
125
|
+
console.log(` Scope: ${args.scope}`);
|
|
126
|
+
console.log(` Description: ${scopeDef.description}`);
|
|
127
|
+
console.log(` Prefix: ${scopeDef.prefix}`);
|
|
128
|
+
console.log(` Key: ${result.key}`);
|
|
129
|
+
console.log(` ID: ${result.id}`);
|
|
130
|
+
console.log("");
|
|
131
|
+
console.log(" Permissions:");
|
|
132
|
+
for (const [resource, actions] of Object.entries(scopeDef.permissions)) {
|
|
133
|
+
console.log(` ${resource}: ${actions.join(", ")}`);
|
|
134
|
+
}
|
|
135
|
+
if (scopeDef.rateLimit) {
|
|
136
|
+
console.log(` Rate limit: ${scopeDef.rateLimit.maxRequests} req / ${scopeDef.rateLimit.timeWindow}ms`);
|
|
137
|
+
}
|
|
138
|
+
if (args["save-env"]) {
|
|
139
|
+
const envFile = join(process.cwd(), ".env.local");
|
|
140
|
+
const varName = args["env-var"] ?? `UC_${args.scope.toUpperCase().replace(/-/g, "_")}_KEY`;
|
|
141
|
+
const line = `${varName}=${result.key}`;
|
|
142
|
+
if (existsSync(envFile)) {
|
|
143
|
+
const existing = readFileSync(envFile, "utf-8");
|
|
144
|
+
if (existing.includes(varName + "=")) {
|
|
145
|
+
// Replace existing
|
|
146
|
+
const updated = existing.replace(new RegExp(`^${varName}=.*$`, "m"), line);
|
|
147
|
+
writeFileSync(envFile, updated);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
writeFileSync(envFile, existing.trimEnd() + "\n" + line + "\n");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
writeFileSync(envFile, line + "\n");
|
|
155
|
+
}
|
|
156
|
+
console.log(` Saved to: .env.local as ${varName}`);
|
|
157
|
+
}
|
|
158
|
+
console.log("");
|
|
159
|
+
console.log("This is the only time the full key is shown. Store it securely.");
|
|
160
|
+
process.exit(0);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
export const apiKeyListCommand = defineCommand({
|
|
164
|
+
meta: {
|
|
165
|
+
name: "list",
|
|
166
|
+
description: "List existing API keys",
|
|
167
|
+
},
|
|
168
|
+
args: {
|
|
169
|
+
config: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Path to commerce.config.ts",
|
|
172
|
+
default: "./commerce.config.ts",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
async run({ args }) {
|
|
176
|
+
const { config } = await bootEngine(args.config);
|
|
177
|
+
const scopes = config.auth?.apiKeyScopes ?? {};
|
|
178
|
+
console.log("Defined API key scopes:");
|
|
179
|
+
console.log("");
|
|
180
|
+
for (const [name, def] of Object.entries(scopes)) {
|
|
181
|
+
console.log(` ${name}`);
|
|
182
|
+
console.log(` ${def.description}`);
|
|
183
|
+
console.log(` Prefix: ${def.prefix}`);
|
|
184
|
+
console.log(` Permissions: ${Object.entries(def.permissions).map(([r, a]) => `${r}:${a.join(",")}`).join(" | ")}`);
|
|
185
|
+
if (def.rateLimit) {
|
|
186
|
+
console.log(` Rate limit: ${def.rateLimit.maxRequests} req / ${def.rateLimit.timeWindow}ms`);
|
|
187
|
+
}
|
|
188
|
+
console.log("");
|
|
189
|
+
}
|
|
190
|
+
process.exit(0);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
export const apiKeyCommand = defineCommand({
|
|
194
|
+
meta: {
|
|
195
|
+
name: "api-key",
|
|
196
|
+
description: "Manage API keys (create, list)",
|
|
197
|
+
},
|
|
198
|
+
subCommands: {
|
|
199
|
+
create: apiKeyCreateCommand,
|
|
200
|
+
list: apiKeyListCommand,
|
|
201
|
+
},
|
|
202
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,11 @@ import { generateMigrationCommand } from "./commands/generate-migration.js";
|
|
|
6
6
|
import { migrateCommand } from "./commands/migrate.js";
|
|
7
7
|
import { deployCommand } from "./commands/deploy.js";
|
|
8
8
|
import { importCommand } from "./commands/import.js";
|
|
9
|
+
import { apiKeyCommand } from "./commands/api-key.js";
|
|
9
10
|
const main = defineCommand({
|
|
10
11
|
meta: {
|
|
11
12
|
name: "@unifiedcommerce/cli",
|
|
12
|
-
version: "0.
|
|
13
|
+
version: "0.2.5",
|
|
13
14
|
description: "UnifiedCommerce Engine CLI",
|
|
14
15
|
},
|
|
15
16
|
subCommands: {
|
|
@@ -18,6 +19,7 @@ const main = defineCommand({
|
|
|
18
19
|
migrate: migrateCommand,
|
|
19
20
|
deploy: deployCommand,
|
|
20
21
|
import: importCommand,
|
|
22
|
+
"api-key": apiKeyCommand,
|
|
21
23
|
generate: defineCommand({
|
|
22
24
|
subCommands: {
|
|
23
25
|
migration: generateMigrationCommand,
|