@nocobase/cli 2.1.0-alpha.16 → 2.1.0-alpha.18

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.
Files changed (105) hide show
  1. package/LICENSE.txt +107 -0
  2. package/README.md +134 -63
  3. package/bin/run.cmd +3 -0
  4. package/bin/run.js +87 -0
  5. package/dist/commands/api/index.js +8 -0
  6. package/dist/commands/env/add.js +53 -0
  7. package/dist/commands/env/auth.js +36 -0
  8. package/dist/commands/env/index.js +27 -0
  9. package/dist/commands/env/list.js +31 -0
  10. package/dist/commands/env/remove.js +54 -0
  11. package/dist/commands/env/update.js +58 -0
  12. package/dist/commands/env/use.js +26 -0
  13. package/dist/commands/resource/create.js +15 -0
  14. package/dist/commands/resource/destroy.js +15 -0
  15. package/dist/commands/resource/get.js +15 -0
  16. package/dist/commands/resource/index.js +7 -0
  17. package/dist/commands/resource/list.js +16 -0
  18. package/dist/commands/resource/query.js +15 -0
  19. package/dist/commands/resource/update.js +15 -0
  20. package/dist/generated/command-registry.js +88 -0
  21. package/dist/lib/api-client.js +199 -0
  22. package/dist/lib/auth-store.js +155 -0
  23. package/dist/lib/bootstrap.js +349 -0
  24. package/dist/lib/build-config.js +10 -0
  25. package/dist/lib/cli-home.js +30 -0
  26. package/dist/lib/env-auth.js +405 -0
  27. package/dist/lib/generated-command.js +142 -0
  28. package/dist/lib/naming.js +70 -0
  29. package/dist/lib/openapi.js +254 -0
  30. package/dist/lib/post-processors.js +23 -0
  31. package/dist/lib/resource-command.js +335 -0
  32. package/dist/lib/resource-request.js +104 -0
  33. package/dist/lib/runtime-generator.js +408 -0
  34. package/dist/lib/runtime-store.js +56 -0
  35. package/dist/lib/ui.js +169 -0
  36. package/dist/post-processors/data-modeling.js +66 -0
  37. package/dist/post-processors/data-source-manager.js +114 -0
  38. package/dist/post-processors/index.js +19 -0
  39. package/nocobase-ctl.config.json +327 -0
  40. package/package.json +50 -25
  41. package/LICENSE +0 -201
  42. package/bin/index.js +0 -39
  43. package/nocobase.conf.tpl +0 -184
  44. package/src/cli.js +0 -28
  45. package/src/commands/benchmark.js +0 -73
  46. package/src/commands/build.js +0 -81
  47. package/src/commands/clean.js +0 -30
  48. package/src/commands/client.js +0 -168
  49. package/src/commands/create-nginx-conf.js +0 -53
  50. package/src/commands/create-plugin.js +0 -33
  51. package/src/commands/dev.js +0 -290
  52. package/src/commands/doc.js +0 -76
  53. package/src/commands/e2e.js +0 -265
  54. package/src/commands/global.js +0 -43
  55. package/src/commands/index.js +0 -45
  56. package/src/commands/instance-id.js +0 -47
  57. package/src/commands/locale/cronstrue.js +0 -122
  58. package/src/commands/locale/react-js-cron/en-US.json +0 -75
  59. package/src/commands/locale/react-js-cron/index.js +0 -17
  60. package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
  61. package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
  62. package/src/commands/locale.js +0 -81
  63. package/src/commands/p-test.js +0 -88
  64. package/src/commands/perf.js +0 -63
  65. package/src/commands/pkg.js +0 -321
  66. package/src/commands/pm2.js +0 -37
  67. package/src/commands/postinstall.js +0 -88
  68. package/src/commands/start.js +0 -148
  69. package/src/commands/tar.js +0 -36
  70. package/src/commands/test-coverage.js +0 -55
  71. package/src/commands/test.js +0 -107
  72. package/src/commands/umi.js +0 -33
  73. package/src/commands/update-deps.js +0 -72
  74. package/src/commands/upgrade.js +0 -47
  75. package/src/commands/view-license-key.js +0 -44
  76. package/src/index.js +0 -14
  77. package/src/license.js +0 -76
  78. package/src/logger.js +0 -75
  79. package/src/plugin-generator.js +0 -80
  80. package/src/util.js +0 -607
  81. package/templates/bundle-status.html +0 -338
  82. package/templates/create-app-package.json +0 -39
  83. package/templates/plugin/.npmignore.tpl +0 -2
  84. package/templates/plugin/README.md.tpl +0 -1
  85. package/templates/plugin/client-v2.d.ts +0 -2
  86. package/templates/plugin/client-v2.js +0 -1
  87. package/templates/plugin/client.d.ts +0 -2
  88. package/templates/plugin/client.js +0 -1
  89. package/templates/plugin/package.json.tpl +0 -12
  90. package/templates/plugin/server.d.ts +0 -2
  91. package/templates/plugin/server.js +0 -1
  92. package/templates/plugin/src/client/client.d.ts +0 -249
  93. package/templates/plugin/src/client/index.tsx.tpl +0 -1
  94. package/templates/plugin/src/client/locale.ts +0 -21
  95. package/templates/plugin/src/client/models/index.ts +0 -12
  96. package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
  97. package/templates/plugin/src/client-v2/client.d.ts +0 -103
  98. package/templates/plugin/src/client-v2/index.tsx.tpl +0 -1
  99. package/templates/plugin/src/client-v2/plugin.tsx.tpl +0 -7
  100. package/templates/plugin/src/index.ts +0 -2
  101. package/templates/plugin/src/locale/en-US.json +0 -1
  102. package/templates/plugin/src/locale/zh-CN.json +0 -1
  103. package/templates/plugin/src/server/collections/.gitkeep +0 -0
  104. package/templates/plugin/src/server/index.ts.tpl +0 -1
  105. package/templates/plugin/src/server/plugin.ts.tpl +0 -19
@@ -0,0 +1,104 @@
1
+ import { executeRawApiRequest } from './api-client.js';
2
+ function buildActionUrl(resource, action, sourceId) {
3
+ if (typeof sourceId === 'undefined' || sourceId === null || !resource.includes('.')) {
4
+ return `${resource}:${action}`;
5
+ }
6
+ const [parentResource, childResource] = resource.split('.');
7
+ return `${parentResource}/${encodeURIComponent(String(sourceId))}/${childResource}:${action}`;
8
+ }
9
+ function buildQueryValue(value) {
10
+ if (typeof value === 'undefined') {
11
+ return undefined;
12
+ }
13
+ if (value === null) {
14
+ return null;
15
+ }
16
+ if (Array.isArray(value)) {
17
+ return value;
18
+ }
19
+ if (typeof value === 'object') {
20
+ return JSON.stringify(value);
21
+ }
22
+ return value;
23
+ }
24
+ function buildRequestQuery(args) {
25
+ const query = {
26
+ filterByTk: buildQueryValue(args.filterByTk),
27
+ filter: buildQueryValue(args.filter),
28
+ fields: buildQueryValue(args.fields),
29
+ appends: buildQueryValue(args.appends),
30
+ except: buildQueryValue(args.except),
31
+ sort: buildQueryValue(args.sort),
32
+ page: buildQueryValue(args.page),
33
+ pageSize: buildQueryValue(args.pageSize),
34
+ paginate: buildQueryValue(args.paginate),
35
+ whitelist: buildQueryValue(args.whitelist),
36
+ blacklist: buildQueryValue(args.blacklist),
37
+ updateAssociationValues: buildQueryValue(args.updateAssociationValues),
38
+ forceUpdate: buildQueryValue(args.forceUpdate),
39
+ };
40
+ for (const key of Object.keys(query)) {
41
+ if (typeof query[key] === 'undefined') {
42
+ delete query[key];
43
+ }
44
+ }
45
+ return query;
46
+ }
47
+ function buildQueryPayload(args) {
48
+ const payload = {
49
+ measures: args.measures,
50
+ dimensions: args.dimensions,
51
+ orders: args.orders,
52
+ filter: args.filter,
53
+ having: args.having,
54
+ limit: args.limit,
55
+ offset: args.offset,
56
+ };
57
+ for (const key of Object.keys(payload)) {
58
+ if (typeof payload[key] === 'undefined') {
59
+ delete payload[key];
60
+ }
61
+ }
62
+ return payload;
63
+ }
64
+ function buildCrudPayload(action, args) {
65
+ if ((action === 'create' || action === 'update') && args.values) {
66
+ return args.values;
67
+ }
68
+ }
69
+ function buildActionQuery(action, args) {
70
+ if (action === 'query') {
71
+ return undefined;
72
+ }
73
+ return buildRequestQuery(args);
74
+ }
75
+ function buildActionPayload(action, args) {
76
+ if (action === 'query') {
77
+ return buildQueryPayload(args);
78
+ }
79
+ return buildCrudPayload(action, args);
80
+ }
81
+ function buildHeaders(action, args) {
82
+ const headers = {};
83
+ if (args.dataSource && args.dataSource !== 'main') {
84
+ headers['x-data-source'] = args.dataSource;
85
+ }
86
+ if (action === 'query' && args.timezone) {
87
+ headers['x-timezone'] = args.timezone;
88
+ }
89
+ return headers;
90
+ }
91
+ export async function executeResourceRequest(options) {
92
+ const path = `/${buildActionUrl(options.args.resource, options.action, options.args.sourceId)}`;
93
+ return executeRawApiRequest({
94
+ envName: options.envName,
95
+ baseUrl: options.baseUrl,
96
+ role: options.role,
97
+ token: options.token,
98
+ method: 'POST',
99
+ path,
100
+ query: buildActionQuery(options.action, options.args),
101
+ body: buildActionPayload(options.action, options.args),
102
+ headers: buildHeaders(options.action, options.args),
103
+ });
104
+ }
@@ -0,0 +1,408 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { loadBuildConfig } from './build-config.js';
3
+ import { toKebabCase, toLogicalActionName, toLogicalResourceName, toResourceSegments } from './naming.js';
4
+ import { collectOperations } from './openapi.js';
5
+ const RESERVED_FLAG_NAMES = new Set(['base-url', 'env', 'token', 'json-output', 'body', 'body-file']);
6
+ function matchesPattern(value, pattern) {
7
+ if (!value) {
8
+ return false;
9
+ }
10
+ if (pattern.endsWith('*')) {
11
+ return value.startsWith(pattern.slice(0, -1));
12
+ }
13
+ return value === pattern;
14
+ }
15
+ function inferParameterType(schema) {
16
+ if (schema?.type) {
17
+ return schema.type;
18
+ }
19
+ for (const candidate of [...(schema?.oneOf ?? []), ...(schema?.anyOf ?? []), ...(schema?.allOf ?? [])]) {
20
+ const resolved = inferParameterType(candidate);
21
+ if (resolved) {
22
+ return resolved;
23
+ }
24
+ }
25
+ return undefined;
26
+ }
27
+ function createUniqueFlagName(baseName, usedFlagNames) {
28
+ let candidate = baseName || 'value';
29
+ if (RESERVED_FLAG_NAMES.has(candidate)) {
30
+ candidate = `param-${candidate}`;
31
+ }
32
+ if (!usedFlagNames.has(candidate)) {
33
+ usedFlagNames.add(candidate);
34
+ return candidate;
35
+ }
36
+ let index = 2;
37
+ while (usedFlagNames.has(`${candidate}-${index}`)) {
38
+ index += 1;
39
+ }
40
+ const unique = `${candidate}-${index}`;
41
+ usedFlagNames.add(unique);
42
+ return unique;
43
+ }
44
+ function isSupportedParameter(parameter) {
45
+ return Boolean(parameter && typeof parameter.name === 'string' && typeof parameter.in === 'string');
46
+ }
47
+ function toGeneratedParameter(parameter, usedFlagNames) {
48
+ return {
49
+ name: parameter.name,
50
+ flagName: createUniqueFlagName(toKebabCase(parameter.name), usedFlagNames),
51
+ in: parameter.in,
52
+ required: parameter.required,
53
+ description: parameter.description,
54
+ type: inferParameterType(parameter.schema),
55
+ isArray: parameter.schema?.type === 'array',
56
+ };
57
+ }
58
+ function getJsonRequestSchema(requestBody) {
59
+ return requestBody?.content?.['application/json']?.schema;
60
+ }
61
+ function normalizeCompositeSchema(schema) {
62
+ if (!schema || typeof schema !== 'object') {
63
+ return schema;
64
+ }
65
+ if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
66
+ return schema.allOf.reduce((result, part) => {
67
+ const normalized = normalizeCompositeSchema(part);
68
+ return {
69
+ ...result,
70
+ ...normalized,
71
+ type: normalized?.type ?? result.type ?? 'object',
72
+ properties: {
73
+ ...(result.properties ?? {}),
74
+ ...(normalized?.properties ?? {}),
75
+ },
76
+ required: [...new Set([...(result.required ?? []), ...(normalized?.required ?? [])])],
77
+ additionalProperties: normalized?.additionalProperties ?? result.additionalProperties,
78
+ description: schema.description ?? normalized?.description ?? result.description,
79
+ };
80
+ }, { type: 'object' });
81
+ }
82
+ return schema;
83
+ }
84
+ function describeSchemaShape(schema, options = {}) {
85
+ if (!schema) {
86
+ return undefined;
87
+ }
88
+ const normalizedSchema = normalizeCompositeSchema(schema);
89
+ if (Array.isArray(normalizedSchema?.oneOf) || Array.isArray(normalizedSchema?.anyOf)) {
90
+ const variants = (normalizedSchema.oneOf ?? normalizedSchema.anyOf)
91
+ .map((candidate) => describeSchemaShape(candidate, options))
92
+ .filter(Boolean);
93
+ return [...new Set(variants)].join(' | ') || 'value';
94
+ }
95
+ if (normalizedSchema.$ref && typeof normalizedSchema.$ref === 'string') {
96
+ return normalizedSchema.$ref.split('/').pop();
97
+ }
98
+ const depth = options.depth ?? 0;
99
+ const maxDepth = options.maxDepth ?? 2;
100
+ const maxProperties = options.maxProperties ?? 6;
101
+ const type = inferParameterType(normalizedSchema) ??
102
+ (normalizedSchema.properties ? 'object' : undefined) ??
103
+ (normalizedSchema.items ? 'array' : undefined) ??
104
+ (normalizedSchema.additionalProperties ? 'object' : undefined);
105
+ if (!type) {
106
+ return 'value';
107
+ }
108
+ if (type === 'array') {
109
+ const itemShape = describeSchemaShape(normalizedSchema.items, {
110
+ depth: depth + 1,
111
+ maxDepth,
112
+ maxProperties,
113
+ });
114
+ return itemShape ? `[${itemShape}]` : '[]';
115
+ }
116
+ if (type === 'object') {
117
+ const properties = Object.entries(normalizedSchema.properties ?? {});
118
+ if (!properties.length) {
119
+ return undefined;
120
+ }
121
+ const required = new Set(normalizedSchema.required ?? []);
122
+ const sortedProperties = [...properties].sort(([left], [right]) => Number(required.has(right)) - Number(required.has(left)));
123
+ return `{${sortedProperties
124
+ .slice(0, maxProperties)
125
+ .map(([name, propertySchema]) => {
126
+ const nestedShape = depth + 1 < maxDepth ? describeSchemaShape(propertySchema, { depth: depth + 1, maxDepth, maxProperties }) : undefined;
127
+ return `${name}${required.has(name) ? '' : '?'}: ${nestedShape ?? inferParameterType(propertySchema) ?? 'value'}`;
128
+ })
129
+ .join(', ')}${sortedProperties.length > maxProperties ? ', ...' : ''}}`;
130
+ }
131
+ return type;
132
+ }
133
+ function extractBodyParameters(requestBody, usedFlagNames) {
134
+ const schema = getJsonRequestSchema(requestBody);
135
+ const properties = normalizeCompositeSchema(schema)?.properties;
136
+ const required = new Set(normalizeCompositeSchema(schema)?.required ?? []);
137
+ return Object.entries(properties ?? {}).map(([name, propertySchema]) => ({
138
+ name,
139
+ flagName: createUniqueFlagName(toKebabCase(name), usedFlagNames),
140
+ in: 'body',
141
+ required: required.has(name),
142
+ description: propertySchema.description,
143
+ type: inferParameterType(propertySchema),
144
+ isArray: propertySchema.type === 'array',
145
+ jsonEncoded: propertySchema.type === 'object' || propertySchema.type === 'array',
146
+ jsonShape: describeSchemaShape(propertySchema),
147
+ }));
148
+ }
149
+ function splitParagraphs(value) {
150
+ return (value ?? '')
151
+ .split(/\n\s*\n/)
152
+ .map((part) => part.trim())
153
+ .filter(Boolean);
154
+ }
155
+ function resolveOperationText(operation) {
156
+ const swaggerSummary = operation.summary?.trim() || undefined;
157
+ const descriptionParagraphs = splitParagraphs(operation.description?.trim());
158
+ if (descriptionParagraphs.length > 0) {
159
+ const [summary, ...rest] = descriptionParagraphs;
160
+ return {
161
+ summary,
162
+ description: rest.join('\n\n') || (swaggerSummary && swaggerSummary !== summary ? swaggerSummary : undefined),
163
+ };
164
+ }
165
+ return {
166
+ summary: swaggerSummary,
167
+ description: undefined,
168
+ };
169
+ }
170
+ function formatFlagExample(parameter) {
171
+ if (parameter.type === 'boolean') {
172
+ return `--${parameter.flagName}`;
173
+ }
174
+ if (parameter.type === 'object') {
175
+ return `--${parameter.flagName} '{\"key\":\"value\"}'`;
176
+ }
177
+ if (parameter.isArray) {
178
+ return `--${parameter.flagName} value1 --${parameter.flagName} value2`;
179
+ }
180
+ return `--${parameter.flagName} <value>`;
181
+ }
182
+ function getSampleJsonValue(parameter) {
183
+ if (parameter.type === 'boolean') {
184
+ return true;
185
+ }
186
+ if (parameter.type === 'integer' || parameter.type === 'number') {
187
+ return 1;
188
+ }
189
+ if (parameter.type === 'array') {
190
+ return [];
191
+ }
192
+ if (parameter.type === 'object') {
193
+ return { key: 'value' };
194
+ }
195
+ return 'value';
196
+ }
197
+ function buildSampleBody(parameters) {
198
+ const requiredBodyParameters = parameters.filter((parameter) => parameter.in === 'body' && parameter.required);
199
+ if (!requiredBodyParameters.length) {
200
+ return '{"key":"value"}';
201
+ }
202
+ return JSON.stringify(Object.fromEntries(requiredBodyParameters.map((parameter) => [parameter.name, getSampleJsonValue(parameter)])));
203
+ }
204
+ export function buildExamples(commandId, operation) {
205
+ const requiredParameters = operation.parameters.filter((parameter) => parameter.required);
206
+ const requiredFlags = requiredParameters.map(formatFlagExample);
207
+ const requiredNonBodyFlags = requiredParameters.filter((parameter) => parameter.in !== 'body').map(formatFlagExample);
208
+ const examples = [`nb api ${commandId}${requiredFlags.length ? ` ${requiredFlags.join(' ')}` : ''}`];
209
+ const firstOptional = operation.parameters.find((parameter) => !parameter.required);
210
+ if (firstOptional) {
211
+ examples.push(`${examples[0]} ${formatFlagExample(firstOptional)}`);
212
+ }
213
+ if (operation.hasBody) {
214
+ const prefix = `nb api ${commandId}${requiredNonBodyFlags.length ? ` ${requiredNonBodyFlags.join(' ')}` : ''}`;
215
+ examples.push(`${prefix} --body '${buildSampleBody(operation.parameters)}'`);
216
+ }
217
+ return [...new Set(examples)];
218
+ }
219
+ function buildDescription(operation) {
220
+ const sections = [];
221
+ if (operation.description) {
222
+ sections.push(operation.description);
223
+ }
224
+ if (operation.moduleDisplayName || operation.moduleDescription) {
225
+ sections.push([operation.moduleDisplayName ? `Module: ${operation.moduleDisplayName}` : undefined, operation.moduleDescription]
226
+ .filter(Boolean)
227
+ .join('\n'));
228
+ }
229
+ if (operation.resourceDisplayName || operation.resourceDescription) {
230
+ sections.push([operation.resourceDisplayName ? `Resource: ${operation.resourceDisplayName}` : undefined, operation.resourceDescription]
231
+ .filter(Boolean)
232
+ .join('\n'));
233
+ }
234
+ sections.push(`HTTP ${operation.method.toUpperCase()} ${operation.pathTemplate}`);
235
+ if (operation.tags?.length) {
236
+ sections.push(`Tags: ${operation.tags.join(', ')}`);
237
+ }
238
+ if (operation.hasBody) {
239
+ const bodyFlags = operation.parameters.filter((parameter) => parameter.in === 'body').map((parameter) => `--${parameter.flagName}`);
240
+ sections.push(bodyFlags.length
241
+ ? `Request body: use body field flags (${bodyFlags.join(', ')}) or pass raw JSON via \`--body\` / \`--body-file\`.`
242
+ : 'Request body: JSON via `--body` or `--body-file`.');
243
+ }
244
+ return sections.join('\n\n');
245
+ }
246
+ function shouldIncludeResource(resourceKey, moduleConfig) {
247
+ if (!resourceKey) {
248
+ return false;
249
+ }
250
+ const includes = moduleConfig.resources?.includes;
251
+ const excludes = moduleConfig.resources?.excludes;
252
+ if (includes?.length && !includes.some((pattern) => matchesPattern(resourceKey, pattern))) {
253
+ return false;
254
+ }
255
+ if (excludes?.length && excludes.some((pattern) => matchesPattern(resourceKey, pattern))) {
256
+ return false;
257
+ }
258
+ return true;
259
+ }
260
+ function getPrimaryResourceKey(pathTemplate) {
261
+ return pathTemplate
262
+ .replace(/^\/+/, '')
263
+ .split(/[/:]/)
264
+ .find((segment) => segment && !segment.startsWith('{'));
265
+ }
266
+ function getOperationMatchKeys(operation) {
267
+ const normalizedPath = operation.pathTemplate.replace(/^\/+/, '');
268
+ const action = normalizedPath.includes(':') ? normalizedPath.slice(normalizedPath.lastIndexOf(':') + 1) : 'call';
269
+ const resourcePath = normalizedPath.includes(':') ? normalizedPath.slice(0, normalizedPath.lastIndexOf(':')) : normalizedPath;
270
+ return [
271
+ action,
272
+ operation.operationId,
273
+ operation.pathTemplate,
274
+ `${resourcePath}:${action}`,
275
+ `${operation.method.toLowerCase()}:${operation.pathTemplate}`,
276
+ `${operation.method.toUpperCase()}:${operation.pathTemplate}`,
277
+ ].filter((value) => Boolean(value));
278
+ }
279
+ function shouldIncludeOperation(operation, resourceConfig) {
280
+ const includes = resourceConfig?.operations?.includes;
281
+ const excludes = resourceConfig?.operations?.excludes;
282
+ const matchKeys = getOperationMatchKeys(operation);
283
+ if (includes?.length && !includes.some((pattern) => matchKeys.some((value) => matchesPattern(value, pattern)))) {
284
+ return false;
285
+ }
286
+ if (excludes?.length && excludes.some((pattern) => matchKeys.some((value) => matchesPattern(value, pattern)))) {
287
+ return false;
288
+ }
289
+ return true;
290
+ }
291
+ function scoreModuleMatch(moduleKey, moduleConfig, resourceKey, operation) {
292
+ if (!shouldIncludeResource(resourceKey, moduleConfig)) {
293
+ return -1;
294
+ }
295
+ const resourceConfig = resourceKey ? moduleConfig.resources?.overrides?.[resourceKey] : undefined;
296
+ if (!shouldIncludeOperation(operation, resourceConfig)) {
297
+ return -1;
298
+ }
299
+ let score = 10;
300
+ if (resourceConfig?.operations?.includes?.length) {
301
+ score += 100;
302
+ }
303
+ const moduleResourceNames = new Set(Object.entries(moduleConfig.resources?.overrides ?? {}).flatMap(([resourceName, resourceConfigValue]) => [
304
+ toKebabCase(resourceName),
305
+ toKebabCase(resourceConfigValue?.name ?? resourceName),
306
+ ]));
307
+ const tagTokens = (operation.tags ?? []).flatMap((tag) => tag.split(/[./:]/).map((part) => toKebabCase(part)).filter(Boolean));
308
+ const tokenMatches = tagTokens.filter((token) => moduleResourceNames.has(token));
309
+ score += tokenMatches.length * 5;
310
+ if (operation.pathTemplate.includes('/desktopRoutes')) {
311
+ score += moduleKey === 'client' ? 20 : 0;
312
+ }
313
+ return score;
314
+ }
315
+ function resolveModuleKey(buildConfig, operation) {
316
+ const resourceKey = getPrimaryResourceKey(operation.pathTemplate);
317
+ const candidates = Object.entries(buildConfig.modules ?? {})
318
+ .filter(([, moduleConfig]) => moduleConfig.include !== false)
319
+ .map(([moduleKey, moduleConfig]) => ({
320
+ moduleKey,
321
+ moduleConfig,
322
+ score: scoreModuleMatch(moduleKey, moduleConfig, resourceKey, operation),
323
+ }))
324
+ .filter((item) => item.score >= 0)
325
+ .sort((left, right) => right.score - left.score);
326
+ return candidates[0];
327
+ }
328
+ export async function generateRuntime(document, configFile, baseUrl) {
329
+ const buildConfig = await loadBuildConfig(configFile);
330
+ const commands = [];
331
+ for (const { method, pathTemplate, operation } of collectOperations(document)) {
332
+ const resolvedModule = resolveModuleKey(buildConfig, {
333
+ method,
334
+ pathTemplate,
335
+ operationId: operation.operationId,
336
+ tags: operation.tags,
337
+ });
338
+ if (!resolvedModule) {
339
+ continue;
340
+ }
341
+ const { moduleKey, moduleConfig } = resolvedModule;
342
+ const resourceKey = getPrimaryResourceKey(pathTemplate);
343
+ const resourceConfig = resourceKey ? moduleConfig.resources?.overrides?.[resourceKey] : undefined;
344
+ const usedFlagNames = new Set();
345
+ const parameters = (operation.parameters ?? []).filter(isSupportedParameter).map((parameter) => toGeneratedParameter(parameter, usedFlagNames));
346
+ const bodyParameters = extractBodyParameters(operation.requestBody, usedFlagNames);
347
+ const allParameters = [...parameters, ...bodyParameters];
348
+ const hasBody = Boolean(operation.requestBody && !('$ref' in operation.requestBody));
349
+ const moduleDisplayName = moduleConfig.name ?? moduleKey;
350
+ const moduleDescription = moduleConfig.description;
351
+ const resourceDisplayName = resourceConfig?.name ?? resourceKey;
352
+ const resourceDescription = resourceConfig?.description;
353
+ const operationText = resolveOperationText({
354
+ summary: operation.summary,
355
+ description: operation.description,
356
+ });
357
+ const resourceSegments = toResourceSegments(pathTemplate);
358
+ const mappedResourceSegments = resourceSegments.length && resourceConfig?.name
359
+ ? [toKebabCase(resourceConfig.name), ...resourceSegments.slice(1)]
360
+ : resourceSegments;
361
+ const segments = [
362
+ ...(resourceConfig?.topLevel ? [] : [toKebabCase(moduleDisplayName)]),
363
+ ...mappedResourceSegments,
364
+ ];
365
+ commands.push({
366
+ moduleName: moduleKey,
367
+ moduleDisplayName,
368
+ moduleDescription,
369
+ resourceName: resourceKey,
370
+ logicalResourceName: toLogicalResourceName(pathTemplate),
371
+ actionName: toLogicalActionName(pathTemplate),
372
+ resourceDisplayName,
373
+ resourceDescription,
374
+ commandId: segments.join(' '),
375
+ method,
376
+ pathTemplate,
377
+ tags: operation.tags,
378
+ summary: operationText.summary ?? `${method.toUpperCase()} ${pathTemplate}`,
379
+ description: buildDescription({
380
+ moduleDisplayName: resourceConfig?.topLevel ? undefined : moduleDisplayName,
381
+ moduleDescription: resourceConfig?.topLevel ? undefined : moduleDescription,
382
+ resourceDisplayName,
383
+ resourceDescription,
384
+ method,
385
+ pathTemplate,
386
+ tags: operation.tags,
387
+ description: operationText.description,
388
+ hasBody,
389
+ parameters: allParameters,
390
+ }),
391
+ examples: buildExamples(segments.join(' '), {
392
+ parameters: allParameters,
393
+ hasBody,
394
+ }),
395
+ parameters: allParameters,
396
+ hasBody,
397
+ bodyRequired: operation.requestBody && !('$ref' in operation.requestBody) ? operation.requestBody.required : undefined,
398
+ });
399
+ }
400
+ const schemaHash = createHash('sha1').update(JSON.stringify(document)).digest('hex').slice(0, 8);
401
+ return {
402
+ version: String(document.info?.version ?? 'unknown'),
403
+ schemaHash,
404
+ generatedAt: new Date().toISOString(),
405
+ baseUrl,
406
+ commands: commands.sort((left, right) => left.commandId.localeCompare(right.commandId)),
407
+ };
408
+ }
@@ -0,0 +1,56 @@
1
+ import fs, { promises as fsp } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolveCliHomeDir } from './cli-home.js';
4
+ function getHomeDir(options = {}) {
5
+ return resolveCliHomeDir(options.scope);
6
+ }
7
+ export function getVersionsDir(options = {}) {
8
+ return path.join(getHomeDir(options), 'versions');
9
+ }
10
+ export function getVersionDir(version, options = {}) {
11
+ return path.join(getVersionsDir(options), version);
12
+ }
13
+ function getRuntimeFile(version, options = {}) {
14
+ return path.join(getVersionDir(version, options), 'commands.json');
15
+ }
16
+ export async function saveRuntime(runtime, options = {}) {
17
+ const versionDir = getVersionDir(runtime.version, options);
18
+ await fsp.mkdir(versionDir, { recursive: true });
19
+ await fsp.writeFile(getRuntimeFile(runtime.version, options), JSON.stringify(runtime, null, 2));
20
+ }
21
+ export async function loadRuntime(version, options = {}) {
22
+ try {
23
+ const content = await fsp.readFile(getRuntimeFile(version, options), 'utf8');
24
+ return JSON.parse(content);
25
+ }
26
+ catch (error) {
27
+ return undefined;
28
+ }
29
+ }
30
+ export function loadRuntimeSync(version, options = {}) {
31
+ if (!version) {
32
+ return undefined;
33
+ }
34
+ try {
35
+ const content = fs.readFileSync(getRuntimeFile(version, options), 'utf8');
36
+ return JSON.parse(content);
37
+ }
38
+ catch (error) {
39
+ return undefined;
40
+ }
41
+ }
42
+ export async function listRuntimes(options = {}) {
43
+ try {
44
+ const entries = await fsp.readdir(getVersionsDir(options), { withFileTypes: true });
45
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
46
+ }
47
+ catch (error) {
48
+ return [];
49
+ }
50
+ }
51
+ export async function deleteRuntime(version, options = {}) {
52
+ await fsp.rm(getVersionDir(version, options), { recursive: true, force: true });
53
+ }
54
+ export function hasRuntimeSync(version, options = {}) {
55
+ return version ? fs.existsSync(getRuntimeFile(version, options)) : false;
56
+ }