@nocobase/cli 2.1.0-beta.2 → 2.1.0-beta.20

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