@nocobase/cli 2.1.0-alpha.4 → 2.1.0-alpha.40

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