@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.
- package/LICENSE.txt +107 -0
- package/README.md +134 -63
- package/bin/run.cmd +3 -0
- package/bin/run.js +87 -0
- package/dist/commands/api/index.js +8 -0
- package/dist/commands/env/add.js +53 -0
- package/dist/commands/env/auth.js +36 -0
- package/dist/commands/env/index.js +27 -0
- package/dist/commands/env/list.js +31 -0
- package/dist/commands/env/remove.js +54 -0
- package/dist/commands/env/update.js +58 -0
- package/dist/commands/env/use.js +26 -0
- package/dist/commands/resource/create.js +15 -0
- package/dist/commands/resource/destroy.js +15 -0
- package/dist/commands/resource/get.js +15 -0
- package/dist/commands/resource/index.js +7 -0
- package/dist/commands/resource/list.js +16 -0
- package/dist/commands/resource/query.js +15 -0
- package/dist/commands/resource/update.js +15 -0
- package/dist/generated/command-registry.js +88 -0
- package/dist/lib/api-client.js +199 -0
- package/dist/lib/auth-store.js +155 -0
- package/dist/lib/bootstrap.js +349 -0
- package/dist/lib/build-config.js +10 -0
- package/dist/lib/cli-home.js +30 -0
- package/dist/lib/env-auth.js +405 -0
- package/dist/lib/generated-command.js +142 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/openapi.js +254 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/resource-command.js +335 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/runtime-generator.js +408 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/ui.js +169 -0
- package/dist/post-processors/data-modeling.js +66 -0
- package/dist/post-processors/data-source-manager.js +114 -0
- package/dist/post-processors/index.js +19 -0
- package/nocobase-ctl.config.json +327 -0
- package/package.json +50 -25
- package/LICENSE +0 -201
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -184
- package/src/cli.js +0 -28
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -81
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -168
- package/src/commands/create-nginx-conf.js +0 -53
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -290
- package/src/commands/doc.js +0 -76
- package/src/commands/e2e.js +0 -265
- package/src/commands/global.js +0 -43
- package/src/commands/index.js +0 -45
- package/src/commands/instance-id.js +0 -47
- package/src/commands/locale/cronstrue.js +0 -122
- package/src/commands/locale/react-js-cron/en-US.json +0 -75
- package/src/commands/locale/react-js-cron/index.js +0 -17
- package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
- package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
- package/src/commands/locale.js +0 -81
- package/src/commands/p-test.js +0 -88
- package/src/commands/perf.js +0 -63
- package/src/commands/pkg.js +0 -321
- package/src/commands/pm2.js +0 -37
- package/src/commands/postinstall.js +0 -88
- package/src/commands/start.js +0 -148
- package/src/commands/tar.js +0 -36
- package/src/commands/test-coverage.js +0 -55
- package/src/commands/test.js +0 -107
- package/src/commands/umi.js +0 -33
- package/src/commands/update-deps.js +0 -72
- package/src/commands/upgrade.js +0 -47
- package/src/commands/view-license-key.js +0 -44
- package/src/index.js +0 -14
- package/src/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -607
- package/templates/bundle-status.html +0 -338
- package/templates/create-app-package.json +0 -39
- package/templates/plugin/.npmignore.tpl +0 -2
- package/templates/plugin/README.md.tpl +0 -1
- package/templates/plugin/client-v2.d.ts +0 -2
- package/templates/plugin/client-v2.js +0 -1
- package/templates/plugin/client.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -12
- package/templates/plugin/server.d.ts +0 -2
- package/templates/plugin/server.js +0 -1
- package/templates/plugin/src/client/client.d.ts +0 -249
- package/templates/plugin/src/client/index.tsx.tpl +0 -1
- package/templates/plugin/src/client/locale.ts +0 -21
- package/templates/plugin/src/client/models/index.ts +0 -12
- package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
- package/templates/plugin/src/client-v2/client.d.ts +0 -103
- package/templates/plugin/src/client-v2/index.tsx.tpl +0 -1
- package/templates/plugin/src/client-v2/plugin.tsx.tpl +0 -7
- package/templates/plugin/src/index.ts +0 -2
- package/templates/plugin/src/locale/en-US.json +0 -1
- package/templates/plugin/src/locale/zh-CN.json +0 -1
- package/templates/plugin/src/server/collections/.gitkeep +0 -0
- package/templates/plugin/src/server/index.ts.tpl +0 -1
- package/templates/plugin/src/server/plugin.ts.tpl +0 -19
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import SwaggerParser from '@apidevtools/swagger-parser';
|
|
5
|
+
import ts from 'typescript';
|
|
6
|
+
const SWAGGER_FILENAMES = new Set(['index.json', 'index.ts', 'swagger.json']);
|
|
7
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'];
|
|
8
|
+
function isPluginSwaggerSource(filePath) {
|
|
9
|
+
return (filePath.includes(`${path.sep}packages${path.sep}plugins${path.sep}`) &&
|
|
10
|
+
filePath.includes(`${path.sep}src${path.sep}swagger${path.sep}`));
|
|
11
|
+
}
|
|
12
|
+
function isCoreSwaggerSource(filePath) {
|
|
13
|
+
return filePath.includes(`${path.sep}packages${path.sep}core${path.sep}server${path.sep}src${path.sep}swagger${path.sep}`);
|
|
14
|
+
}
|
|
15
|
+
function toModuleName(filePath) {
|
|
16
|
+
if (isCoreSwaggerSource(filePath)) {
|
|
17
|
+
return 'core';
|
|
18
|
+
}
|
|
19
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
20
|
+
const pluginMatch = normalized.match(/packages\/plugins\/@nocobase\/plugin-([^/]+)\//);
|
|
21
|
+
return pluginMatch?.[1] ?? '';
|
|
22
|
+
}
|
|
23
|
+
function toPackageFile(filePath) {
|
|
24
|
+
if (isCoreSwaggerSource(filePath)) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
28
|
+
const match = normalized.match(/^(.*\/packages\/plugins\/@nocobase\/plugin-[^/]+)\//);
|
|
29
|
+
return match ? `${match[1]}/package.json` : '';
|
|
30
|
+
}
|
|
31
|
+
function toPackageName(filePath) {
|
|
32
|
+
if (isCoreSwaggerSource(filePath)) {
|
|
33
|
+
return '@nocobase/server';
|
|
34
|
+
}
|
|
35
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
36
|
+
const match = normalized.match(/packages\/plugins\/(@nocobase\/plugin-[^/]+)\//);
|
|
37
|
+
return match?.[1] ?? '';
|
|
38
|
+
}
|
|
39
|
+
async function walk(dir, result) {
|
|
40
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
const fullPath = path.join(dir, entry.name);
|
|
43
|
+
if (entry.isDirectory()) {
|
|
44
|
+
await walk(fullPath, result);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!SWAGGER_FILENAMES.has(entry.name)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!isPluginSwaggerSource(fullPath) && !isCoreSwaggerSource(fullPath)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
result.push(fullPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function discoverSwaggerSources(sourceRoot) {
|
|
57
|
+
const files = [];
|
|
58
|
+
await walk(path.join(sourceRoot, 'packages'), files);
|
|
59
|
+
return files
|
|
60
|
+
.sort()
|
|
61
|
+
.map((sourceFile) => ({
|
|
62
|
+
moduleName: toModuleName(sourceFile),
|
|
63
|
+
sourceFile,
|
|
64
|
+
sourceId: path.relative(sourceRoot, sourceFile).replace(/\\/g, '/'),
|
|
65
|
+
packageFile: toPackageFile(sourceFile) || undefined,
|
|
66
|
+
packageName: toPackageName(sourceFile) || undefined,
|
|
67
|
+
format: sourceFile.endsWith('.json') ? 'json' : 'ts',
|
|
68
|
+
}))
|
|
69
|
+
.filter((item) => item.moduleName);
|
|
70
|
+
}
|
|
71
|
+
function getPropertyName(name) {
|
|
72
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
73
|
+
return name.text;
|
|
74
|
+
}
|
|
75
|
+
if (ts.isComputedPropertyName(name) && ts.isStringLiteral(name.expression)) {
|
|
76
|
+
return name.expression.text;
|
|
77
|
+
}
|
|
78
|
+
throw new Error('Unsupported computed property in swagger object.');
|
|
79
|
+
}
|
|
80
|
+
function evaluateExpression(node) {
|
|
81
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
82
|
+
const value = {};
|
|
83
|
+
for (const property of node.properties) {
|
|
84
|
+
if (ts.isPropertyAssignment(property)) {
|
|
85
|
+
value[getPropertyName(property.name)] = evaluateExpression(property.initializer);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
89
|
+
throw new Error(`Unsupported shorthand property "${property.name.text}" in swagger object.`);
|
|
90
|
+
}
|
|
91
|
+
if (ts.isSpreadAssignment(property)) {
|
|
92
|
+
throw new Error('Unsupported spread assignment in swagger object.');
|
|
93
|
+
}
|
|
94
|
+
if (ts.isMethodDeclaration(property) || ts.isAccessor(property)) {
|
|
95
|
+
throw new Error('Unsupported method/accessor in swagger object.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
101
|
+
return node.elements.map((element) => {
|
|
102
|
+
if (ts.isSpreadElement(element)) {
|
|
103
|
+
throw new Error('Unsupported spread element in swagger array.');
|
|
104
|
+
}
|
|
105
|
+
return evaluateExpression(element);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
109
|
+
return node.text;
|
|
110
|
+
}
|
|
111
|
+
if (ts.isNumericLiteral(node)) {
|
|
112
|
+
return Number(node.text);
|
|
113
|
+
}
|
|
114
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
124
|
+
return evaluateExpression(node.expression);
|
|
125
|
+
}
|
|
126
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
127
|
+
return {
|
|
128
|
+
__target__: evaluateExpression(node.expression),
|
|
129
|
+
__property__: node.name.text,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (ts.isCallExpression(node)) {
|
|
133
|
+
const callee = evaluateExpression(node.expression);
|
|
134
|
+
const args = node.arguments.map((argument) => evaluateExpression(argument));
|
|
135
|
+
if (callee &&
|
|
136
|
+
typeof callee === 'object' &&
|
|
137
|
+
'__target__' in callee &&
|
|
138
|
+
'__property__' in callee &&
|
|
139
|
+
callee.__property__ === 'join' &&
|
|
140
|
+
Array.isArray(callee.__target__)) {
|
|
141
|
+
return callee.__target__.join(args[0] ?? ',');
|
|
142
|
+
}
|
|
143
|
+
throw new Error('Unsupported call expression in swagger object.');
|
|
144
|
+
}
|
|
145
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
146
|
+
const operand = evaluateExpression(node.operand);
|
|
147
|
+
if (node.operator === ts.SyntaxKind.MinusToken) {
|
|
148
|
+
return -operand;
|
|
149
|
+
}
|
|
150
|
+
if (node.operator === ts.SyntaxKind.PlusToken) {
|
|
151
|
+
return +operand;
|
|
152
|
+
}
|
|
153
|
+
if (node.operator === ts.SyntaxKind.ExclamationToken) {
|
|
154
|
+
return !operand;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`Unsupported swagger expression kind: ${ts.SyntaxKind[node.kind]}`);
|
|
158
|
+
}
|
|
159
|
+
function parseTypeScriptSwagger(sourceText, fileName) {
|
|
160
|
+
const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
161
|
+
for (const statement of sourceFile.statements) {
|
|
162
|
+
if (!ts.isExportAssignment(statement)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (!ts.isObjectLiteralExpression(statement.expression)) {
|
|
166
|
+
throw new Error('Expected `export default` to be an object literal.');
|
|
167
|
+
}
|
|
168
|
+
return evaluateExpression(statement.expression);
|
|
169
|
+
}
|
|
170
|
+
throw new Error('Missing `export default` in swagger source.');
|
|
171
|
+
}
|
|
172
|
+
function normalizeDocument(document) {
|
|
173
|
+
return {
|
|
174
|
+
...document,
|
|
175
|
+
openapi: document.openapi ?? '3.0.2',
|
|
176
|
+
info: {
|
|
177
|
+
...(document.info ?? {}),
|
|
178
|
+
title: document.info?.title ?? 'NocoBase API',
|
|
179
|
+
version: document.info?.version ?? '1.0.0',
|
|
180
|
+
},
|
|
181
|
+
paths: document.paths ?? {},
|
|
182
|
+
components: document.components ?? {},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export async function loadSwaggerDocument(source) {
|
|
186
|
+
const content = await fs.readFile(source.sourceFile, 'utf8');
|
|
187
|
+
const document = normalizeDocument(source.format === 'json'
|
|
188
|
+
? JSON.parse(content)
|
|
189
|
+
: parseTypeScriptSwagger(content, source.sourceFile));
|
|
190
|
+
try {
|
|
191
|
+
await SwaggerParser.validate(document);
|
|
192
|
+
return (await SwaggerParser.dereference(document));
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
return document;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function resolveLocalRef(document, ref) {
|
|
199
|
+
if (!ref.startsWith('#/')) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
return ref
|
|
203
|
+
.slice(2)
|
|
204
|
+
.split('/')
|
|
205
|
+
.reduce((current, segment) => current?.[segment], document);
|
|
206
|
+
}
|
|
207
|
+
function dereferenceNode(node, document, seen = new Set()) {
|
|
208
|
+
if (Array.isArray(node)) {
|
|
209
|
+
return node.map((item) => dereferenceNode(item, document, seen));
|
|
210
|
+
}
|
|
211
|
+
if (!node || typeof node !== 'object') {
|
|
212
|
+
return node;
|
|
213
|
+
}
|
|
214
|
+
const ref = node.$ref;
|
|
215
|
+
if (typeof ref === 'string') {
|
|
216
|
+
if (seen.has(ref)) {
|
|
217
|
+
return {};
|
|
218
|
+
}
|
|
219
|
+
const resolved = resolveLocalRef(document, ref);
|
|
220
|
+
if (!resolved) {
|
|
221
|
+
return node;
|
|
222
|
+
}
|
|
223
|
+
return dereferenceNode(resolved, document, new Set([...seen, ref]));
|
|
224
|
+
}
|
|
225
|
+
return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, dereferenceNode(value, document, seen)]));
|
|
226
|
+
}
|
|
227
|
+
export async function sha1File(filePath) {
|
|
228
|
+
const content = await fs.readFile(filePath);
|
|
229
|
+
return createHash('sha1').update(content).digest('hex');
|
|
230
|
+
}
|
|
231
|
+
export function collectOperations(document) {
|
|
232
|
+
const operations = [];
|
|
233
|
+
for (const [pathTemplate, pathItem] of Object.entries(document.paths ?? {})) {
|
|
234
|
+
for (const method of HTTP_METHODS) {
|
|
235
|
+
const operation = pathItem?.[method];
|
|
236
|
+
if (!operation || '$ref' in operation) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const parameters = [...(pathItem.parameters ?? []), ...(operation.parameters ?? [])]
|
|
240
|
+
.map((parameter) => dereferenceNode(parameter, document))
|
|
241
|
+
.filter((parameter) => Boolean(parameter && !('$ref' in parameter)));
|
|
242
|
+
operations.push({
|
|
243
|
+
method,
|
|
244
|
+
pathTemplate,
|
|
245
|
+
operation: {
|
|
246
|
+
...operation,
|
|
247
|
+
parameters,
|
|
248
|
+
requestBody: operation.requestBody ? dereferenceNode(operation.requestBody, document) : undefined,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return operations;
|
|
254
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function buildKey(resource, action) {
|
|
2
|
+
return `${resource}:${action}`;
|
|
3
|
+
}
|
|
4
|
+
class PostProcessorRegistry {
|
|
5
|
+
processors = new Map();
|
|
6
|
+
register(resource, action, processor) {
|
|
7
|
+
this.processors.set(buildKey(resource, action), processor);
|
|
8
|
+
}
|
|
9
|
+
resolve(resource, action) {
|
|
10
|
+
if (!resource || !action) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return this.processors.get(buildKey(resource, action));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export const postProcessorRegistry = new PostProcessorRegistry();
|
|
17
|
+
export async function applyPostProcessor(result, context) {
|
|
18
|
+
const processor = postProcessorRegistry.resolve(context.operation.logicalResourceName, context.operation.actionName);
|
|
19
|
+
if (!processor) {
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
return processor(result, context);
|
|
23
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { executeResourceRequest } from './resource-request.js';
|
|
3
|
+
import { setVerboseMode } from './ui.js';
|
|
4
|
+
function parseJson(value, flagName) {
|
|
5
|
+
try {
|
|
6
|
+
return JSON.parse(value);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
throw new Error(`Invalid JSON for --${flagName}: ${error?.message ?? 'parse failed'}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function parseFlexibleValue(value, flagName) {
|
|
13
|
+
if (value === undefined) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
if (trimmed.startsWith('[') || trimmed.startsWith('{') || trimmed === 'null' || trimmed === 'true' || trimmed === 'false') {
|
|
21
|
+
return parseJson(trimmed, flagName);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function parseObjectFlag(value, flagName) {
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const parsed = parseJson(value, flagName);
|
|
30
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
31
|
+
throw new Error(`--${flagName} must be a JSON object`);
|
|
32
|
+
}
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
function parseJsonArrayFlag(value, flagName) {
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const parsed = parseJson(value, flagName);
|
|
40
|
+
if (!Array.isArray(parsed)) {
|
|
41
|
+
throw new Error(`--${flagName} must be a JSON array`);
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
function parseStringArrayFlags(value, flagName) {
|
|
46
|
+
if (value === undefined) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
if (value.length === 1) {
|
|
51
|
+
const trimmed = value[0].trim();
|
|
52
|
+
if (trimmed.startsWith('[')) {
|
|
53
|
+
const parsed = parseJson(trimmed, flagName);
|
|
54
|
+
if (!Array.isArray(parsed)) {
|
|
55
|
+
throw new Error(`--${flagName} must be repeated or use a JSON array`);
|
|
56
|
+
}
|
|
57
|
+
return parsed.map((item) => String(item));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return value.map((item) => String(item));
|
|
61
|
+
}
|
|
62
|
+
const trimmed = value.trim();
|
|
63
|
+
if (trimmed.startsWith('[')) {
|
|
64
|
+
const parsed = parseJson(trimmed, flagName);
|
|
65
|
+
if (!Array.isArray(parsed)) {
|
|
66
|
+
throw new Error(`--${flagName} must be repeated or use a JSON array`);
|
|
67
|
+
}
|
|
68
|
+
return parsed.map((item) => String(item));
|
|
69
|
+
}
|
|
70
|
+
return [String(value)];
|
|
71
|
+
}
|
|
72
|
+
function printResponse(command, response, jsonOutput) {
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
command.error(`Request failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`);
|
|
75
|
+
}
|
|
76
|
+
if (jsonOutput) {
|
|
77
|
+
command.log(JSON.stringify(response.data, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
command.log(`HTTP ${response.status}`);
|
|
81
|
+
}
|
|
82
|
+
export const resourceBaseFlags = {
|
|
83
|
+
'base-url': Flags.string({
|
|
84
|
+
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
85
|
+
}),
|
|
86
|
+
verbose: Flags.boolean({
|
|
87
|
+
description: 'Show detailed progress output',
|
|
88
|
+
default: false,
|
|
89
|
+
}),
|
|
90
|
+
env: Flags.string({
|
|
91
|
+
char: 'e',
|
|
92
|
+
description: 'Environment name',
|
|
93
|
+
}),
|
|
94
|
+
role: Flags.string({
|
|
95
|
+
description: 'Role override, sent as X-Role',
|
|
96
|
+
}),
|
|
97
|
+
token: Flags.string({
|
|
98
|
+
char: 't',
|
|
99
|
+
description: 'API key override',
|
|
100
|
+
}),
|
|
101
|
+
'json-output': Flags.boolean({
|
|
102
|
+
char: 'j',
|
|
103
|
+
description: 'Print raw JSON response',
|
|
104
|
+
default: true,
|
|
105
|
+
allowNo: true,
|
|
106
|
+
}),
|
|
107
|
+
resource: Flags.string({
|
|
108
|
+
description: 'Resource name such as users, orders, or association resources like posts.comments',
|
|
109
|
+
required: true,
|
|
110
|
+
}),
|
|
111
|
+
'data-source': Flags.string({
|
|
112
|
+
description: 'Data source key. Defaults to main.',
|
|
113
|
+
}),
|
|
114
|
+
};
|
|
115
|
+
export const resourceAssociationFlags = {
|
|
116
|
+
'source-id': Flags.string({
|
|
117
|
+
description: 'Source record ID for association resources like posts.comments.',
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
export const listFlags = {
|
|
121
|
+
...resourceBaseFlags,
|
|
122
|
+
...resourceAssociationFlags,
|
|
123
|
+
filter: Flags.string({
|
|
124
|
+
description: 'Filter object as JSON',
|
|
125
|
+
}),
|
|
126
|
+
fields: Flags.string({
|
|
127
|
+
description: 'Fields to query. Repeat the flag or pass a JSON array.',
|
|
128
|
+
multiple: true,
|
|
129
|
+
}),
|
|
130
|
+
appends: Flags.string({
|
|
131
|
+
description: 'Association or appended fields to include. Repeat the flag or pass a JSON array.',
|
|
132
|
+
multiple: true,
|
|
133
|
+
}),
|
|
134
|
+
except: Flags.string({
|
|
135
|
+
description: 'Fields to exclude from the result. Repeat the flag or pass a JSON array.',
|
|
136
|
+
multiple: true,
|
|
137
|
+
}),
|
|
138
|
+
sort: Flags.string({
|
|
139
|
+
description: 'Sort fields such as -createdAt. Repeat the flag or pass a JSON array.',
|
|
140
|
+
multiple: true,
|
|
141
|
+
}),
|
|
142
|
+
page: Flags.integer({
|
|
143
|
+
description: 'Page number for list action.',
|
|
144
|
+
}),
|
|
145
|
+
'page-size': Flags.integer({
|
|
146
|
+
description: 'Page size for list action.',
|
|
147
|
+
}),
|
|
148
|
+
paginate: Flags.boolean({
|
|
149
|
+
description: 'Whether to use pagination for list action.',
|
|
150
|
+
allowNo: true,
|
|
151
|
+
}),
|
|
152
|
+
};
|
|
153
|
+
export const getFlags = {
|
|
154
|
+
...resourceBaseFlags,
|
|
155
|
+
...resourceAssociationFlags,
|
|
156
|
+
'filter-by-tk': Flags.string({
|
|
157
|
+
description: 'Primary key value used by get. Supports JSON arrays for composite or multiple keys.',
|
|
158
|
+
}),
|
|
159
|
+
fields: Flags.string({
|
|
160
|
+
description: 'Fields to query. Repeat the flag or pass a JSON array.',
|
|
161
|
+
multiple: true,
|
|
162
|
+
}),
|
|
163
|
+
appends: Flags.string({
|
|
164
|
+
description: 'Association or appended fields to include. Repeat the flag or pass a JSON array.',
|
|
165
|
+
multiple: true,
|
|
166
|
+
}),
|
|
167
|
+
except: Flags.string({
|
|
168
|
+
description: 'Fields to exclude from the result. Repeat the flag or pass a JSON array.',
|
|
169
|
+
multiple: true,
|
|
170
|
+
}),
|
|
171
|
+
};
|
|
172
|
+
export const createFlags = {
|
|
173
|
+
...resourceBaseFlags,
|
|
174
|
+
...resourceAssociationFlags,
|
|
175
|
+
values: Flags.string({
|
|
176
|
+
description: 'Record values used by create as a JSON object.',
|
|
177
|
+
required: true,
|
|
178
|
+
}),
|
|
179
|
+
whitelist: Flags.string({
|
|
180
|
+
description: 'Fields allowed to be written. Repeat the flag or pass a JSON array.',
|
|
181
|
+
multiple: true,
|
|
182
|
+
}),
|
|
183
|
+
blacklist: Flags.string({
|
|
184
|
+
description: 'Fields forbidden to be written. Repeat the flag or pass a JSON array.',
|
|
185
|
+
multiple: true,
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
188
|
+
export const updateFlags = {
|
|
189
|
+
...resourceBaseFlags,
|
|
190
|
+
...resourceAssociationFlags,
|
|
191
|
+
'filter-by-tk': Flags.string({
|
|
192
|
+
description: 'Primary key value used by update. Supports JSON arrays for composite or multiple keys.',
|
|
193
|
+
}),
|
|
194
|
+
filter: Flags.string({
|
|
195
|
+
description: 'Filter object for update as JSON.',
|
|
196
|
+
}),
|
|
197
|
+
values: Flags.string({
|
|
198
|
+
description: 'Record values used by update as a JSON object.',
|
|
199
|
+
required: true,
|
|
200
|
+
}),
|
|
201
|
+
whitelist: Flags.string({
|
|
202
|
+
description: 'Fields allowed to be written. Repeat the flag or pass a JSON array.',
|
|
203
|
+
multiple: true,
|
|
204
|
+
}),
|
|
205
|
+
blacklist: Flags.string({
|
|
206
|
+
description: 'Fields forbidden to be written. Repeat the flag or pass a JSON array.',
|
|
207
|
+
multiple: true,
|
|
208
|
+
}),
|
|
209
|
+
'update-association-values': Flags.string({
|
|
210
|
+
description: 'Association fields that should be updated together. Repeat the flag or pass a JSON array.',
|
|
211
|
+
multiple: true,
|
|
212
|
+
}),
|
|
213
|
+
'force-update': Flags.boolean({
|
|
214
|
+
description: 'Whether update should force writing unchanged values.',
|
|
215
|
+
allowNo: true,
|
|
216
|
+
}),
|
|
217
|
+
};
|
|
218
|
+
export const destroyFlags = {
|
|
219
|
+
...resourceBaseFlags,
|
|
220
|
+
...resourceAssociationFlags,
|
|
221
|
+
'filter-by-tk': Flags.string({
|
|
222
|
+
description: 'Primary key value used by destroy. Supports JSON arrays for composite or multiple keys.',
|
|
223
|
+
}),
|
|
224
|
+
filter: Flags.string({
|
|
225
|
+
description: 'Filter object for destroy as JSON.',
|
|
226
|
+
}),
|
|
227
|
+
};
|
|
228
|
+
export const queryFlags = {
|
|
229
|
+
...resourceBaseFlags,
|
|
230
|
+
measures: Flags.string({
|
|
231
|
+
description: 'Measure definitions for query aggregation as a JSON array.',
|
|
232
|
+
}),
|
|
233
|
+
dimensions: Flags.string({
|
|
234
|
+
description: 'Dimension definitions for query aggregation as a JSON array.',
|
|
235
|
+
}),
|
|
236
|
+
orders: Flags.string({
|
|
237
|
+
description: 'Order definitions for query aggregation as a JSON array.',
|
|
238
|
+
}),
|
|
239
|
+
filter: Flags.string({
|
|
240
|
+
description: 'Filter object for query as JSON.',
|
|
241
|
+
}),
|
|
242
|
+
having: Flags.string({
|
|
243
|
+
description: 'Having object for grouped query as JSON.',
|
|
244
|
+
}),
|
|
245
|
+
limit: Flags.integer({
|
|
246
|
+
description: 'Limit for query result rows.',
|
|
247
|
+
}),
|
|
248
|
+
offset: Flags.integer({
|
|
249
|
+
description: 'Offset for query result rows.',
|
|
250
|
+
}),
|
|
251
|
+
timezone: Flags.string({
|
|
252
|
+
description: 'Optional timezone for query formatting.',
|
|
253
|
+
}),
|
|
254
|
+
};
|
|
255
|
+
function pickSharedArgs(flags) {
|
|
256
|
+
return {
|
|
257
|
+
resource: flags.resource,
|
|
258
|
+
dataSource: flags['data-source'],
|
|
259
|
+
sourceId: parseFlexibleValue(flags['source-id'], 'source-id'),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
export function buildListArgs(flags) {
|
|
263
|
+
return {
|
|
264
|
+
...pickSharedArgs(flags),
|
|
265
|
+
filter: parseObjectFlag(flags.filter, 'filter'),
|
|
266
|
+
fields: parseStringArrayFlags(flags.fields, 'fields'),
|
|
267
|
+
appends: parseStringArrayFlags(flags.appends, 'appends'),
|
|
268
|
+
except: parseStringArrayFlags(flags.except, 'except'),
|
|
269
|
+
sort: parseStringArrayFlags(flags.sort, 'sort'),
|
|
270
|
+
page: flags.page,
|
|
271
|
+
pageSize: flags['page-size'],
|
|
272
|
+
paginate: flags.paginate,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
export function buildGetArgs(flags) {
|
|
276
|
+
return {
|
|
277
|
+
...pickSharedArgs(flags),
|
|
278
|
+
filterByTk: parseFlexibleValue(flags['filter-by-tk'], 'filter-by-tk'),
|
|
279
|
+
fields: parseStringArrayFlags(flags.fields, 'fields'),
|
|
280
|
+
appends: parseStringArrayFlags(flags.appends, 'appends'),
|
|
281
|
+
except: parseStringArrayFlags(flags.except, 'except'),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
export function buildCreateArgs(flags) {
|
|
285
|
+
return {
|
|
286
|
+
...pickSharedArgs(flags),
|
|
287
|
+
values: parseObjectFlag(flags.values, 'values'),
|
|
288
|
+
whitelist: parseStringArrayFlags(flags.whitelist, 'whitelist'),
|
|
289
|
+
blacklist: parseStringArrayFlags(flags.blacklist, 'blacklist'),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
export function buildUpdateArgs(flags) {
|
|
293
|
+
return {
|
|
294
|
+
...pickSharedArgs(flags),
|
|
295
|
+
filterByTk: parseFlexibleValue(flags['filter-by-tk'], 'filter-by-tk'),
|
|
296
|
+
filter: parseObjectFlag(flags.filter, 'filter'),
|
|
297
|
+
values: parseObjectFlag(flags.values, 'values'),
|
|
298
|
+
whitelist: parseStringArrayFlags(flags.whitelist, 'whitelist'),
|
|
299
|
+
blacklist: parseStringArrayFlags(flags.blacklist, 'blacklist'),
|
|
300
|
+
updateAssociationValues: parseStringArrayFlags(flags['update-association-values'], 'update-association-values'),
|
|
301
|
+
forceUpdate: flags['force-update'],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
export function buildDestroyArgs(flags) {
|
|
305
|
+
return {
|
|
306
|
+
...pickSharedArgs(flags),
|
|
307
|
+
filterByTk: parseFlexibleValue(flags['filter-by-tk'], 'filter-by-tk'),
|
|
308
|
+
filter: parseObjectFlag(flags.filter, 'filter'),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
export function buildQueryArgs(flags) {
|
|
312
|
+
return {
|
|
313
|
+
...pickSharedArgs(flags),
|
|
314
|
+
measures: parseJsonArrayFlag(flags.measures, 'measures'),
|
|
315
|
+
dimensions: parseJsonArrayFlag(flags.dimensions, 'dimensions'),
|
|
316
|
+
orders: parseJsonArrayFlag(flags.orders, 'orders'),
|
|
317
|
+
filter: parseObjectFlag(flags.filter, 'filter'),
|
|
318
|
+
having: parseObjectFlag(flags.having, 'having'),
|
|
319
|
+
limit: flags.limit,
|
|
320
|
+
offset: flags.offset,
|
|
321
|
+
timezone: flags.timezone,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
export async function runResourceCommand(command, action, flags, args) {
|
|
325
|
+
setVerboseMode(Boolean(flags.verbose));
|
|
326
|
+
const response = await executeResourceRequest({
|
|
327
|
+
envName: flags.env,
|
|
328
|
+
baseUrl: flags['base-url'],
|
|
329
|
+
role: flags.role,
|
|
330
|
+
token: flags.token,
|
|
331
|
+
action,
|
|
332
|
+
args,
|
|
333
|
+
});
|
|
334
|
+
printResponse(command, response, flags['json-output']);
|
|
335
|
+
}
|