@nocobase/ctl 0.1.5

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 (38) hide show
  1. package/README.md +164 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +14 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +20 -0
  6. package/dist/commands/env/add.js +51 -0
  7. package/dist/commands/env/index.js +27 -0
  8. package/dist/commands/env/list.js +31 -0
  9. package/dist/commands/env/remove.js +54 -0
  10. package/dist/commands/env/update.js +54 -0
  11. package/dist/commands/env/use.js +26 -0
  12. package/dist/commands/resource/create.js +15 -0
  13. package/dist/commands/resource/destroy.js +15 -0
  14. package/dist/commands/resource/get.js +15 -0
  15. package/dist/commands/resource/index.js +7 -0
  16. package/dist/commands/resource/list.js +16 -0
  17. package/dist/commands/resource/query.js +15 -0
  18. package/dist/commands/resource/update.js +15 -0
  19. package/dist/generated/command-registry.js +81 -0
  20. package/dist/lib/api-client.js +196 -0
  21. package/dist/lib/auth-store.js +92 -0
  22. package/dist/lib/bootstrap.js +263 -0
  23. package/dist/lib/build-config.js +10 -0
  24. package/dist/lib/cli-home.js +30 -0
  25. package/dist/lib/generated-command.js +113 -0
  26. package/dist/lib/naming.js +70 -0
  27. package/dist/lib/openapi.js +254 -0
  28. package/dist/lib/post-processors.js +23 -0
  29. package/dist/lib/resource-command.js +331 -0
  30. package/dist/lib/resource-request.js +103 -0
  31. package/dist/lib/runtime-generator.js +383 -0
  32. package/dist/lib/runtime-store.js +56 -0
  33. package/dist/lib/ui.js +154 -0
  34. package/dist/post-processors/data-modeling.js +66 -0
  35. package/dist/post-processors/data-source-manager.js +114 -0
  36. package/dist/post-processors/index.js +19 -0
  37. package/nocobase-ctl.config.json +327 -0
  38. package/package.json +61 -0
@@ -0,0 +1,383 @@
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 buildExamples(commandId, operation) {
183
+ const requiredFlags = operation.parameters.filter((parameter) => parameter.required).map(formatFlagExample);
184
+ const examples = [`nocobase ${commandId}${requiredFlags.length ? ` ${requiredFlags.join(' ')}` : ''}`];
185
+ const firstOptional = operation.parameters.find((parameter) => !parameter.required);
186
+ if (firstOptional) {
187
+ examples.push(`${examples[0]} ${formatFlagExample(firstOptional)}`);
188
+ }
189
+ if (operation.hasBody) {
190
+ examples.push(`${examples[0]} --body '{"key":"value"}'`);
191
+ }
192
+ return [...new Set(examples)];
193
+ }
194
+ function buildDescription(operation) {
195
+ const sections = [];
196
+ if (operation.description) {
197
+ sections.push(operation.description);
198
+ }
199
+ if (operation.moduleDisplayName || operation.moduleDescription) {
200
+ sections.push([operation.moduleDisplayName ? `Module: ${operation.moduleDisplayName}` : undefined, operation.moduleDescription]
201
+ .filter(Boolean)
202
+ .join('\n'));
203
+ }
204
+ if (operation.resourceDisplayName || operation.resourceDescription) {
205
+ sections.push([operation.resourceDisplayName ? `Resource: ${operation.resourceDisplayName}` : undefined, operation.resourceDescription]
206
+ .filter(Boolean)
207
+ .join('\n'));
208
+ }
209
+ sections.push(`HTTP ${operation.method.toUpperCase()} ${operation.pathTemplate}`);
210
+ if (operation.tags?.length) {
211
+ sections.push(`Tags: ${operation.tags.join(', ')}`);
212
+ }
213
+ if (operation.hasBody) {
214
+ const bodyFlags = operation.parameters.filter((parameter) => parameter.in === 'body').map((parameter) => `--${parameter.flagName}`);
215
+ sections.push(bodyFlags.length
216
+ ? `Request body: use body field flags (${bodyFlags.join(', ')}) or pass raw JSON via \`--body\` / \`--body-file\`.`
217
+ : 'Request body: JSON via `--body` or `--body-file`.');
218
+ }
219
+ return sections.join('\n\n');
220
+ }
221
+ function shouldIncludeResource(resourceKey, moduleConfig) {
222
+ if (!resourceKey) {
223
+ return false;
224
+ }
225
+ const includes = moduleConfig.resources?.includes;
226
+ const excludes = moduleConfig.resources?.excludes;
227
+ if (includes?.length && !includes.some((pattern) => matchesPattern(resourceKey, pattern))) {
228
+ return false;
229
+ }
230
+ if (excludes?.length && excludes.some((pattern) => matchesPattern(resourceKey, pattern))) {
231
+ return false;
232
+ }
233
+ return true;
234
+ }
235
+ function getPrimaryResourceKey(pathTemplate) {
236
+ return pathTemplate
237
+ .replace(/^\/+/, '')
238
+ .split(/[/:]/)
239
+ .find((segment) => segment && !segment.startsWith('{'));
240
+ }
241
+ function getOperationMatchKeys(operation) {
242
+ const normalizedPath = operation.pathTemplate.replace(/^\/+/, '');
243
+ const action = normalizedPath.includes(':') ? normalizedPath.slice(normalizedPath.lastIndexOf(':') + 1) : 'call';
244
+ const resourcePath = normalizedPath.includes(':') ? normalizedPath.slice(0, normalizedPath.lastIndexOf(':')) : normalizedPath;
245
+ return [
246
+ action,
247
+ operation.operationId,
248
+ operation.pathTemplate,
249
+ `${resourcePath}:${action}`,
250
+ `${operation.method.toLowerCase()}:${operation.pathTemplate}`,
251
+ `${operation.method.toUpperCase()}:${operation.pathTemplate}`,
252
+ ].filter((value) => Boolean(value));
253
+ }
254
+ function shouldIncludeOperation(operation, resourceConfig) {
255
+ const includes = resourceConfig?.operations?.includes;
256
+ const excludes = resourceConfig?.operations?.excludes;
257
+ const matchKeys = getOperationMatchKeys(operation);
258
+ if (includes?.length && !includes.some((pattern) => matchKeys.some((value) => matchesPattern(value, pattern)))) {
259
+ return false;
260
+ }
261
+ if (excludes?.length && excludes.some((pattern) => matchKeys.some((value) => matchesPattern(value, pattern)))) {
262
+ return false;
263
+ }
264
+ return true;
265
+ }
266
+ function scoreModuleMatch(moduleKey, moduleConfig, resourceKey, operation) {
267
+ if (!shouldIncludeResource(resourceKey, moduleConfig)) {
268
+ return -1;
269
+ }
270
+ const resourceConfig = resourceKey ? moduleConfig.resources?.overrides?.[resourceKey] : undefined;
271
+ if (!shouldIncludeOperation(operation, resourceConfig)) {
272
+ return -1;
273
+ }
274
+ let score = 10;
275
+ if (resourceConfig?.operations?.includes?.length) {
276
+ score += 100;
277
+ }
278
+ const moduleResourceNames = new Set(Object.entries(moduleConfig.resources?.overrides ?? {}).flatMap(([resourceName, resourceConfigValue]) => [
279
+ toKebabCase(resourceName),
280
+ toKebabCase(resourceConfigValue?.name ?? resourceName),
281
+ ]));
282
+ const tagTokens = (operation.tags ?? []).flatMap((tag) => tag.split(/[./:]/).map((part) => toKebabCase(part)).filter(Boolean));
283
+ const tokenMatches = tagTokens.filter((token) => moduleResourceNames.has(token));
284
+ score += tokenMatches.length * 5;
285
+ if (operation.pathTemplate.includes('/desktopRoutes')) {
286
+ score += moduleKey === 'client' ? 20 : 0;
287
+ }
288
+ return score;
289
+ }
290
+ function resolveModuleKey(buildConfig, operation) {
291
+ const resourceKey = getPrimaryResourceKey(operation.pathTemplate);
292
+ const candidates = Object.entries(buildConfig.modules ?? {})
293
+ .filter(([, moduleConfig]) => moduleConfig.include !== false)
294
+ .map(([moduleKey, moduleConfig]) => ({
295
+ moduleKey,
296
+ moduleConfig,
297
+ score: scoreModuleMatch(moduleKey, moduleConfig, resourceKey, operation),
298
+ }))
299
+ .filter((item) => item.score >= 0)
300
+ .sort((left, right) => right.score - left.score);
301
+ return candidates[0];
302
+ }
303
+ export async function generateRuntime(document, configFile, baseUrl) {
304
+ const buildConfig = await loadBuildConfig(configFile);
305
+ const commands = [];
306
+ for (const { method, pathTemplate, operation } of collectOperations(document)) {
307
+ const resolvedModule = resolveModuleKey(buildConfig, {
308
+ method,
309
+ pathTemplate,
310
+ operationId: operation.operationId,
311
+ tags: operation.tags,
312
+ });
313
+ if (!resolvedModule) {
314
+ continue;
315
+ }
316
+ const { moduleKey, moduleConfig } = resolvedModule;
317
+ const resourceKey = getPrimaryResourceKey(pathTemplate);
318
+ const resourceConfig = resourceKey ? moduleConfig.resources?.overrides?.[resourceKey] : undefined;
319
+ const usedFlagNames = new Set();
320
+ const parameters = (operation.parameters ?? []).filter(isSupportedParameter).map((parameter) => toGeneratedParameter(parameter, usedFlagNames));
321
+ const bodyParameters = extractBodyParameters(operation.requestBody, usedFlagNames);
322
+ const allParameters = [...parameters, ...bodyParameters];
323
+ const hasBody = Boolean(operation.requestBody && !('$ref' in operation.requestBody));
324
+ const moduleDisplayName = moduleConfig.name ?? moduleKey;
325
+ const moduleDescription = moduleConfig.description;
326
+ const resourceDisplayName = resourceConfig?.name ?? resourceKey;
327
+ const resourceDescription = resourceConfig?.description;
328
+ const operationText = resolveOperationText({
329
+ summary: operation.summary,
330
+ description: operation.description,
331
+ });
332
+ const resourceSegments = toResourceSegments(pathTemplate);
333
+ const mappedResourceSegments = resourceSegments.length && resourceConfig?.name
334
+ ? [toKebabCase(resourceConfig.name), ...resourceSegments.slice(1)]
335
+ : resourceSegments;
336
+ const segments = [
337
+ ...(resourceConfig?.topLevel ? [] : [toKebabCase(moduleDisplayName)]),
338
+ ...mappedResourceSegments,
339
+ ];
340
+ commands.push({
341
+ moduleName: moduleKey,
342
+ moduleDisplayName,
343
+ moduleDescription,
344
+ resourceName: resourceKey,
345
+ logicalResourceName: toLogicalResourceName(pathTemplate),
346
+ actionName: toLogicalActionName(pathTemplate),
347
+ resourceDisplayName,
348
+ resourceDescription,
349
+ commandId: segments.join(' '),
350
+ method,
351
+ pathTemplate,
352
+ tags: operation.tags,
353
+ summary: operationText.summary ?? `${method.toUpperCase()} ${pathTemplate}`,
354
+ description: buildDescription({
355
+ moduleDisplayName: resourceConfig?.topLevel ? undefined : moduleDisplayName,
356
+ moduleDescription: resourceConfig?.topLevel ? undefined : moduleDescription,
357
+ resourceDisplayName,
358
+ resourceDescription,
359
+ method,
360
+ pathTemplate,
361
+ tags: operation.tags,
362
+ description: operationText.description,
363
+ hasBody,
364
+ parameters: allParameters,
365
+ }),
366
+ examples: buildExamples(segments.join(' '), {
367
+ parameters: allParameters,
368
+ hasBody,
369
+ }),
370
+ parameters: allParameters,
371
+ hasBody,
372
+ bodyRequired: operation.requestBody && !('$ref' in operation.requestBody) ? operation.requestBody.required : undefined,
373
+ });
374
+ }
375
+ const schemaHash = createHash('sha1').update(JSON.stringify(document)).digest('hex').slice(0, 8);
376
+ return {
377
+ version: String(document.info?.version ?? 'unknown'),
378
+ schemaHash,
379
+ generatedAt: new Date().toISOString(),
380
+ baseUrl,
381
+ commands: commands.sort((left, right) => left.commandId.localeCompare(right.commandId)),
382
+ };
383
+ }
@@ -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
+ }
package/dist/lib/ui.js ADDED
@@ -0,0 +1,154 @@
1
+ import readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import ora from 'ora';
4
+ import pc from 'picocolors';
5
+ let activeSpinner;
6
+ let verboseMode = false;
7
+ function stringWidth(value) {
8
+ return Array.from(value).length;
9
+ }
10
+ function pad(value, width) {
11
+ const padding = Math.max(0, width - stringWidth(value));
12
+ return `${value}${' '.repeat(padding)}`;
13
+ }
14
+ export function isInteractiveTerminal() {
15
+ return Boolean(input.isTTY && output.isTTY);
16
+ }
17
+ export function setVerboseMode(value) {
18
+ verboseMode = value;
19
+ }
20
+ export function isVerboseMode() {
21
+ return verboseMode;
22
+ }
23
+ export async function promptText(message, options) {
24
+ if (!isInteractiveTerminal()) {
25
+ return options?.defaultValue ?? '';
26
+ }
27
+ const rl = readline.createInterface({
28
+ input,
29
+ output,
30
+ terminal: true,
31
+ });
32
+ try {
33
+ const suffix = options?.defaultValue ? ` (${options.defaultValue})` : '';
34
+ const hint = options?.secret ? ' [input visible]' : '';
35
+ const prompt = `${message}${suffix}${hint}: `;
36
+ const answer = await rl.question(prompt);
37
+ return answer.trim() || options?.defaultValue || '';
38
+ }
39
+ finally {
40
+ rl.close();
41
+ }
42
+ }
43
+ export async function confirmAction(message, options) {
44
+ if (!isInteractiveTerminal()) {
45
+ return Boolean(options?.defaultValue);
46
+ }
47
+ stopTask();
48
+ const rl = readline.createInterface({
49
+ input,
50
+ output,
51
+ terminal: true,
52
+ });
53
+ try {
54
+ const suffix = options?.defaultValue ? pc.dim('[Y/n]') : pc.dim('[y/N]');
55
+ const prompt = `${pc.yellow('?')} ${pc.bold(message)} ${suffix} `;
56
+ const answer = await rl.question(prompt);
57
+ const normalized = answer.trim().toLowerCase();
58
+ if (!normalized) {
59
+ return Boolean(options?.defaultValue);
60
+ }
61
+ return normalized === 'y' || normalized === 'yes';
62
+ }
63
+ finally {
64
+ rl.close();
65
+ }
66
+ }
67
+ export function printSection(title) {
68
+ console.log(pc.bold(title));
69
+ }
70
+ export function printInfo(message) {
71
+ if (activeSpinner) {
72
+ activeSpinner.info(pc.cyan(message));
73
+ activeSpinner = undefined;
74
+ return;
75
+ }
76
+ console.log(pc.cyan(message));
77
+ }
78
+ export function printVerbose(message) {
79
+ if (!verboseMode) {
80
+ return;
81
+ }
82
+ printInfo(message);
83
+ }
84
+ export function printSuccess(message) {
85
+ if (activeSpinner) {
86
+ activeSpinner.succeed(pc.green(message));
87
+ activeSpinner = undefined;
88
+ return;
89
+ }
90
+ console.log(pc.green(message));
91
+ }
92
+ export function printWarning(message) {
93
+ if (activeSpinner) {
94
+ activeSpinner.warn(pc.yellow(message));
95
+ activeSpinner = undefined;
96
+ return;
97
+ }
98
+ console.log(pc.yellow(message));
99
+ }
100
+ export function printVerboseWarning(message) {
101
+ if (!verboseMode) {
102
+ return;
103
+ }
104
+ printWarning(message);
105
+ }
106
+ export function startTask(message) {
107
+ if (activeSpinner) {
108
+ activeSpinner.stop();
109
+ }
110
+ activeSpinner = ora({
111
+ text: pc.cyan(message),
112
+ isSilent: !isInteractiveTerminal(),
113
+ }).start();
114
+ if (!isInteractiveTerminal()) {
115
+ console.log(pc.cyan(message));
116
+ }
117
+ }
118
+ export function updateTask(message) {
119
+ if (!activeSpinner) {
120
+ startTask(message);
121
+ return;
122
+ }
123
+ activeSpinner.text = pc.cyan(message);
124
+ }
125
+ export function succeedTask(message) {
126
+ if (activeSpinner) {
127
+ activeSpinner.succeed(pc.green(message));
128
+ activeSpinner = undefined;
129
+ return;
130
+ }
131
+ console.log(pc.green(message));
132
+ }
133
+ export function failTask(message) {
134
+ if (activeSpinner) {
135
+ activeSpinner.fail(pc.red(message));
136
+ activeSpinner = undefined;
137
+ return;
138
+ }
139
+ console.error(pc.red(message));
140
+ }
141
+ export function stopTask() {
142
+ if (activeSpinner) {
143
+ activeSpinner.stop();
144
+ activeSpinner = undefined;
145
+ }
146
+ }
147
+ export function renderTable(headers, rows) {
148
+ const widths = headers.map((header, index) => {
149
+ return rows.reduce((max, row) => Math.max(max, stringWidth(row[index] ?? '')), stringWidth(header));
150
+ });
151
+ const renderRow = (row) => row.map((cell, index) => pad(cell ?? '', widths[index])).join(' ').trimEnd();
152
+ const divider = widths.map((width) => '-'.repeat(width)).join(' ');
153
+ return [renderRow(headers), divider, ...rows.map(renderRow)].join('\n');
154
+ }
@@ -0,0 +1,66 @@
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 { postProcessorRegistry } from "../lib/post-processors.js";
10
+ function toArray(value) {
11
+ if (Array.isArray(value)) {
12
+ return value;
13
+ }
14
+ if (Array.isArray(value?.data)) {
15
+ return value.data;
16
+ }
17
+ return [];
18
+ }
19
+ function pickCollectionSummary(item) {
20
+ return {
21
+ key: item?.key,
22
+ name: item?.name,
23
+ title: item?.title,
24
+ description: item?.description,
25
+ };
26
+ }
27
+ function pickFieldSummary(item) {
28
+ return {
29
+ key: item?.key,
30
+ name: item?.name,
31
+ type: item?.type,
32
+ title: item?.uiSchema?.title,
33
+ description: item?.description,
34
+ collectionName: item?.collectionName,
35
+ };
36
+ }
37
+ function simplifyCollectionsListResult(result) {
38
+ const items = toArray(result);
39
+ return {
40
+ data: items.map((item) => pickCollectionSummary(item)),
41
+ meta: result?.meta,
42
+ nextActions: [
43
+ 'Use collections:get with filterByTk=<collectionName> to inspect one collection in detail.',
44
+ 'Use collections:apply with --verify when you need normalized verification in the same response.',
45
+ ],
46
+ };
47
+ }
48
+ function simplifyFieldsListResult(result) {
49
+ const items = toArray(result);
50
+ return {
51
+ data: items.map((item) => pickFieldSummary(item)),
52
+ meta: result?.meta,
53
+ };
54
+ }
55
+ export function registerDataModelingPostProcessors() {
56
+ postProcessorRegistry.register('collections', 'list', simplifyCollectionsListResult);
57
+ postProcessorRegistry.register('collections', 'apply', (result) => ({
58
+ data: pickCollectionSummary(result?.data || {}),
59
+ verify: result?.verify,
60
+ }));
61
+ postProcessorRegistry.register('collections', 'verify', (result) => result);
62
+ postProcessorRegistry.register('fields', 'apply', (result) => ({
63
+ data: pickFieldSummary(result?.data || {}),
64
+ }));
65
+ postProcessorRegistry.register('collections.fields', 'list', simplifyFieldsListResult);
66
+ }