@happyvertical/smrt-core 0.45.3 → 0.46.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/AGENTS.md +1 -0
- package/agents/generators.md +0 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/embedded-write-queue.d.ts.map +1 -1
- package/dist/embedded-write-queue.js +20 -1
- package/dist/embedded-write-queue.js.map +1 -1
- package/dist/generators/custom-action.d.ts +71 -39
- package/dist/generators/custom-action.d.ts.map +1 -1
- package/dist/generators/custom-action.js +71 -39
- package/dist/generators/custom-action.js.map +1 -1
- package/dist/generators/index.d.ts +0 -2
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -3
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +1 -1
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/tenant-gate.d.ts +33 -9
- package/dist/generators/tenant-gate.d.ts.map +1 -1
- package/dist/generators/tenant-gate.js +2 -1
- package/dist/generators/tenant-gate.js.map +1 -1
- package/dist/generators.js +2 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/knowledge.js +28 -16
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/framework-base-tables.d.ts +148 -0
- package/dist/migrations/framework-base-tables.d.ts.map +1 -0
- package/dist/migrations/framework-base-tables.js +419 -0
- package/dist/migrations/framework-base-tables.js.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -1
- package/dist/migrations.js +2 -1
- package/dist/postgres-permissions.d.ts +40 -0
- package/dist/postgres-permissions.d.ts.map +1 -0
- package/dist/postgres-permissions.js +353 -0
- package/dist/postgres-permissions.js.map +1 -0
- package/dist/registry/framework-base-classes.d.ts.map +1 -1
- package/dist/registry/framework-base-classes.js +4 -1
- package/dist/registry/framework-base-classes.js.map +1 -1
- package/dist/scripts/create-wrappers.js +0 -5
- package/dist/smrt-knowledge.json +7 -8
- package/dist/testing/database.d.ts +6 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +9 -1
- package/dist/testing/database.js.map +1 -1
- package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
- package/dist/vite-plugin/api-client-entries.js +16 -14
- package/dist/vite-plugin/api-client-entries.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +2 -166
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts +3 -2
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +20 -24
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/templates/default-ui.ts +22 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +4 -9
- package/dist/generators/cli.d.ts +0 -155
- package/dist/generators/cli.d.ts.map +0 -1
- package/dist/generators/cli.js +0 -473
- package/dist/generators/cli.js.map +0 -1
package/dist/generators/cli.js
DELETED
|
@@ -1,473 +0,0 @@
|
|
|
1
|
-
import { CRUD_OPERATIONS, buildCustomActionInvocationArgs, customActionParameterInputName, isCrudOperation, isFrameworkLifecycleMethod, normalizeCustomActionFailure, resolveCustomActionMetadata, resolveCustomActionNames } from "./custom-action.js";
|
|
2
|
-
import { isFrameworkBaseClass } from "../registry/framework-base-classes.js";
|
|
3
|
-
import { ObjectRegistry } from "../registry.js";
|
|
4
|
-
import { SmrtCollection } from "../collection.js";
|
|
5
|
-
import { runWithTenantGate } from "./tenant-gate.js";
|
|
6
|
-
//#region src/generators/cli.ts
|
|
7
|
-
/**
|
|
8
|
-
* Flags consumed by the CLI itself — never treated as create/update field
|
|
9
|
-
* values or as custom-action options.
|
|
10
|
-
*/
|
|
11
|
-
var RESERVED_FLAGS = /* @__PURE__ */ new Set([
|
|
12
|
-
"tenant",
|
|
13
|
-
"all-tenants",
|
|
14
|
-
"from-file",
|
|
15
|
-
"limit",
|
|
16
|
-
"offset",
|
|
17
|
-
"order-by",
|
|
18
|
-
"orderBy",
|
|
19
|
-
"where",
|
|
20
|
-
"format",
|
|
21
|
-
"json",
|
|
22
|
-
"help",
|
|
23
|
-
"id"
|
|
24
|
-
]);
|
|
25
|
-
/**
|
|
26
|
-
* Generate and run an admin CLI for the registered `@smrt()` objects.
|
|
27
|
-
*/
|
|
28
|
-
var CLIGenerator = class {
|
|
29
|
-
config;
|
|
30
|
-
context;
|
|
31
|
-
constructor(config = {}, context = {}) {
|
|
32
|
-
this.config = {
|
|
33
|
-
name: "smrt-cli",
|
|
34
|
-
version: "1.0.0",
|
|
35
|
-
description: "Auto-generated CLI from smrt objects",
|
|
36
|
-
...config
|
|
37
|
-
};
|
|
38
|
-
this.context = context;
|
|
39
|
-
}
|
|
40
|
-
/** CLI program name. */
|
|
41
|
-
get name() {
|
|
42
|
-
return this.config.name;
|
|
43
|
-
}
|
|
44
|
-
/** CLI version. */
|
|
45
|
-
get version() {
|
|
46
|
-
return this.config.version;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Return the argv handler invoked by the generated `setupCLI()` wrapper.
|
|
50
|
-
*
|
|
51
|
-
* @returns An async function accepting the post-`node script` argv slice.
|
|
52
|
-
*/
|
|
53
|
-
generateHandler() {
|
|
54
|
-
return async (args) => {
|
|
55
|
-
await this.run(args ?? []);
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Parse and execute a single CLI invocation.
|
|
60
|
-
*
|
|
61
|
-
* @param args - argv slice (command + flags).
|
|
62
|
-
*/
|
|
63
|
-
async run(args) {
|
|
64
|
-
if (args.length === 0 || args[0] === "--help" || args[0] === "help") {
|
|
65
|
-
console.log(await this.helpText());
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const parsed = this.parseArgs(args);
|
|
69
|
-
if (!parsed) {
|
|
70
|
-
console.log(await this.helpText());
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const classInfo = this.resolveClass(parsed.objectName);
|
|
74
|
-
if (!classInfo) throw new Error(`Object type '${parsed.objectName}' not found`);
|
|
75
|
-
const objectName = classInfo.name || parsed.objectName;
|
|
76
|
-
await this.assertCommandExposed(objectName, parsed.action);
|
|
77
|
-
const result = await runWithTenantGate({
|
|
78
|
-
className: objectName,
|
|
79
|
-
tenantId: typeof parsed.flags.tenant === "string" ? parsed.flags.tenant : this.context.tenantId,
|
|
80
|
-
allowCrossTenant: parsed.flags["all-tenants"] === true || this.context.allowCrossTenant === true,
|
|
81
|
-
surface: "CLI"
|
|
82
|
-
}, async () => {
|
|
83
|
-
const collection = await this.getCollection(objectName);
|
|
84
|
-
return this.executeAction(collection, objectName, parsed);
|
|
85
|
-
});
|
|
86
|
-
if (result !== void 0) console.log(JSON.stringify(this.toPublicData(result), null, 2));
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Parse argv into a command + flags. Supports `name:action` and
|
|
90
|
-
* `name action` command forms, `--key value`, `--key=value`, and bare
|
|
91
|
-
* `--flag` booleans.
|
|
92
|
-
*/
|
|
93
|
-
parseArgs(args) {
|
|
94
|
-
const [command, ...rest] = args;
|
|
95
|
-
if (!command || command.startsWith("-")) return null;
|
|
96
|
-
let objectName;
|
|
97
|
-
let action;
|
|
98
|
-
let tail = rest;
|
|
99
|
-
if (command.includes(":")) {
|
|
100
|
-
const [obj, act] = command.split(":");
|
|
101
|
-
objectName = obj.toLowerCase();
|
|
102
|
-
action = act;
|
|
103
|
-
} else if (rest.length > 0 && !rest[0].startsWith("-")) {
|
|
104
|
-
objectName = command.toLowerCase();
|
|
105
|
-
action = rest[0];
|
|
106
|
-
tail = rest.slice(1);
|
|
107
|
-
} else return null;
|
|
108
|
-
if (!objectName || !action) return null;
|
|
109
|
-
const flags = {};
|
|
110
|
-
let positional;
|
|
111
|
-
for (let i = 0; i < tail.length; i++) {
|
|
112
|
-
const token = tail[i];
|
|
113
|
-
if (token.startsWith("--")) {
|
|
114
|
-
const body = token.slice(2);
|
|
115
|
-
const eq = body.indexOf("=");
|
|
116
|
-
if (eq !== -1) flags[body.slice(0, eq)] = body.slice(eq + 1);
|
|
117
|
-
else {
|
|
118
|
-
const next = tail[i + 1];
|
|
119
|
-
if (next !== void 0 && !next.startsWith("--")) {
|
|
120
|
-
flags[body] = next;
|
|
121
|
-
i++;
|
|
122
|
-
} else flags[body] = true;
|
|
123
|
-
}
|
|
124
|
-
} else if (positional === void 0) positional = token;
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
objectName,
|
|
128
|
-
action,
|
|
129
|
-
positional,
|
|
130
|
-
flags
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Resolve a registered class by simple name (case-insensitive), mirroring the
|
|
135
|
-
* MCP generator's lookup.
|
|
136
|
-
*/
|
|
137
|
-
resolveClass(objectName) {
|
|
138
|
-
const registeredClasses = ObjectRegistry.getAllClasses();
|
|
139
|
-
for (const [key, info] of registeredClasses) if ((info.name || key).toLowerCase() === objectName.toLowerCase()) return info;
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Throw if `action` is not exposed for `objectName` under its `@smrt({ cli })`
|
|
144
|
-
* config. `cli: false` disables the object entirely; an `include` list is the
|
|
145
|
-
* complete allowlist; custom methods must be `isPublic`.
|
|
146
|
-
*/
|
|
147
|
-
async assertCommandExposed(objectName, action) {
|
|
148
|
-
const cliConfig = ObjectRegistry.getConfig(objectName)?.cli;
|
|
149
|
-
if (cliConfig === false) throw new Error(`CLI is disabled for ${objectName}`);
|
|
150
|
-
const included = typeof cliConfig === "object" ? cliConfig?.include : void 0;
|
|
151
|
-
if ((typeof cliConfig === "object" && cliConfig?.exclude ? cliConfig.exclude : []).includes(action)) throw new Error(`Command '${action}' is excluded for ${objectName}`);
|
|
152
|
-
if (isCrudOperation(action)) {
|
|
153
|
-
if (included && !included.includes(action)) throw new Error(`Command '${action}' is not enabled for ${objectName}`);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (isFrameworkLifecycleMethod(action)) throw new Error(`Command '${action}' is a framework lifecycle method, not a custom action, on ${objectName}`);
|
|
157
|
-
const methodDef = (await ObjectRegistry.getAllMethods(objectName)).get(action);
|
|
158
|
-
if (!methodDef) throw new Error(`Unknown command '${action}' for ${objectName}`);
|
|
159
|
-
if (!methodDef.isPublic) throw new Error(`Command '${action}' is not public on ${objectName}`);
|
|
160
|
-
if (included !== void 0 && !included.includes(action)) throw new Error(`Command '${action}' is not enabled for ${objectName}`);
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Get the collection for an object via the registry factory. Uses
|
|
164
|
-
* `ObjectRegistry.getCollection`, which caches, initializes, and — for plain
|
|
165
|
-
* `@smrt()` models without a hand-written collection class — auto-creates the
|
|
166
|
-
* default collection. (A bare `collectionConstructor` check would wrongly
|
|
167
|
-
* throw for those models even though CRUD is advertised.)
|
|
168
|
-
*/
|
|
169
|
-
async getCollection(objectName) {
|
|
170
|
-
return ObjectRegistry.getCollection(objectName, {
|
|
171
|
-
ai: this.context.ai,
|
|
172
|
-
db: this.context.db
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Execute a parsed command against a collection.
|
|
177
|
-
*/
|
|
178
|
-
async executeAction(collection, objectName, parsed) {
|
|
179
|
-
const { action, positional, flags } = parsed;
|
|
180
|
-
switch (action) {
|
|
181
|
-
case "list": {
|
|
182
|
-
const listOptions = {
|
|
183
|
-
limit: Math.min(this.toNumber(flags.limit, 50), 1e3),
|
|
184
|
-
offset: this.toNumber(flags.offset, 0)
|
|
185
|
-
};
|
|
186
|
-
const orderBy = flags["order-by"] ?? flags.orderBy;
|
|
187
|
-
if (typeof orderBy === "string") listOptions.orderBy = orderBy;
|
|
188
|
-
if (typeof flags.where === "string") listOptions.where = JSON.parse(flags.where);
|
|
189
|
-
return collection.list(listOptions);
|
|
190
|
-
}
|
|
191
|
-
case "get": {
|
|
192
|
-
const id = positional ?? this.flagString(flags.id);
|
|
193
|
-
if (!id) throw new Error("An id is required for get");
|
|
194
|
-
const item = await collection.get(id);
|
|
195
|
-
if (!item) throw new Error(`${objectName} not found`);
|
|
196
|
-
return item;
|
|
197
|
-
}
|
|
198
|
-
case "create": {
|
|
199
|
-
const data = this.applyWritablePolicy(objectName, await this.readPayload(flags));
|
|
200
|
-
if (this.context.user) {
|
|
201
|
-
data.created_by = this.context.user.id;
|
|
202
|
-
data.owner_id = this.context.user.id;
|
|
203
|
-
}
|
|
204
|
-
const created = await collection.create(data);
|
|
205
|
-
await created.save();
|
|
206
|
-
return created;
|
|
207
|
-
}
|
|
208
|
-
case "update": {
|
|
209
|
-
const id = positional ?? this.flagString(flags.id);
|
|
210
|
-
if (!id) throw new Error("An id is required for update");
|
|
211
|
-
const existing = await collection.get(id);
|
|
212
|
-
if (!existing) throw new Error(`${objectName} not found`);
|
|
213
|
-
const data = this.applyWritablePolicy(objectName, await this.readPayload(flags));
|
|
214
|
-
Object.assign(existing, data);
|
|
215
|
-
if (this.context.user) existing.updated_by = this.context.user.id;
|
|
216
|
-
await existing.save();
|
|
217
|
-
return existing;
|
|
218
|
-
}
|
|
219
|
-
case "delete": {
|
|
220
|
-
const id = positional ?? this.flagString(flags.id);
|
|
221
|
-
if (!id) throw new Error("An id is required for delete");
|
|
222
|
-
const existing = await collection.get(id);
|
|
223
|
-
if (!existing) throw new Error(`${objectName} not found`);
|
|
224
|
-
await existing.delete();
|
|
225
|
-
return {
|
|
226
|
-
success: true,
|
|
227
|
-
id,
|
|
228
|
-
message: `${objectName} deleted`
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
default: return this.executeCustomAction(objectName, collection, action, positional, flags);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Execute a custom method against its manifest-declared item/collection
|
|
236
|
-
* target. The legacy collection fallback remains only for unavailable
|
|
237
|
-
* metadata, where target scope cannot be established safely.
|
|
238
|
-
*/
|
|
239
|
-
async executeCustomAction(objectName, collection, action, positional, flags) {
|
|
240
|
-
const id = positional ?? this.flagString(flags.id);
|
|
241
|
-
const methodDef = (await ObjectRegistry.getAllMethods(objectName)).get(action);
|
|
242
|
-
const metadata = resolveCustomActionMetadata({
|
|
243
|
-
actionName: action,
|
|
244
|
-
method: methodDef,
|
|
245
|
-
apiConfig: ObjectRegistry.getConfig(objectName).api,
|
|
246
|
-
...this.hasCollectionReceiver(ObjectRegistry.getClass(objectName)) ? { defaultScope: "collection" } : {}
|
|
247
|
-
});
|
|
248
|
-
const args = this.customOptions(flags, metadata);
|
|
249
|
-
const methodArgs = buildCustomActionInvocationArgs(metadata, metadata.parameters?.length === 1 && metadata.parameters[0]?.name === "options" ? Object.keys(args).length > 0 ? { options: args } : {} : args);
|
|
250
|
-
if (methodDef && metadata.idRequired && !id) throw new Error(`An id is required for custom action '${action}'`);
|
|
251
|
-
if (methodDef && !metadata.idRequired && id) throw new Error(`Custom action '${action}' is collection-scoped and does not accept an id`);
|
|
252
|
-
if (id) {
|
|
253
|
-
const object = await collection.get(id);
|
|
254
|
-
if (!object) throw new Error("Object not found");
|
|
255
|
-
const candidate = object[action];
|
|
256
|
-
if (typeof candidate !== "function") throw new Error(`Method '${action}' not found on object instance`);
|
|
257
|
-
const result = await candidate.call(object, ...methodArgs);
|
|
258
|
-
const failure = normalizeCustomActionFailure(result);
|
|
259
|
-
if (failure) throw new Error(JSON.stringify({ error: failure }));
|
|
260
|
-
return result;
|
|
261
|
-
}
|
|
262
|
-
if (methodDef?.isStatic) {
|
|
263
|
-
const classInfo = ObjectRegistry.getClass(objectName);
|
|
264
|
-
const candidate = (classInfo?.constructor)?.[action];
|
|
265
|
-
if (typeof candidate !== "function") throw new Error(`Static method '${action}' not found on ${objectName}`);
|
|
266
|
-
const result = await candidate.call(classInfo?.constructor, ...methodArgs);
|
|
267
|
-
const failure = normalizeCustomActionFailure(result);
|
|
268
|
-
if (failure) throw new Error(JSON.stringify({ error: failure }));
|
|
269
|
-
return result;
|
|
270
|
-
}
|
|
271
|
-
const collectionCandidate = collection[action];
|
|
272
|
-
if (typeof collectionCandidate === "function") {
|
|
273
|
-
const result = await collectionCandidate.call(collection, ...methodArgs);
|
|
274
|
-
const failure = normalizeCustomActionFailure(result);
|
|
275
|
-
if (failure) throw new Error(JSON.stringify({ error: failure }));
|
|
276
|
-
return result;
|
|
277
|
-
}
|
|
278
|
-
throw new Error(`Method '${action}' not found on collection. Provide an id for object-specific actions.`);
|
|
279
|
-
}
|
|
280
|
-
hasCollectionReceiver(classInfo) {
|
|
281
|
-
return !!classInfo && classInfo.constructor.prototype instanceof SmrtCollection;
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Resolve the create/update payload: `--from-file <path>` (JSON) takes
|
|
285
|
-
* precedence; otherwise non-reserved `--field value` flags form the body.
|
|
286
|
-
* Reading from a file also keeps secrets off the process argv (#1556).
|
|
287
|
-
*/
|
|
288
|
-
async readPayload(flags) {
|
|
289
|
-
const fromFile = flags["from-file"];
|
|
290
|
-
if (typeof fromFile === "string" && fromFile) {
|
|
291
|
-
const { readFile } = await import("node:fs/promises");
|
|
292
|
-
const content = await readFile(fromFile, "utf-8");
|
|
293
|
-
const parsed = JSON.parse(content);
|
|
294
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("--from-file must contain a JSON object");
|
|
295
|
-
return parsed;
|
|
296
|
-
}
|
|
297
|
-
const data = {};
|
|
298
|
-
for (const [key, value] of Object.entries(flags)) {
|
|
299
|
-
if (RESERVED_FLAGS.has(key)) continue;
|
|
300
|
-
data[key] = this.coerceValue(value);
|
|
301
|
-
}
|
|
302
|
-
return data;
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Collect custom-action flags. Typed action parameters may deliberately use
|
|
306
|
-
* standard CLI names such as `limit`, `offset`, `where`, or `format`; retain
|
|
307
|
-
* those rather than silently dropping them as CRUD flags. Item actions keep
|
|
308
|
-
* `--id` for their receiver and accept an action parameter named `id` as
|
|
309
|
-
* `--action-id` to preserve separate namespaces.
|
|
310
|
-
*/
|
|
311
|
-
customOptions(flags, metadata) {
|
|
312
|
-
const declaredInputs = new Set((metadata?.parameters ?? []).map((parameter) => customActionParameterInputName(metadata ?? { idRequired: false }, parameter.name)));
|
|
313
|
-
const options = {};
|
|
314
|
-
for (const [rawKey, value] of Object.entries(flags)) {
|
|
315
|
-
const key = rawKey === "action-id" ? "actionId" : rawKey;
|
|
316
|
-
if (rawKey === "id") continue;
|
|
317
|
-
if (RESERVED_FLAGS.has(rawKey) && !declaredInputs.has(key)) continue;
|
|
318
|
-
if (rawKey === "action-id" && !declaredInputs.has(key)) continue;
|
|
319
|
-
options[key] = this.coerceValue(value);
|
|
320
|
-
}
|
|
321
|
-
return options;
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Mass-assignment guard (#1540/#1556): strip framework/server-managed and
|
|
325
|
-
* `@field({ readonly })` fields, and — when an `@smrt({ api: { writable } })`
|
|
326
|
-
* allowlist is set — intersect with it. Identical policy to the REST/MCP
|
|
327
|
-
* generators so a CLI create/update cannot set fields the API forbids.
|
|
328
|
-
*/
|
|
329
|
-
applyWritablePolicy(objectName, data) {
|
|
330
|
-
if (!data || typeof data !== "object") return {};
|
|
331
|
-
const serverManaged = /* @__PURE__ */ new Set([
|
|
332
|
-
"id",
|
|
333
|
-
"tenantId",
|
|
334
|
-
"tenant_id",
|
|
335
|
-
"createdAt",
|
|
336
|
-
"created_at",
|
|
337
|
-
"updatedAt",
|
|
338
|
-
"updated_at"
|
|
339
|
-
]);
|
|
340
|
-
const readonly = /* @__PURE__ */ new Set();
|
|
341
|
-
let writable = null;
|
|
342
|
-
if (objectName) {
|
|
343
|
-
const apiConfig = ObjectRegistry.getConfig(objectName)?.api;
|
|
344
|
-
if (apiConfig && typeof apiConfig === "object" && Array.isArray(apiConfig.writable)) writable = apiConfig.writable;
|
|
345
|
-
for (const [name, def] of ObjectRegistry.getFields(objectName)) if (def && (def.readonly === true || def._meta?.readonly === true)) readonly.add(name);
|
|
346
|
-
}
|
|
347
|
-
const result = {};
|
|
348
|
-
for (const [key, value] of Object.entries(data)) {
|
|
349
|
-
if (key.startsWith("_")) continue;
|
|
350
|
-
if (serverManaged.has(key)) continue;
|
|
351
|
-
if (readonly.has(key)) continue;
|
|
352
|
-
if (writable && !writable.includes(key)) continue;
|
|
353
|
-
result[key] = value;
|
|
354
|
-
}
|
|
355
|
-
return result;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Serialize command output, excluding `@field({ sensitive })` fields (#1540).
|
|
359
|
-
* Recurses arrays/plain objects so a SmrtObject nested in a custom-action
|
|
360
|
-
* result is stripped too; a cycle guard prevents infinite loops.
|
|
361
|
-
*/
|
|
362
|
-
toPublicData(value, seen = /* @__PURE__ */ new WeakSet(), options = this.getPublicJsonOptions()) {
|
|
363
|
-
if (value === null || typeof value !== "object") return value;
|
|
364
|
-
if (typeof value.toPublicJSON === "function") return value.toPublicJSON(options);
|
|
365
|
-
if (Array.isArray(value)) {
|
|
366
|
-
if (seen.has(value)) return value;
|
|
367
|
-
seen.add(value);
|
|
368
|
-
return value.map((entry) => this.toPublicData(entry, seen, options));
|
|
369
|
-
}
|
|
370
|
-
const proto = Object.getPrototypeOf(value);
|
|
371
|
-
if (proto !== Object.prototype && proto !== null) return value;
|
|
372
|
-
if (seen.has(value)) return value;
|
|
373
|
-
seen.add(value);
|
|
374
|
-
const out = {};
|
|
375
|
-
for (const [key, entry] of Object.entries(value)) out[key] = this.toPublicData(entry, seen, options);
|
|
376
|
-
return out;
|
|
377
|
-
}
|
|
378
|
-
getPublicJsonOptions() {
|
|
379
|
-
return { permissions: this.context.permissions };
|
|
380
|
-
}
|
|
381
|
-
/** List the runnable commands grouped by object, for help output. */
|
|
382
|
-
async listCommands() {
|
|
383
|
-
const commands = [];
|
|
384
|
-
for (const [key, classInfo] of ObjectRegistry.getAllClasses()) {
|
|
385
|
-
const objectName = classInfo.name || key;
|
|
386
|
-
if (isFrameworkBaseClass(objectName, classInfo.packageName)) continue;
|
|
387
|
-
const cliConfig = ObjectRegistry.getConfig(objectName)?.cli;
|
|
388
|
-
if (cliConfig === false) continue;
|
|
389
|
-
const lower = objectName.toLowerCase();
|
|
390
|
-
const included = typeof cliConfig === "object" ? cliConfig?.include : void 0;
|
|
391
|
-
const excluded = typeof cliConfig === "object" && cliConfig?.exclude ? cliConfig.exclude : [];
|
|
392
|
-
const expose = (cmd) => !excluded.includes(cmd) && (!included || included.includes(cmd));
|
|
393
|
-
for (const verb of CRUD_OPERATIONS) if (expose(verb)) commands.push(`${lower}:${verb}`);
|
|
394
|
-
const customActions = resolveCustomActionNames(await ObjectRegistry.getAllMethods(objectName), {
|
|
395
|
-
include: included,
|
|
396
|
-
exclude: excluded
|
|
397
|
-
}, CRUD_OPERATIONS);
|
|
398
|
-
for (const methodName of customActions) commands.push(`${lower}:${methodName}`);
|
|
399
|
-
}
|
|
400
|
-
return commands.sort();
|
|
401
|
-
}
|
|
402
|
-
async helpText() {
|
|
403
|
-
const commands = await this.listCommands();
|
|
404
|
-
return [
|
|
405
|
-
`${this.config.name} ${this.config.version}`,
|
|
406
|
-
this.config.description ?? "",
|
|
407
|
-
"",
|
|
408
|
-
"Usage: <object>:<action> [id] [--flags]",
|
|
409
|
-
"",
|
|
410
|
-
"Global flags:",
|
|
411
|
-
" --tenant <id> run the command inside a specific tenant",
|
|
412
|
-
" --all-tenants allow cross-tenant access (operator opt-in)",
|
|
413
|
-
" --from-file <p> read create/update payload from a JSON file",
|
|
414
|
-
" --where <json> filter for list",
|
|
415
|
-
" --limit / --offset / --order-by",
|
|
416
|
-
"",
|
|
417
|
-
"Commands:",
|
|
418
|
-
...commands.map((c) => ` ${c}`)
|
|
419
|
-
].join("\n");
|
|
420
|
-
}
|
|
421
|
-
toNumber(value, fallback) {
|
|
422
|
-
if (typeof value !== "string") return fallback;
|
|
423
|
-
const n = Number.parseInt(value, 10);
|
|
424
|
-
return Number.isNaN(n) ? fallback : n;
|
|
425
|
-
}
|
|
426
|
-
flagString(value) {
|
|
427
|
-
return typeof value === "string" ? value : void 0;
|
|
428
|
-
}
|
|
429
|
-
/** Coerce a flag value to a JSON scalar/object where it parses cleanly. */
|
|
430
|
-
coerceValue(value) {
|
|
431
|
-
if (typeof value !== "string") return value;
|
|
432
|
-
if (value === "true") return true;
|
|
433
|
-
if (value === "false") return false;
|
|
434
|
-
if (value !== "" && !Number.isNaN(Number(value))) return Number(value);
|
|
435
|
-
if (value.startsWith("{") && value.endsWith("}") || value.startsWith("[") && value.endsWith("]")) try {
|
|
436
|
-
return JSON.parse(value);
|
|
437
|
-
} catch {
|
|
438
|
-
return value;
|
|
439
|
-
}
|
|
440
|
-
return value;
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
/**
|
|
444
|
-
* Convenience runner used by the generated `setupCLI()` wrapper.
|
|
445
|
-
*
|
|
446
|
-
* @param config - CLI configuration.
|
|
447
|
-
* @param context - Per-invocation context.
|
|
448
|
-
* @returns A `{ run, generator }` pair; `run(argv)` strips the leading
|
|
449
|
-
* `node script` entries before dispatching.
|
|
450
|
-
*/
|
|
451
|
-
function setupCLI(config = {}, context = {}) {
|
|
452
|
-
const generator = new CLIGenerator(config, context);
|
|
453
|
-
return {
|
|
454
|
-
run: async (argv) => {
|
|
455
|
-
await generator.generateHandler()(argv.slice(2));
|
|
456
|
-
},
|
|
457
|
-
generator
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Get a bare argv handler for a generated CLI.
|
|
462
|
-
*
|
|
463
|
-
* @param config - CLI configuration.
|
|
464
|
-
* @param context - Per-invocation context.
|
|
465
|
-
* @returns An async argv-slice handler.
|
|
466
|
-
*/
|
|
467
|
-
function getCLIHandler(config = {}, context = {}) {
|
|
468
|
-
return new CLIGenerator(config, context).generateHandler();
|
|
469
|
-
}
|
|
470
|
-
//#endregion
|
|
471
|
-
export { CLIGenerator, getCLIHandler, setupCLI };
|
|
472
|
-
|
|
473
|
-
//# sourceMappingURL=cli.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../../src/generators/cli.ts"],"sourcesContent":["/**\n * CLI command generator for smrt objects\n *\n * Exposes registered `@smrt()` objects as a runnable admin CLI: each object gets\n * `list`/`get`/`create`/`update`/`delete` commands plus its public custom\n * methods, dispatched as `objectname:action`.\n *\n * Security parity with the REST/MCP generators (#1540, #1547, #1554, #1556):\n * - **Mass-assignment guard** — create/update bodies are filtered through the\n * `@smrt({ api: { writable: [...] } })` allowlist, dropping server-managed and\n * `@field({ readonly })` fields ({@link CLIGenerator.applyWritablePolicy}).\n * - **Exhaustive include** — an `include` list is the COMPLETE allowlist for the\n * surface; custom methods are gated on `isPublic`.\n * - **Sensitive redaction** — command *output* is serialized through\n * {@link CLIGenerator.toPublicData} so `@field({ sensitive })` values never\n * print (input bodies are guarded by the writable allowlist above).\n * - **Fail-closed tenant context** — tenant-scoped reads/writes run inside the\n * tenancy gate; without `--tenant <id>` / `--all-tenants` (and with tenancy\n * enabled) the command throws rather than ranging across all tenants.\n */\n\nimport type { AIClient, AIClientOptions } from '@happyvertical/ai';\nimport { SmrtCollection } from '../collection';\nimport type { DatabaseConfig } from '../database.js';\nimport type { PublicJsonOptions, SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport type { RegisteredClass } from '../registry/types.js';\nimport {\n buildCustomActionInvocationArgs,\n CRUD_OPERATIONS,\n customActionParameterInputName,\n isCrudOperation,\n isFrameworkLifecycleMethod,\n normalizeCustomActionFailure,\n resolveCustomActionMetadata,\n resolveCustomActionNames,\n} from './custom-action.js';\nimport { runWithTenantGate } from './tenant-gate.js';\n\n/**\n * Public-data view exposed by SMRT objects: `toPublicJSON()` strips\n * `@field({ sensitive })` values before serialization (#1540).\n */\ninterface PublicSerializable {\n toPublicJSON(options?: PublicJsonOptions): unknown;\n}\n\n/**\n * View of an instance/collection for dispatching custom methods by name. Each\n * key is potentially a callable taking the parsed CLI options; callers narrow\n * with `typeof === 'function'` before invoking.\n */\ntype DynamicallyCallable = Record<\n string,\n ((...args: unknown[]) => unknown) | unknown\n>;\n\n/**\n * Configuration for a generated CLI.\n */\nexport interface CLIConfig {\n /** CLI program name (used in help output). */\n name?: string;\n /** CLI version. */\n version?: string;\n /** CLI description. */\n description?: string;\n}\n\n/**\n * Per-invocation context for a generated CLI.\n */\nexport interface CLIContext {\n /** Database handle passed to collections. */\n db?: DatabaseConfig;\n /** AI provider passed to collections. */\n ai?: AIClientOptions | AIClient;\n /** Authenticated operator, when the host establishes one. */\n user?: {\n id: string;\n roles?: string[];\n };\n /** Resolved permission slugs held by the operator. */\n permissions?: Iterable<string>;\n /**\n * Default tenant for tenant-scoped commands when no `--tenant` flag is given.\n * Hosts that authenticate an operator may set this from the principal.\n */\n tenantId?: string;\n /**\n * Default cross-tenant opt-in when no `--all-tenants` flag is given. Hosts\n * may set this for trusted operator/admin shells.\n */\n allowCrossTenant?: boolean;\n}\n\n/**\n * Flags consumed by the CLI itself — never treated as create/update field\n * values or as custom-action options.\n */\nconst RESERVED_FLAGS = new Set([\n 'tenant',\n 'all-tenants',\n 'from-file',\n 'limit',\n 'offset',\n 'order-by',\n 'orderBy',\n 'where',\n 'format',\n 'json',\n 'help',\n 'id',\n]);\n\n/** Parsed CLI invocation. */\ninterface ParsedInvocation {\n /** Object/collection segment (lowercased), e.g. `product`. */\n objectName: string;\n /** Action segment, e.g. `list` or a custom method name. */\n action: string;\n /** First positional argument after the command (typically an id). */\n positional?: string;\n /** Parsed flags. String for `--k v`/`--k=v`, `true` for bare `--flag`. */\n flags: Record<string, string | boolean>;\n}\n\n/**\n * Generate and run an admin CLI for the registered `@smrt()` objects.\n */\nexport class CLIGenerator {\n private config: CLIConfig;\n private context: CLIContext;\n\n constructor(config: CLIConfig = {}, context: CLIContext = {}) {\n this.config = {\n name: 'smrt-cli',\n version: '1.0.0',\n description: 'Auto-generated CLI from smrt objects',\n ...config,\n };\n this.context = context;\n }\n\n /** CLI program name. */\n get name(): string | undefined {\n return this.config.name;\n }\n\n /** CLI version. */\n get version(): string | undefined {\n return this.config.version;\n }\n\n /**\n * Return the argv handler invoked by the generated `setupCLI()` wrapper.\n *\n * @returns An async function accepting the post-`node script` argv slice.\n */\n generateHandler(): (args: string[]) => Promise<void> {\n return async (args: string[]) => {\n await this.run(args ?? []);\n };\n }\n\n /**\n * Parse and execute a single CLI invocation.\n *\n * @param args - argv slice (command + flags).\n */\n async run(args: string[]): Promise<void> {\n if (args.length === 0 || args[0] === '--help' || args[0] === 'help') {\n console.log(await this.helpText());\n return;\n }\n\n const parsed = this.parseArgs(args);\n if (!parsed) {\n console.log(await this.helpText());\n return;\n }\n\n const classInfo = this.resolveClass(parsed.objectName);\n if (!classInfo) {\n throw new Error(`Object type '${parsed.objectName}' not found`);\n }\n const objectName: string = classInfo.name || parsed.objectName;\n\n await this.assertCommandExposed(objectName, parsed.action);\n\n const tenantId =\n typeof parsed.flags.tenant === 'string'\n ? parsed.flags.tenant\n : this.context.tenantId;\n const allowCrossTenant =\n parsed.flags['all-tenants'] === true ||\n this.context.allowCrossTenant === true;\n\n // Tenant-scoping is resolved authoritatively inside tenancy (by class name),\n // matching the interceptor and covering both @TenantScoped and\n // @smrt({ tenantScoped }) registrations (#1554).\n const result = await runWithTenantGate(\n { className: objectName, tenantId, allowCrossTenant, surface: 'CLI' },\n async () => {\n const collection = await this.getCollection(objectName);\n return this.executeAction(collection, objectName, parsed);\n },\n );\n\n if (result !== undefined) {\n console.log(JSON.stringify(this.toPublicData(result), null, 2));\n }\n }\n\n /**\n * Parse argv into a command + flags. Supports `name:action` and\n * `name action` command forms, `--key value`, `--key=value`, and bare\n * `--flag` booleans.\n */\n private parseArgs(args: string[]): ParsedInvocation | null {\n const [command, ...rest] = args;\n if (!command || command.startsWith('-')) return null;\n\n let objectName: string;\n let action: string;\n let tail = rest;\n\n if (command.includes(':')) {\n const [obj, act] = command.split(':');\n objectName = obj.toLowerCase();\n action = act;\n } else if (rest.length > 0 && !rest[0].startsWith('-')) {\n objectName = command.toLowerCase();\n action = rest[0];\n tail = rest.slice(1);\n } else {\n // A bare object name with no action is not a runnable command.\n return null;\n }\n\n if (!objectName || !action) return null;\n\n const flags: Record<string, string | boolean> = {};\n let positional: string | undefined;\n\n for (let i = 0; i < tail.length; i++) {\n const token = tail[i];\n if (token.startsWith('--')) {\n const body = token.slice(2);\n const eq = body.indexOf('=');\n if (eq !== -1) {\n flags[body.slice(0, eq)] = body.slice(eq + 1);\n } else {\n const next = tail[i + 1];\n if (next !== undefined && !next.startsWith('--')) {\n flags[body] = next;\n i++;\n } else {\n flags[body] = true;\n }\n }\n } else if (positional === undefined) {\n positional = token;\n }\n }\n\n return { objectName, action, positional, flags };\n }\n\n /**\n * Resolve a registered class by simple name (case-insensitive), mirroring the\n * MCP generator's lookup.\n */\n private resolveClass(objectName: string): RegisteredClass | null {\n const registeredClasses = ObjectRegistry.getAllClasses();\n for (const [key, info] of registeredClasses) {\n const simpleName = info.name || key;\n if (simpleName.toLowerCase() === objectName.toLowerCase()) {\n return info;\n }\n }\n return null;\n }\n\n /**\n * Throw if `action` is not exposed for `objectName` under its `@smrt({ cli })`\n * config. `cli: false` disables the object entirely; an `include` list is the\n * complete allowlist; custom methods must be `isPublic`.\n */\n private async assertCommandExposed(\n objectName: string,\n action: string,\n ): Promise<void> {\n const config = ObjectRegistry.getConfig(objectName);\n const cliConfig = config?.cli;\n\n if (cliConfig === false) {\n throw new Error(`CLI is disabled for ${objectName}`);\n }\n\n const included: string[] | undefined =\n typeof cliConfig === 'object' ? cliConfig?.include : undefined;\n const excluded: string[] =\n typeof cliConfig === 'object' && cliConfig?.exclude\n ? cliConfig.exclude\n : [];\n\n if (excluded.includes(action)) {\n throw new Error(`Command '${action}' is excluded for ${objectName}`);\n }\n\n const isCrud = isCrudOperation(action);\n\n if (isCrud) {\n // An include list, when present, is the complete allowlist for CRUD too.\n if (included && !included.includes(action)) {\n throw new Error(`Command '${action}' is not enabled for ${objectName}`);\n }\n return;\n }\n\n // A framework lifecycle method (save/initialize/... — see\n // FRAMEWORK_LIFECYCLE_METHOD_NAMES) is never a custom action, even when\n // this class declares its own override: it is the mechanism behind\n // generated CRUD, not a distinct operation, and listCommands() never\n // advertises it. Reject it here too so an advertised command and an\n // invokable one cannot disagree (#2638).\n if (isFrameworkLifecycleMethod(action)) {\n throw new Error(\n `Command '${action}' is a framework lifecycle method, not a custom action, on ${objectName}`,\n );\n }\n\n // Custom method: must be public, and — when an include list is present — it\n // is the COMPLETE allowlist (parity with MCP exhaustive-include, #1547).\n const methods = await ObjectRegistry.getAllMethods(objectName);\n const methodDef = methods.get(action);\n if (!methodDef) {\n throw new Error(`Unknown command '${action}' for ${objectName}`);\n }\n if (!methodDef.isPublic) {\n throw new Error(`Command '${action}' is not public on ${objectName}`);\n }\n if (included !== undefined && !included.includes(action)) {\n throw new Error(`Command '${action}' is not enabled for ${objectName}`);\n }\n }\n\n /**\n * Get the collection for an object via the registry factory. Uses\n * `ObjectRegistry.getCollection`, which caches, initializes, and — for plain\n * `@smrt()` models without a hand-written collection class — auto-creates the\n * default collection. (A bare `collectionConstructor` check would wrongly\n * throw for those models even though CRUD is advertised.)\n */\n private async getCollection(\n objectName: string,\n ): Promise<SmrtCollection<SmrtObject>> {\n return ObjectRegistry.getCollection(objectName, {\n ai: this.context.ai,\n db: this.context.db,\n });\n }\n\n /**\n * Execute a parsed command against a collection.\n */\n private async executeAction(\n collection: SmrtCollection<SmrtObject>,\n objectName: string,\n parsed: ParsedInvocation,\n ): Promise<unknown> {\n const { action, positional, flags } = parsed;\n\n switch (action) {\n case 'list': {\n const listOptions: {\n limit: number;\n offset: number;\n orderBy?: string;\n where?: Record<string, unknown>;\n } = {\n limit: Math.min(this.toNumber(flags.limit, 50), 1000),\n offset: this.toNumber(flags.offset, 0),\n };\n const orderBy = flags['order-by'] ?? flags.orderBy;\n if (typeof orderBy === 'string') listOptions.orderBy = orderBy;\n if (typeof flags.where === 'string') {\n listOptions.where = JSON.parse(flags.where);\n }\n return collection.list(listOptions);\n }\n\n case 'get': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for get');\n const item = await collection.get(id);\n if (!item) throw new Error(`${objectName} not found`);\n return item;\n }\n\n case 'create': {\n const data = this.applyWritablePolicy(\n objectName,\n await this.readPayload(flags),\n );\n if (this.context.user) {\n data.created_by = this.context.user.id;\n data.owner_id = this.context.user.id;\n }\n const created = await collection.create(data);\n await created.save();\n return created;\n }\n\n case 'update': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for update');\n const existing = await collection.get(id);\n if (!existing) throw new Error(`${objectName} not found`);\n const data = this.applyWritablePolicy(\n objectName,\n await this.readPayload(flags),\n );\n Object.assign(existing, data);\n if (this.context.user) {\n // Stamp the server-managed audit column on the instance; this field\n // is not part of the public SmrtObject surface.\n (existing as unknown as Record<string, unknown>).updated_by =\n this.context.user.id;\n }\n await existing.save();\n return existing;\n }\n\n case 'delete': {\n const id = positional ?? this.flagString(flags.id);\n if (!id) throw new Error('An id is required for delete');\n const existing = await collection.get(id);\n if (!existing) throw new Error(`${objectName} not found`);\n await existing.delete();\n return { success: true, id, message: `${objectName} deleted` };\n }\n\n default:\n return this.executeCustomAction(\n objectName,\n collection,\n action,\n positional,\n flags,\n );\n }\n }\n\n /**\n * Execute a custom method against its manifest-declared item/collection\n * target. The legacy collection fallback remains only for unavailable\n * metadata, where target scope cannot be established safely.\n */\n private async executeCustomAction(\n objectName: string,\n collection: SmrtCollection<SmrtObject>,\n action: string,\n positional: string | undefined,\n flags: Record<string, string | boolean>,\n ): Promise<unknown> {\n const id = positional ?? this.flagString(flags.id);\n const methodDef = (await ObjectRegistry.getAllMethods(objectName)).get(\n action,\n );\n const metadata = resolveCustomActionMetadata({\n actionName: action,\n method: methodDef,\n apiConfig: ObjectRegistry.getConfig(objectName).api,\n ...(this.hasCollectionReceiver(ObjectRegistry.getClass(objectName))\n ? { defaultScope: 'collection' as const }\n : {}),\n });\n const args = this.customOptions(flags, metadata);\n const invocationArgs =\n metadata.parameters?.length === 1 &&\n metadata.parameters[0]?.name === 'options'\n ? Object.keys(args).length > 0\n ? { options: args }\n : {}\n : args;\n const methodArgs = buildCustomActionInvocationArgs(\n metadata,\n invocationArgs,\n );\n\n if (methodDef && metadata.idRequired && !id) {\n throw new Error(`An id is required for custom action '${action}'`);\n }\n if (methodDef && !metadata.idRequired && id) {\n throw new Error(\n `Custom action '${action}' is collection-scoped and does not accept an id`,\n );\n }\n\n if (id) {\n const object = await collection.get(id);\n if (!object) throw new Error('Object not found');\n // Custom methods are dispatched dynamically by name; index through a\n // callable-keyed view of the instance, narrowing before invoking.\n const candidate = (object as unknown as DynamicallyCallable)[action];\n if (typeof candidate !== 'function') {\n throw new Error(`Method '${action}' not found on object instance`);\n }\n const result = await candidate.call(object, ...methodArgs);\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n\n if (methodDef?.isStatic) {\n const classInfo = ObjectRegistry.getClass(objectName);\n const candidate = (\n classInfo?.constructor as unknown as DynamicallyCallable | undefined\n )?.[action];\n if (typeof candidate !== 'function') {\n throw new Error(`Static method '${action}' not found on ${objectName}`);\n }\n const result = await candidate.call(\n classInfo?.constructor,\n ...methodArgs,\n );\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n\n const collectionCandidate = (collection as unknown as DynamicallyCallable)[\n action\n ];\n if (typeof collectionCandidate === 'function') {\n const result = await collectionCandidate.call(collection, ...methodArgs);\n const failure = normalizeCustomActionFailure(result);\n if (failure) throw new Error(JSON.stringify({ error: failure }));\n return result;\n }\n throw new Error(\n `Method '${action}' not found on collection. Provide an id for object-specific actions.`,\n );\n }\n\n private hasCollectionReceiver(classInfo?: RegisteredClass): boolean {\n return (\n !!classInfo && classInfo.constructor.prototype instanceof SmrtCollection\n );\n }\n\n /**\n * Resolve the create/update payload: `--from-file <path>` (JSON) takes\n * precedence; otherwise non-reserved `--field value` flags form the body.\n * Reading from a file also keeps secrets off the process argv (#1556).\n */\n private async readPayload(\n flags: Record<string, string | boolean>,\n ): Promise<Record<string, unknown>> {\n const fromFile = flags['from-file'];\n if (typeof fromFile === 'string' && fromFile) {\n const { readFile } = await import('node:fs/promises');\n const content = await readFile(fromFile, 'utf-8');\n const parsed = JSON.parse(content);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('--from-file must contain a JSON object');\n }\n return parsed as Record<string, unknown>;\n }\n\n const data: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(flags)) {\n if (RESERVED_FLAGS.has(key)) continue;\n data[key] = this.coerceValue(value);\n }\n return data;\n }\n\n /**\n * Collect custom-action flags. Typed action parameters may deliberately use\n * standard CLI names such as `limit`, `offset`, `where`, or `format`; retain\n * those rather than silently dropping them as CRUD flags. Item actions keep\n * `--id` for their receiver and accept an action parameter named `id` as\n * `--action-id` to preserve separate namespaces.\n */\n private customOptions(\n flags: Record<string, string | boolean>,\n metadata?: { parameters?: Array<{ name: string }>; idRequired: boolean },\n ): Record<string, unknown> {\n const declaredInputs = new Set(\n (metadata?.parameters ?? []).map((parameter) =>\n customActionParameterInputName(\n metadata ?? { idRequired: false },\n parameter.name,\n ),\n ),\n );\n const options: Record<string, unknown> = {};\n for (const [rawKey, value] of Object.entries(flags)) {\n const key = rawKey === 'action-id' ? 'actionId' : rawKey;\n if (rawKey === 'id') continue;\n if (RESERVED_FLAGS.has(rawKey) && !declaredInputs.has(key)) continue;\n if (rawKey === 'action-id' && !declaredInputs.has(key)) continue;\n options[key] = this.coerceValue(value);\n }\n return options;\n }\n\n /**\n * Mass-assignment guard (#1540/#1556): strip framework/server-managed and\n * `@field({ readonly })` fields, and — when an `@smrt({ api: { writable } })`\n * allowlist is set — intersect with it. Identical policy to the REST/MCP\n * generators so a CLI create/update cannot set fields the API forbids.\n */\n private applyWritablePolicy(\n objectName: string | undefined,\n data: unknown,\n ): Record<string, unknown> {\n if (!data || typeof data !== 'object') return {};\n\n const serverManaged = new Set([\n 'id',\n 'tenantId',\n 'tenant_id',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n ]);\n\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n\n if (objectName) {\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (\n apiConfig &&\n typeof apiConfig === 'object' &&\n Array.isArray((apiConfig as { writable?: unknown }).writable)\n ) {\n writable = (apiConfig as { writable: string[] }).writable;\n }\n\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && (def.readonly === true || def._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Serialize command output, excluding `@field({ sensitive })` fields (#1540).\n * Recurses arrays/plain objects so a SmrtObject nested in a custom-action\n * result is stripped too; a cycle guard prevents infinite loops.\n */\n private toPublicData(\n value: unknown,\n seen: WeakSet<object> = new WeakSet(),\n options: PublicJsonOptions = this.getPublicJsonOptions(),\n ): unknown {\n if (value === null || typeof value !== 'object') return value;\n if (\n typeof (value as Partial<PublicSerializable>).toPublicJSON === 'function'\n ) {\n return (value as PublicSerializable).toPublicJSON(options);\n }\n if (Array.isArray(value)) {\n if (seen.has(value)) return value;\n seen.add(value);\n return value.map((entry) => this.toPublicData(entry, seen, options));\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) return value;\n if (seen.has(value)) return value;\n seen.add(value);\n const out: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n out[key] = this.toPublicData(entry, seen, options);\n }\n return out;\n }\n\n private getPublicJsonOptions(): PublicJsonOptions {\n return { permissions: this.context.permissions };\n }\n\n /** List the runnable commands grouped by object, for help output. */\n async listCommands(): Promise<string[]> {\n const commands: string[] = [];\n for (const [key, classInfo] of ObjectRegistry.getAllClasses()) {\n const objectName = classInfo.name || key;\n\n // The framework's own abstract base classes are scaffolding, not\n // resources — never expose an `objectname:list`/`:create`/... command\n // for them, regardless of config (#2642).\n if (isFrameworkBaseClass(objectName, classInfo.packageName)) continue;\n\n const config = ObjectRegistry.getConfig(objectName);\n const cliConfig = config?.cli;\n if (cliConfig === false) continue;\n\n const lower = objectName.toLowerCase();\n const included: string[] | undefined =\n typeof cliConfig === 'object' ? cliConfig?.include : undefined;\n const excluded: string[] =\n typeof cliConfig === 'object' && cliConfig?.exclude\n ? cliConfig.exclude\n : [];\n\n const expose = (cmd: string) =>\n !excluded.includes(cmd) && (!included || included.includes(cmd));\n\n for (const verb of CRUD_OPERATIONS) {\n if (expose(verb)) commands.push(`${lower}:${verb}`);\n }\n\n const methods = await ObjectRegistry.getAllMethods(objectName);\n const customActions = resolveCustomActionNames(\n methods,\n { include: included, exclude: excluded },\n CRUD_OPERATIONS,\n );\n for (const methodName of customActions) {\n commands.push(`${lower}:${methodName}`);\n }\n }\n return commands.sort();\n }\n\n private async helpText(): Promise<string> {\n const commands = await this.listCommands();\n return [\n `${this.config.name} ${this.config.version}`,\n this.config.description ?? '',\n '',\n 'Usage: <object>:<action> [id] [--flags]',\n '',\n 'Global flags:',\n ' --tenant <id> run the command inside a specific tenant',\n ' --all-tenants allow cross-tenant access (operator opt-in)',\n ' --from-file <p> read create/update payload from a JSON file',\n ' --where <json> filter for list',\n ' --limit / --offset / --order-by',\n '',\n 'Commands:',\n ...commands.map((c) => ` ${c}`),\n ].join('\\n');\n }\n\n private toNumber(\n value: string | boolean | undefined,\n fallback: number,\n ): number {\n if (typeof value !== 'string') return fallback;\n const n = Number.parseInt(value, 10);\n return Number.isNaN(n) ? fallback : n;\n }\n\n private flagString(value: string | boolean | undefined): string | undefined {\n return typeof value === 'string' ? value : undefined;\n }\n\n /** Coerce a flag value to a JSON scalar/object where it parses cleanly. */\n private coerceValue(value: string | boolean): unknown {\n if (typeof value !== 'string') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (value !== '' && !Number.isNaN(Number(value))) return Number(value);\n if (\n (value.startsWith('{') && value.endsWith('}')) ||\n (value.startsWith('[') && value.endsWith(']'))\n ) {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n return value;\n }\n}\n\n/**\n * Convenience runner used by the generated `setupCLI()` wrapper.\n *\n * @param config - CLI configuration.\n * @param context - Per-invocation context.\n * @returns A `{ run, generator }` pair; `run(argv)` strips the leading\n * `node script` entries before dispatching.\n */\nexport function setupCLI(config: CLIConfig = {}, context: CLIContext = {}) {\n const generator = new CLIGenerator(config, context);\n return {\n run: async (argv: string[]) => {\n const handler = generator.generateHandler();\n await handler(argv.slice(2));\n },\n generator,\n };\n}\n\n/**\n * Get a bare argv handler for a generated CLI.\n *\n * @param config - CLI configuration.\n * @param context - Per-invocation context.\n * @returns An async argv-slice handler.\n */\nexport function getCLIHandler(\n config: CLIConfig = {},\n context: CLIContext = {},\n) {\n const generator = new CLIGenerator(config, context);\n return generator.generateHandler();\n}\n"],"mappings":"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"}
|