@nocobase/cli 3.0.0-alpha.2 → 3.0.0-alpha.4
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/dist/commands/api/swagger/get.js +96 -0
- package/dist/commands/api/swagger/index.js +20 -0
- package/dist/commands/api/swagger/list.js +92 -0
- package/dist/commands/init.js +16 -0
- package/dist/lib/bootstrap.js +3 -1
- package/dist/lib/browser.js +29 -0
- package/dist/lib/env-proxy.js +24 -3
- package/dist/lib/generated-command.js +81 -0
- package/dist/lib/managed-env-file.js +10 -12
- package/dist/lib/portal-create.js +2 -2
- package/dist/lib/portal-deploy.js +4 -4
- package/dist/lib/portal-dev.js +2 -2
- package/dist/lib/portal-source.js +2 -2
- package/dist/lib/prompt-web-ui.js +4 -5
- package/dist/lib/run-npm.js +22 -4
- package/dist/lib/runtime-generator.js +28 -1
- package/dist/lib/swagger-command.js +52 -0
- package/dist/locale/en-US.json +26 -0
- package/dist/locale/zh-CN.json +26 -0
- package/package.json +2 -2
|
@@ -0,0 +1,96 @@
|
|
|
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 { promises as fs } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { Command, Flags } from '@oclif/core';
|
|
12
|
+
import { translateCli } from '../../../lib/cli-locale.js';
|
|
13
|
+
import { executeSwaggerRequest, swaggerRequestFlags } from '../../../lib/swagger-command.js';
|
|
14
|
+
import { renderTable } from '../../../lib/ui.js';
|
|
15
|
+
const swaggerText = (key, values, fallback) => translateCli(`commands.swagger.${key}`, values, { fallback });
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
18
|
+
}
|
|
19
|
+
function unwrapData(value) {
|
|
20
|
+
if (!isRecord(value) || typeof value.openapi === 'string') {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return Object.prototype.hasOwnProperty.call(value, 'data') ? value.data : value;
|
|
24
|
+
}
|
|
25
|
+
function normalizeDocument(value) {
|
|
26
|
+
const document = unwrapData(value);
|
|
27
|
+
if (!isRecord(document) ||
|
|
28
|
+
typeof document.openapi !== 'string' ||
|
|
29
|
+
!isRecord(document.info) ||
|
|
30
|
+
!isRecord(document.paths)) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return document;
|
|
34
|
+
}
|
|
35
|
+
export default class SwaggerGet extends Command {
|
|
36
|
+
static summary = 'Get a NocoBase OpenAPI document';
|
|
37
|
+
static examples = [
|
|
38
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
39
|
+
'<%= config.bin %> <%= command.id %> --namespace collections/orders --json',
|
|
40
|
+
'<%= config.bin %> <%= command.id %> --namespace plugins/ai --output ./openapi/ai.json',
|
|
41
|
+
];
|
|
42
|
+
static flags = {
|
|
43
|
+
...swaggerRequestFlags,
|
|
44
|
+
namespace: Flags.string({
|
|
45
|
+
aliases: ['ns'],
|
|
46
|
+
description: 'Document namespace, such as core, plugins/ai, or collections/orders',
|
|
47
|
+
}),
|
|
48
|
+
output: Flags.string({
|
|
49
|
+
char: 'o',
|
|
50
|
+
description: 'Write the OpenAPI document to a file',
|
|
51
|
+
}),
|
|
52
|
+
'json-output': Flags.boolean({
|
|
53
|
+
char: 'j',
|
|
54
|
+
aliases: ['json'],
|
|
55
|
+
description: 'Print the complete OpenAPI document as JSON',
|
|
56
|
+
default: false,
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
async run() {
|
|
60
|
+
const { flags } = await this.parse(SwaggerGet);
|
|
61
|
+
const namespace = flags.namespace?.trim() || undefined;
|
|
62
|
+
const response = await executeSwaggerRequest(this, flags, '/swagger:get', { ns: namespace });
|
|
63
|
+
if (!response) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const details = JSON.stringify(response.data, null, 2);
|
|
68
|
+
this.error(response.status === 404
|
|
69
|
+
? swaggerText('errors.pluginDisabled', undefined, 'The API documentation plugin is not enabled. Enable it before requesting Swagger documents.')
|
|
70
|
+
: swaggerText('errors.requestFailed', { status: response.status, details }, `Swagger request failed with status ${response.status}\n${details}`));
|
|
71
|
+
}
|
|
72
|
+
const document = normalizeDocument(response.data);
|
|
73
|
+
if (!document) {
|
|
74
|
+
this.error(swaggerText('errors.invalidDocument', undefined, 'swagger:get returned an invalid OpenAPI document.'));
|
|
75
|
+
}
|
|
76
|
+
const json = `${JSON.stringify(document, null, 2)}\n`;
|
|
77
|
+
if (flags.output) {
|
|
78
|
+
const outputPath = path.resolve(flags.output);
|
|
79
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
80
|
+
await fs.writeFile(outputPath, json);
|
|
81
|
+
this.log(swaggerText('messages.saved', { output: outputPath }, `Saved Swagger document to ${outputPath}.`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (flags['json-output']) {
|
|
85
|
+
this.log(json.trimEnd());
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.log(renderTable([swaggerText('table.field', undefined, 'Field'), swaggerText('table.value', undefined, 'Value')], [
|
|
89
|
+
[swaggerText('fields.namespace', undefined, 'Namespace'), namespace ?? 'all'],
|
|
90
|
+
[swaggerText('fields.title', undefined, 'Title'), document.info.title ?? ''],
|
|
91
|
+
[swaggerText('fields.version', undefined, 'Version'), document.info.version ?? ''],
|
|
92
|
+
[swaggerText('fields.openapi', undefined, 'OpenAPI'), document.openapi],
|
|
93
|
+
[swaggerText('fields.paths', undefined, 'Paths'), String(Object.keys(document.paths).length)],
|
|
94
|
+
]));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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 { Command, loadHelpClass } from '@oclif/core';
|
|
10
|
+
export default class Swagger extends Command {
|
|
11
|
+
static summary = 'Inspect the current NocoBase OpenAPI documentation';
|
|
12
|
+
async run() {
|
|
13
|
+
await this.parse(Swagger);
|
|
14
|
+
const Help = await loadHelpClass(this.config);
|
|
15
|
+
await new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions).showHelp([
|
|
16
|
+
this.id ?? 'api:swagger',
|
|
17
|
+
...this.argv,
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
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 { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { translateCli } from '../../../lib/cli-locale.js';
|
|
11
|
+
import { executeSwaggerRequest, swaggerRequestFlags } from '../../../lib/swagger-command.js';
|
|
12
|
+
import { renderTable } from '../../../lib/ui.js';
|
|
13
|
+
const swaggerText = (key, values, fallback) => translateCli(`commands.swagger.${key}`, values, { fallback });
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
16
|
+
}
|
|
17
|
+
function unwrapData(value) {
|
|
18
|
+
return isRecord(value) && Object.prototype.hasOwnProperty.call(value, 'data') ? value.data : value;
|
|
19
|
+
}
|
|
20
|
+
function readNamespace(url) {
|
|
21
|
+
try {
|
|
22
|
+
return new URL(url, 'http://localhost').searchParams.get('ns') || 'all';
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return 'all';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function normalizeDestinations(value) {
|
|
29
|
+
const data = unwrapData(value);
|
|
30
|
+
if (!Array.isArray(data)) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const destinations = [];
|
|
34
|
+
for (const item of data) {
|
|
35
|
+
if (!isRecord(item) || typeof item.name !== 'string' || typeof item.url !== 'string') {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
destinations.push({
|
|
39
|
+
name: item.name,
|
|
40
|
+
namespace: readNamespace(item.url),
|
|
41
|
+
url: item.url,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return destinations;
|
|
45
|
+
}
|
|
46
|
+
export default class SwaggerList extends Command {
|
|
47
|
+
static summary = 'List available NocoBase OpenAPI document namespaces';
|
|
48
|
+
static examples = [
|
|
49
|
+
'<%= config.bin %> <%= command.id %>',
|
|
50
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
51
|
+
'<%= config.bin %> <%= command.id %> --env dev --yes --json',
|
|
52
|
+
];
|
|
53
|
+
static flags = {
|
|
54
|
+
...swaggerRequestFlags,
|
|
55
|
+
'json-output': Flags.boolean({
|
|
56
|
+
char: 'j',
|
|
57
|
+
aliases: ['json'],
|
|
58
|
+
description: 'Print namespaces as JSON',
|
|
59
|
+
default: false,
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
async run() {
|
|
63
|
+
const { flags } = await this.parse(SwaggerList);
|
|
64
|
+
const response = await executeSwaggerRequest(this, flags, '/swagger:getUrls');
|
|
65
|
+
if (!response) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const details = JSON.stringify(response.data, null, 2);
|
|
70
|
+
this.error(response.status === 404
|
|
71
|
+
? swaggerText('errors.pluginDisabled', undefined, 'The API documentation plugin is not enabled. Enable it before requesting Swagger documents.')
|
|
72
|
+
: swaggerText('errors.requestFailed', { status: response.status, details }, `Swagger request failed with status ${response.status}\n${details}`));
|
|
73
|
+
}
|
|
74
|
+
const destinations = normalizeDestinations(response.data);
|
|
75
|
+
if (!destinations) {
|
|
76
|
+
this.error(swaggerText('errors.invalidDestinations', undefined, 'swagger:getUrls returned an invalid destination list.'));
|
|
77
|
+
}
|
|
78
|
+
if (flags['json-output']) {
|
|
79
|
+
this.log(JSON.stringify(destinations, null, 2));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!destinations.length) {
|
|
83
|
+
this.log(swaggerText('messages.empty', undefined, 'No Swagger document namespaces are available.'));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.log(renderTable([
|
|
87
|
+
swaggerText('table.name', undefined, 'Name'),
|
|
88
|
+
swaggerText('table.namespace', undefined, 'Namespace'),
|
|
89
|
+
swaggerText('table.url', undefined, 'URL'),
|
|
90
|
+
], destinations.map((item) => [item.name, item.namespace, item.url])));
|
|
91
|
+
}
|
|
92
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -109,6 +109,15 @@ function resolveInitDownloadVersion(results) {
|
|
|
109
109
|
function initVersionPromptValue(version) {
|
|
110
110
|
return version === 'latest' || version === 'beta' || version === 'alpha' ? version : 'other';
|
|
111
111
|
}
|
|
112
|
+
export function defaultInitDownloadVersionForCliVersion(cliVersion) {
|
|
113
|
+
if (/-alpha(?:[.-]|$)/i.test(cliVersion)) {
|
|
114
|
+
return 'alpha';
|
|
115
|
+
}
|
|
116
|
+
if (/-beta(?:[.-]|$)/i.test(cliVersion)) {
|
|
117
|
+
return 'beta';
|
|
118
|
+
}
|
|
119
|
+
return 'latest';
|
|
120
|
+
}
|
|
112
121
|
function yesInitialValue(def, fallback) {
|
|
113
122
|
if ('yesInitialValue' in def && def.yesInitialValue !== undefined) {
|
|
114
123
|
return String(def.yesInitialValue);
|
|
@@ -409,8 +418,15 @@ Prompt modes:
|
|
|
409
418
|
installAccessToken: installConnectionAccessTokenPrompt,
|
|
410
419
|
};
|
|
411
420
|
buildPromptCatalog(flags, options) {
|
|
421
|
+
const downloadVersion = defaultInitDownloadVersionForCliVersion(String(this.config.pjson?.version ?? '').trim());
|
|
422
|
+
const versionPrompt = Init.prompts.version;
|
|
412
423
|
const prompts = {
|
|
413
424
|
...Init.prompts,
|
|
425
|
+
version: {
|
|
426
|
+
...versionPrompt,
|
|
427
|
+
initialValue: downloadVersion,
|
|
428
|
+
yesInitialValue: downloadVersion,
|
|
429
|
+
},
|
|
414
430
|
installApiBaseUrl: createInstallConnectionApiBaseUrlPrompt(options.defaultApiHost),
|
|
415
431
|
};
|
|
416
432
|
if (flags['skip-auth']) {
|
package/dist/lib/bootstrap.js
CHANGED
|
@@ -72,7 +72,9 @@ function hasVersionFlag(argv) {
|
|
|
72
72
|
function isBuiltinCommand(argv) {
|
|
73
73
|
const commandTokens = argv.filter((token) => token && !token.startsWith('-'));
|
|
74
74
|
const [topic, subtopic] = commandTokens;
|
|
75
|
-
return topic === 'env' ||
|
|
75
|
+
return (topic === 'env' ||
|
|
76
|
+
topic === 'resource' ||
|
|
77
|
+
(topic === 'api' && (subtopic === 'resource' || subtopic === 'swagger')));
|
|
76
78
|
}
|
|
77
79
|
export function shouldSkipRuntimeBootstrap(argv) {
|
|
78
80
|
return hasVersionFlag(argv) || isBuiltinCommand(argv);
|
|
@@ -0,0 +1,29 @@
|
|
|
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 { spawn } from 'node:child_process';
|
|
10
|
+
export async function openUrlInDefaultBrowser(url) {
|
|
11
|
+
const [command, args, options] = process.platform === 'darwin'
|
|
12
|
+
? ['open', [url], { detached: true, stdio: 'ignore' }]
|
|
13
|
+
: process.platform === 'win32'
|
|
14
|
+
? ['cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore', windowsHide: true }]
|
|
15
|
+
: ['xdg-open', [url], { detached: true, stdio: 'ignore' }];
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
try {
|
|
18
|
+
const child = spawn(command, args, options);
|
|
19
|
+
child.once('error', () => resolve(false));
|
|
20
|
+
child.once('spawn', () => {
|
|
21
|
+
child.unref();
|
|
22
|
+
resolve(true);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
resolve(false);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
package/dist/lib/env-proxy.js
CHANGED
|
@@ -605,17 +605,30 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
605
605
|
` ${MANAGED_NGINX_CONFIG_BLOCK_END}`,
|
|
606
606
|
].join('\n');
|
|
607
607
|
}
|
|
608
|
-
function
|
|
608
|
+
function buildPortalRootPublicPath(appPublicPath) {
|
|
609
609
|
return appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
610
610
|
? `/${PORTAL_CLIENT_PREFIX}/`
|
|
611
611
|
: `${trimTrailingSlash(appPublicPath)}/${PORTAL_CLIENT_PREFIX}/`;
|
|
612
612
|
}
|
|
613
613
|
function buildNginxPortalLocationBlock(context) {
|
|
614
|
-
const portalBasePath = trimTrailingSlash(
|
|
614
|
+
const portalBasePath = trimTrailingSlash(buildPortalRootPublicPath(context.appPublicPath));
|
|
615
615
|
const portalBasePathPattern = escapeRegExp(portalBasePath);
|
|
616
616
|
return [
|
|
617
|
+
` location = ${portalBasePath} {`,
|
|
618
|
+
' absolute_redirect off;',
|
|
619
|
+
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
620
|
+
' }',
|
|
621
|
+
'',
|
|
622
|
+
` location = ${portalBasePath}/ {`,
|
|
623
|
+
' absolute_redirect off;',
|
|
624
|
+
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
625
|
+
' }',
|
|
626
|
+
'',
|
|
617
627
|
` location ^~ ${portalBasePath}/apps/ {`,
|
|
618
628
|
' absolute_redirect off;',
|
|
629
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/?$) {`,
|
|
630
|
+
` return 302 ${context.v2PublicPath}apps/$subapp/$is_args$args;`,
|
|
631
|
+
' }',
|
|
619
632
|
'',
|
|
620
633
|
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
621
634
|
` return 308 ${portalBasePath}/apps/$subapp/$portal/$is_args$args;`,
|
|
@@ -640,7 +653,6 @@ function buildNginxPortalLocationBlock(context) {
|
|
|
640
653
|
'',
|
|
641
654
|
` location ^~ ${portalBasePath}/ {`,
|
|
642
655
|
' absolute_redirect off;',
|
|
643
|
-
'',
|
|
644
656
|
` if ($uri ~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
645
657
|
` return 308 ${portalBasePath}/$portal/$is_args$args;`,
|
|
646
658
|
' }',
|
|
@@ -1229,6 +1241,7 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1229
1241
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
1230
1242
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
1231
1243
|
const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
|
|
1244
|
+
const portalBasePath = trimTrailingSlash(buildPortalRootPublicPath(context.appPublicPath));
|
|
1232
1245
|
const rootRedirectBlock = context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
1233
1246
|
? ''
|
|
1234
1247
|
: `
|
|
@@ -1321,6 +1334,14 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1321
1334
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1322
1335
|
' }',
|
|
1323
1336
|
'',
|
|
1337
|
+
` handle ${portalBasePath} {`,
|
|
1338
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1339
|
+
' }',
|
|
1340
|
+
'',
|
|
1341
|
+
` handle ${portalBasePath}/* {`,
|
|
1342
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1343
|
+
' }',
|
|
1344
|
+
'',
|
|
1324
1345
|
` @settingsRoute path_regexp settingsRoute ${settingsRoutePattern}`,
|
|
1325
1346
|
' handle @settingsRoute {',
|
|
1326
1347
|
` root * ${publicDir}`,
|
|
@@ -15,12 +15,20 @@
|
|
|
15
15
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
16
16
|
*/
|
|
17
17
|
import { Command, Flags } from '@oclif/core';
|
|
18
|
+
import { getCurrentEnvName, getEnv } from './auth-store.js';
|
|
18
19
|
import { executeApiRequest } from './api-client.js';
|
|
20
|
+
import { resolveAppUrlFromApiBaseUrl } from '../commands/env/shared.js';
|
|
19
21
|
import { findApiCommandCompatViolation, formatApiCommandCompatViolation } from './api-command-compat.js';
|
|
22
|
+
import { openUrlInDefaultBrowser } from './browser.js';
|
|
20
23
|
import { ensureCrossEnvConfirmed } from './env-guard.js';
|
|
21
24
|
import { applyPostProcessor } from './post-processors.js';
|
|
22
25
|
import { readInstalledManagedSkillsVersion } from './skills-manager.js';
|
|
23
26
|
import { registerPostProcessors } from '../post-processors/index.js';
|
|
27
|
+
const UI_OPERATION_QUERY_KEY = '_operation_';
|
|
28
|
+
const UI_OPERATION_VERSION = 1;
|
|
29
|
+
function encodeUIOperation(operation) {
|
|
30
|
+
return Buffer.from(JSON.stringify(operation), 'utf8').toString('base64url');
|
|
31
|
+
}
|
|
24
32
|
function buildParameterFlag(parameter, options) {
|
|
25
33
|
const hints = [parameter.in];
|
|
26
34
|
if (parameter.isFile) {
|
|
@@ -104,6 +112,13 @@ export function createGeneratedFlags(operation) {
|
|
|
104
112
|
required: true,
|
|
105
113
|
});
|
|
106
114
|
}
|
|
115
|
+
if (operation.ui) {
|
|
116
|
+
flags.ui = Flags.boolean({
|
|
117
|
+
description: 'Open the corresponding page in the NocoBase UI',
|
|
118
|
+
default: false,
|
|
119
|
+
helpGroup: 'Global',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
107
122
|
flags['api-base-url'] = Flags.string({
|
|
108
123
|
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
109
124
|
helpGroup: 'Global',
|
|
@@ -142,6 +157,68 @@ export function createGeneratedFlags(operation) {
|
|
|
142
157
|
});
|
|
143
158
|
return flags;
|
|
144
159
|
}
|
|
160
|
+
function hasFlagValue(value) {
|
|
161
|
+
if (Array.isArray(value)) {
|
|
162
|
+
return value.length > 0;
|
|
163
|
+
}
|
|
164
|
+
return value !== undefined && value !== '';
|
|
165
|
+
}
|
|
166
|
+
function listProvidedBodyFlags(flags, operation) {
|
|
167
|
+
const rawBodyFlags = ['body', 'body-file'].filter((flagName) => hasFlagValue(flags[flagName])).map((flagName) => `--${flagName}`);
|
|
168
|
+
const uiParameterNames = new Set(operation.ui?.parameters ?? []);
|
|
169
|
+
const bodyFieldFlags = operation.parameters
|
|
170
|
+
.filter((parameter) => parameter.in === 'body' && !uiParameterNames.has(parameter.name) && hasFlagValue(flags[parameter.flagName]))
|
|
171
|
+
.map((parameter) => `--${parameter.flagName}`);
|
|
172
|
+
return [...rawBodyFlags, ...bodyFieldFlags];
|
|
173
|
+
}
|
|
174
|
+
async function resolveUiAppUrl(flags) {
|
|
175
|
+
const apiBaseUrl = typeof flags['api-base-url'] === 'string' ? flags['api-base-url'] : undefined;
|
|
176
|
+
if (apiBaseUrl) {
|
|
177
|
+
return resolveAppUrlFromApiBaseUrl(apiBaseUrl);
|
|
178
|
+
}
|
|
179
|
+
const requestedEnv = typeof flags.env === 'string' ? flags.env : undefined;
|
|
180
|
+
const envName = requestedEnv ?? (await getCurrentEnvName());
|
|
181
|
+
const env = await getEnv(envName);
|
|
182
|
+
if (!env?.baseUrl) {
|
|
183
|
+
throw new Error(env
|
|
184
|
+
? `Env "${envName}" is missing a base URL. Use --api-base-url or update env "${envName}" with \`nb env update ${envName} --api-base-url <url>\` first.`
|
|
185
|
+
: `Env "${envName}" is not configured. Use --api-base-url or run \`nb init --ui --env ${envName}\` first.`);
|
|
186
|
+
}
|
|
187
|
+
return resolveAppUrlFromApiBaseUrl(env.baseUrl);
|
|
188
|
+
}
|
|
189
|
+
function buildUiOperationUrl(appUrl, path, encodedOperation) {
|
|
190
|
+
const url = new URL(appUrl);
|
|
191
|
+
url.pathname = `${url.pathname.replace(/\/+$/, '')}/${path}`;
|
|
192
|
+
url.searchParams.set(UI_OPERATION_QUERY_KEY, encodedOperation);
|
|
193
|
+
return url.toString();
|
|
194
|
+
}
|
|
195
|
+
async function openUiOperation(command, operation, flags) {
|
|
196
|
+
const { operationId, ui } = operation;
|
|
197
|
+
if (!ui || !operationId) {
|
|
198
|
+
command.error('This API operation does not support --ui.');
|
|
199
|
+
}
|
|
200
|
+
const bodyFlags = listProvidedBodyFlags(flags, operation);
|
|
201
|
+
if (bodyFlags.length) {
|
|
202
|
+
command.error('--ui cannot be combined with API request body flags. Remove --ui to submit through the API, or remove the body flags to open the UI.');
|
|
203
|
+
}
|
|
204
|
+
const uiParameterNames = new Set(ui.parameters ?? []);
|
|
205
|
+
const params = Object.fromEntries(operation.parameters
|
|
206
|
+
.filter((parameter) => uiParameterNames.has(parameter.name) && hasFlagValue(flags[parameter.flagName]))
|
|
207
|
+
.map((parameter) => [parameter.name, flags[parameter.flagName]]));
|
|
208
|
+
const uiOperation = {
|
|
209
|
+
v: UI_OPERATION_VERSION,
|
|
210
|
+
operationId,
|
|
211
|
+
...(Object.keys(params).length ? { params } : {}),
|
|
212
|
+
};
|
|
213
|
+
const encodedOperation = encodeUIOperation(uiOperation);
|
|
214
|
+
const appUrl = await resolveUiAppUrl(flags);
|
|
215
|
+
const targetUrl = buildUiOperationUrl(appUrl, ui.path, encodedOperation);
|
|
216
|
+
const opened = await openUrlInDefaultBrowser(targetUrl);
|
|
217
|
+
command.log(targetUrl);
|
|
218
|
+
if (!opened) {
|
|
219
|
+
command.warn('Could not open the default browser. Copy the URL above to continue.');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
145
222
|
export class GeneratedApiCommand extends Command {
|
|
146
223
|
static operation;
|
|
147
224
|
static runtimeVersion;
|
|
@@ -169,6 +246,10 @@ export class GeneratedApiCommand extends Command {
|
|
|
169
246
|
if (compatViolation) {
|
|
170
247
|
this.error(formatApiCommandCompatViolation(compatViolation));
|
|
171
248
|
}
|
|
249
|
+
if (flags.ui) {
|
|
250
|
+
await openUiOperation(this, ctor.operation, flags);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
172
253
|
const response = await executeApiRequest({
|
|
173
254
|
cliVersion,
|
|
174
255
|
skillsVersion,
|
|
@@ -21,9 +21,6 @@ function trimValue(value) {
|
|
|
21
21
|
const text = String(value ?? '').trim();
|
|
22
22
|
return text || undefined;
|
|
23
23
|
}
|
|
24
|
-
function normalizeEnvFilePath(value) {
|
|
25
|
-
return value.replace(/\\/g, '/');
|
|
26
|
-
}
|
|
27
24
|
function stripWrappingQuotes(value) {
|
|
28
25
|
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
29
26
|
return value
|
|
@@ -63,38 +60,39 @@ export function resolveManagedLocalEnvFilePath(runtime) {
|
|
|
63
60
|
const config = runtime.env.config ?? {};
|
|
64
61
|
const explicitEnvFile = trimValue(config.envFile);
|
|
65
62
|
if (explicitEnvFile) {
|
|
66
|
-
return
|
|
63
|
+
return resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile;
|
|
67
64
|
}
|
|
68
65
|
const configuredAppPath = resolveConfiguredAppPath(config);
|
|
69
66
|
if (configuredAppPath) {
|
|
70
|
-
return
|
|
67
|
+
return path.join(configuredAppPath, '.env');
|
|
71
68
|
}
|
|
72
69
|
if (path.basename(runtime.projectRoot) === 'source') {
|
|
73
|
-
return
|
|
70
|
+
return path.resolve(runtime.projectRoot, '..', '.env');
|
|
74
71
|
}
|
|
75
|
-
return
|
|
72
|
+
return path.join(runtime.projectRoot, '.env');
|
|
76
73
|
}
|
|
77
74
|
export function resolveManagedEnvFilePathFromConfig(envName, config) {
|
|
78
75
|
const kind = config?.kind ?? resolveEnvKind(config);
|
|
79
76
|
if (kind === 'docker') {
|
|
80
|
-
|
|
81
|
-
return filePath ? normalizeEnvFilePath(filePath) : undefined;
|
|
77
|
+
return resolveDockerEnvFilePath(envName, config);
|
|
82
78
|
}
|
|
83
79
|
if (kind !== 'local') {
|
|
84
80
|
return undefined;
|
|
85
81
|
}
|
|
86
82
|
const explicitEnvFile = trimValue(config?.envFile);
|
|
87
83
|
if (explicitEnvFile) {
|
|
88
|
-
return
|
|
84
|
+
return resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile;
|
|
89
85
|
}
|
|
90
86
|
const configuredAppPath = resolveConfiguredAppPath(config);
|
|
91
87
|
if (configuredAppPath) {
|
|
92
|
-
return
|
|
88
|
+
return path.join(configuredAppPath, '.env');
|
|
93
89
|
}
|
|
94
90
|
const configuredAppRootPath = trimValue(config?.appRootPath);
|
|
95
91
|
if (configuredAppRootPath) {
|
|
96
92
|
const appRootPath = resolveConfiguredEnvPath(configuredAppRootPath) ?? configuredAppRootPath;
|
|
97
|
-
return
|
|
93
|
+
return path.basename(appRootPath) === 'source'
|
|
94
|
+
? path.resolve(appRootPath, '..', '.env')
|
|
95
|
+
: path.join(appRootPath, '.env');
|
|
98
96
|
}
|
|
99
97
|
return undefined;
|
|
100
98
|
}
|
|
@@ -21,7 +21,7 @@ import { resolveEnvRelativePath } from './cli-home.js';
|
|
|
21
21
|
import { translateCli } from './cli-locale.js';
|
|
22
22
|
import { buildPortalCommandEnv } from './portal-command-env.js';
|
|
23
23
|
import { buildPortalConfig, mergePortalConfigIntoOptions, writePortalConfig, } from './portal-config.js';
|
|
24
|
-
import { run } from './run-npm.js';
|
|
24
|
+
import { run, runPnpmCommand } from './run-npm.js';
|
|
25
25
|
const DEFAULT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
26
26
|
const DEFAULT_PORTAL_APP_NAME = 'main';
|
|
27
27
|
const TEMPLATE_COPY_EXCLUDED_NAMES = new Set(['.git', 'node_modules', '.DS_Store']);
|
|
@@ -392,7 +392,7 @@ export async function createPortalWorkspace(options) {
|
|
|
392
392
|
const hasPackageJson = await pathExists(path.join(portalDir, 'package.json'));
|
|
393
393
|
if (hasPackageJson) {
|
|
394
394
|
const runCommand = options.runCommand ?? run;
|
|
395
|
-
await runCommand
|
|
395
|
+
await runPnpmCommand(runCommand, ['install'], {
|
|
396
396
|
cwd: portalDir,
|
|
397
397
|
env: buildPortalCommandEnv(),
|
|
398
398
|
envMode: 'replace',
|
|
@@ -16,7 +16,7 @@ import { buildPortalBasePath, resolvePortalAppFromApiBaseUrl, resolvePortalStora
|
|
|
16
16
|
import { buildPortalCommandEnv } from './portal-command-env.js';
|
|
17
17
|
import { updatePortalEnvFiles } from './portal-env-files.js';
|
|
18
18
|
import { mergePortalConfigIntoOptions, readPortalConfig } from './portal-config.js';
|
|
19
|
-
import { run } from './run-npm.js';
|
|
19
|
+
import { run, runPnpmCommand } from './run-npm.js';
|
|
20
20
|
const portalDeployText = (key, values, fallback) => translateCli(`commands.portalDeploy.${key}`, values, { fallback });
|
|
21
21
|
const DEPLOY_OPERATION = {
|
|
22
22
|
method: 'POST',
|
|
@@ -208,13 +208,13 @@ export async function deployPortalWorkspace(options) {
|
|
|
208
208
|
portalBase,
|
|
209
209
|
});
|
|
210
210
|
const runCommand = options.runCommand ?? run;
|
|
211
|
-
await runCommand
|
|
211
|
+
await runPnpmCommand(runCommand, ['install'], {
|
|
212
212
|
cwd: portalDir,
|
|
213
213
|
env: buildPortalCommandEnv(),
|
|
214
214
|
envMode: 'replace',
|
|
215
215
|
errorName: 'pnpm install',
|
|
216
216
|
});
|
|
217
|
-
await runCommand
|
|
217
|
+
await runPnpmCommand(runCommand, ['build'], {
|
|
218
218
|
cwd: portalDir,
|
|
219
219
|
env: buildPortalCommandEnv({
|
|
220
220
|
NOCOBASE_API_URL: apiBaseUrl,
|
|
@@ -223,7 +223,7 @@ export async function deployPortalWorkspace(options) {
|
|
|
223
223
|
envMode: 'replace',
|
|
224
224
|
errorName: 'pnpm build',
|
|
225
225
|
});
|
|
226
|
-
await runCommand
|
|
226
|
+
await runPnpmCommand(runCommand, ['build:html'], {
|
|
227
227
|
cwd: portalDir,
|
|
228
228
|
env: buildPortalCommandEnv({
|
|
229
229
|
NOCOBASE_API_URL: apiBaseUrl,
|
package/dist/lib/portal-dev.js
CHANGED
|
@@ -12,7 +12,7 @@ import { translateCli } from './cli-locale.js';
|
|
|
12
12
|
import { buildPortalBasePath, resolvePortalAppFromApiBaseUrl, resolvePortalStoragePath, validatePortalSlug, } from './portal-create.js';
|
|
13
13
|
import { buildPortalCommandEnv } from './portal-command-env.js';
|
|
14
14
|
import { updatePortalEnvFiles } from './portal-env-files.js';
|
|
15
|
-
import { run } from './run-npm.js';
|
|
15
|
+
import { run, runPnpmCommand } from './run-npm.js';
|
|
16
16
|
const portalDevText = (key, values, fallback) => translateCli(`commands.portalDev.${key}`, values, { fallback });
|
|
17
17
|
function trimValue(value) {
|
|
18
18
|
return String(value ?? '').trim();
|
|
@@ -66,7 +66,7 @@ export async function devPortalWorkspace(options) {
|
|
|
66
66
|
};
|
|
67
67
|
options.onStart?.(result);
|
|
68
68
|
const runCommand = options.runCommand ?? run;
|
|
69
|
-
await runCommand
|
|
69
|
+
await runPnpmCommand(runCommand, ['dev'], {
|
|
70
70
|
cwd: portalDir,
|
|
71
71
|
env: buildPortalCommandEnv({
|
|
72
72
|
NOCOBASE_API_URL: apiBaseUrl,
|
|
@@ -19,7 +19,7 @@ import { buildPortalCommandEnv } from './portal-command-env.js';
|
|
|
19
19
|
import { buildPortalBasePath, resolvePortalAppFromApiBaseUrl, resolvePortalStoragePath, validatePortalSlug, } from './portal-create.js';
|
|
20
20
|
import { listPortalWorkspaces } from './portal-list.js';
|
|
21
21
|
import { findPortalListItem } from './portal-info.js';
|
|
22
|
-
import { run } from './run-npm.js';
|
|
22
|
+
import { run, runPnpmCommand } from './run-npm.js';
|
|
23
23
|
const execFileAsync = promisify(execFile);
|
|
24
24
|
const portalSourceText = (key, values, fallback) => translateCli(`commands.portalSource.${key}`, values, { fallback });
|
|
25
25
|
const PULL_SOURCE_OPERATION = {
|
|
@@ -194,7 +194,7 @@ async function installPortalDependencies(params) {
|
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
const runCommand = params.runCommand ?? run;
|
|
197
|
-
await runCommand
|
|
197
|
+
await runPnpmCommand(runCommand, ['install'], {
|
|
198
198
|
cwd: params.portalDir,
|
|
199
199
|
env: buildPortalCommandEnv(),
|
|
200
200
|
envMode: 'replace',
|
|
@@ -765,11 +765,10 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
765
765
|
}
|
|
766
766
|
};
|
|
767
767
|
const servePage = (port) => {
|
|
768
|
-
const base = `http://${publicHost}:${port}`;
|
|
769
768
|
const formInner = buildPwcFormHtml(catalog, formDefaults, initialShow, pwcStepDefs, 0, pwcNSteps, locale, uiText);
|
|
770
769
|
const wizardClientJson = JSON.stringify({ n: pwcNSteps, stepDefs: pwcStepDefs });
|
|
771
|
-
const pwcValStepUrl = pwcNSteps > 1 ? JSON.stringify(
|
|
772
|
-
const pwcValFieldUrl = JSON.stringify(
|
|
770
|
+
const pwcValStepUrl = pwcNSteps > 1 ? JSON.stringify(resolveValidateStepPath) : 'null';
|
|
771
|
+
const pwcValFieldUrl = JSON.stringify(resolveValidateFieldPath);
|
|
773
772
|
const uiTextJson = JSON.stringify(uiText);
|
|
774
773
|
const pwcShellClass = options.stages && options.stages.length > 0
|
|
775
774
|
? 'pwc-shell pwc-shell--stages'
|
|
@@ -1493,8 +1492,8 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
1493
1492
|
</div>
|
|
1494
1493
|
<script>
|
|
1495
1494
|
(function () {
|
|
1496
|
-
var sub = ${JSON.stringify(
|
|
1497
|
-
var ref = ${JSON.stringify(
|
|
1495
|
+
var sub = ${JSON.stringify(submitPath)};
|
|
1496
|
+
var ref = ${JSON.stringify(reflowPath)};
|
|
1498
1497
|
var pwcValStep = ${pwcValStepUrl};
|
|
1499
1498
|
var pwcValField = ${pwcValFieldUrl};
|
|
1500
1499
|
var pwcStepMeta = ${JSON.stringify(PWC_FORM_META_STEP)};
|
package/dist/lib/run-npm.js
CHANGED
|
@@ -75,18 +75,36 @@ function buildProcessEnv(options) {
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
function createMissingCommandError(name, label, error) {
|
|
78
|
-
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : undefined;
|
|
79
|
-
if (code !== 'ENOENT') {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
78
|
if (!Object.prototype.hasOwnProperty.call(MISSING_COMMAND_SPECS, name)) {
|
|
83
79
|
return undefined;
|
|
84
80
|
}
|
|
85
81
|
const spec = MISSING_COMMAND_SPECS[name];
|
|
82
|
+
if (!isMissingCommandError(name, spec.displayName, error)) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
86
85
|
return new Error(translateCli('commands.shared.missingCommand', { action: label, displayName: spec.displayName, configKey: spec.configKey }, {
|
|
87
86
|
fallback: `Couldn't run \`${label}\` because the ${spec.displayName} executable could not be found. Install ${spec.displayName} or update \`nb config set ${spec.configKey} <path>\` and try again.`,
|
|
88
87
|
}));
|
|
89
88
|
}
|
|
89
|
+
function isMissingCommandError(name, displayName, error) {
|
|
90
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : undefined;
|
|
91
|
+
if (code === 'ENOENT') {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
const lowerMessage = message.toLowerCase();
|
|
96
|
+
return (lowerMessage.includes(`spawn ${name.toLowerCase()} enoent`) ||
|
|
97
|
+
lowerMessage.includes(`${name.toLowerCase()} executable could not be found`) ||
|
|
98
|
+
lowerMessage.includes(`${displayName.toLowerCase()} executable could not be found`));
|
|
99
|
+
}
|
|
100
|
+
export async function runPnpmCommand(runCommand, args, options) {
|
|
101
|
+
try {
|
|
102
|
+
await runCommand('pnpm', args, options);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
throw createMissingCommandError('pnpm', options.errorName ?? `pnpm ${args.join(' ')}`.trim(), error) ?? error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
90
108
|
function isDockerDaemonUnavailableError(error) {
|
|
91
109
|
const message = error instanceof Error ? error.message : String(error);
|
|
92
110
|
return DOCKER_DAEMON_UNAVAILABLE_PATTERNS.some((pattern) => pattern.test(message));
|
|
@@ -18,7 +18,31 @@ import { createHash } from 'node:crypto';
|
|
|
18
18
|
import { loadBuildConfig } from './build-config.js';
|
|
19
19
|
import { toKebabCase, toLogicalActionName, toLogicalResourceName, toResourceSegments } from './naming.js';
|
|
20
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']);
|
|
21
|
+
const RESERVED_FLAG_NAMES = new Set(['api-base-url', 'base-url', 'env', 'token', 'json-output', 'body', 'body-file', 'ui', 'yes']);
|
|
22
|
+
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
23
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === 'string' && Boolean(item));
|
|
24
|
+
function getGeneratedUIOperation(operation, parameters) {
|
|
25
|
+
if (!operation.operationId) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const extension = operation['x-nocobase-cli-ui'];
|
|
29
|
+
if (!isRecord(extension) || typeof extension.path !== 'string') {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const path = extension.path.trim();
|
|
33
|
+
if (!path || path.startsWith('/') || /[?#]/.test(path) || /^[a-z][a-z\d+.-]*:/i.test(path)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const mappedParameters = extension.parameters === undefined ? [] : extension.parameters;
|
|
37
|
+
if (!isStringArray(mappedParameters) || new Set(mappedParameters).size !== mappedParameters.length) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const allowedParameters = new Set(parameters.map((parameter) => parameter.name));
|
|
41
|
+
if (!mappedParameters.every((parameter) => allowedParameters.has(parameter))) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return { path, parameters: mappedParameters };
|
|
45
|
+
}
|
|
22
46
|
function matchesPattern(value, pattern) {
|
|
23
47
|
if (!value) {
|
|
24
48
|
return false;
|
|
@@ -425,6 +449,7 @@ export async function generateRuntime(document, configFile, baseUrl) {
|
|
|
425
449
|
const parameters = (operation.parameters ?? []).filter(isSupportedParameter).map((parameter) => toGeneratedParameter(parameter, usedFlagNames));
|
|
426
450
|
const bodyParameters = extractBodyParameters(operation.requestBody, usedFlagNames);
|
|
427
451
|
const allParameters = [...parameters, ...bodyParameters];
|
|
452
|
+
const ui = getGeneratedUIOperation(operation, allParameters);
|
|
428
453
|
const hasBody = Boolean(operation.requestBody && !('$ref' in operation.requestBody));
|
|
429
454
|
const requestContentType = getRequestContentType(operation.requestBody);
|
|
430
455
|
const responseType = getResponseType(operation);
|
|
@@ -456,6 +481,8 @@ export async function generateRuntime(document, configFile, baseUrl) {
|
|
|
456
481
|
resourceDisplayName,
|
|
457
482
|
resourceDescription,
|
|
458
483
|
commandId: segments.join(' '),
|
|
484
|
+
operationId: operation.operationId,
|
|
485
|
+
ui,
|
|
459
486
|
method,
|
|
460
487
|
pathTemplate,
|
|
461
488
|
tags: operation.tags,
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { Flags } from '@oclif/core';
|
|
10
|
+
import { executeRawApiRequest } from './api-client.js';
|
|
11
|
+
import { ensureCrossEnvConfirmed } from './env-guard.js';
|
|
12
|
+
export const swaggerRequestFlags = {
|
|
13
|
+
env: Flags.string({
|
|
14
|
+
char: 'e',
|
|
15
|
+
description: 'CLI env name; omitted uses the current env',
|
|
16
|
+
}),
|
|
17
|
+
yes: Flags.boolean({
|
|
18
|
+
char: 'y',
|
|
19
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
'api-base-url': Flags.string({
|
|
23
|
+
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
24
|
+
}),
|
|
25
|
+
role: Flags.string({
|
|
26
|
+
description: 'Role override, sent as X-Role',
|
|
27
|
+
}),
|
|
28
|
+
token: Flags.string({
|
|
29
|
+
char: 't',
|
|
30
|
+
description: 'API key or access token override',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
export async function executeSwaggerRequest(command, flags, path, query) {
|
|
34
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
35
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
36
|
+
command,
|
|
37
|
+
requestedEnv,
|
|
38
|
+
yes: flags.yes,
|
|
39
|
+
});
|
|
40
|
+
if (!confirmed) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return executeRawApiRequest({
|
|
44
|
+
envName: requestedEnv,
|
|
45
|
+
baseUrl: flags['api-base-url'],
|
|
46
|
+
token: flags.token,
|
|
47
|
+
role: flags.role,
|
|
48
|
+
method: 'GET',
|
|
49
|
+
path,
|
|
50
|
+
query,
|
|
51
|
+
});
|
|
52
|
+
}
|
package/dist/locale/en-US.json
CHANGED
|
@@ -259,6 +259,32 @@
|
|
|
259
259
|
"localSynced": "Local synced"
|
|
260
260
|
}
|
|
261
261
|
},
|
|
262
|
+
"swagger": {
|
|
263
|
+
"errors": {
|
|
264
|
+
"pluginDisabled": "The API documentation plugin is not enabled. Enable it before requesting Swagger documents.",
|
|
265
|
+
"requestFailed": "Swagger request failed with status {{status}}\n{{details}}",
|
|
266
|
+
"invalidDestinations": "swagger:getUrls returned an invalid destination list.",
|
|
267
|
+
"invalidDocument": "swagger:get returned an invalid OpenAPI document."
|
|
268
|
+
},
|
|
269
|
+
"messages": {
|
|
270
|
+
"empty": "No Swagger document namespaces are available.",
|
|
271
|
+
"saved": "Saved Swagger document to {{output}}."
|
|
272
|
+
},
|
|
273
|
+
"table": {
|
|
274
|
+
"name": "Name",
|
|
275
|
+
"namespace": "Namespace",
|
|
276
|
+
"url": "URL",
|
|
277
|
+
"field": "Field",
|
|
278
|
+
"value": "Value"
|
|
279
|
+
},
|
|
280
|
+
"fields": {
|
|
281
|
+
"namespace": "Namespace",
|
|
282
|
+
"title": "Title",
|
|
283
|
+
"version": "Version",
|
|
284
|
+
"openapi": "OpenAPI",
|
|
285
|
+
"paths": "Paths"
|
|
286
|
+
}
|
|
287
|
+
},
|
|
262
288
|
"portalInfo": {
|
|
263
289
|
"errors": {
|
|
264
290
|
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
package/dist/locale/zh-CN.json
CHANGED
|
@@ -259,6 +259,32 @@
|
|
|
259
259
|
"localSynced": "本地已同步"
|
|
260
260
|
}
|
|
261
261
|
},
|
|
262
|
+
"swagger": {
|
|
263
|
+
"errors": {
|
|
264
|
+
"pluginDisabled": "API 文档插件尚未启用。请先启用该插件,再获取 Swagger 文档。",
|
|
265
|
+
"requestFailed": "Swagger 请求失败,状态码 {{status}}\n{{details}}",
|
|
266
|
+
"invalidDestinations": "swagger:getUrls 返回了无效的文档列表。",
|
|
267
|
+
"invalidDocument": "swagger:get 返回了无效的 OpenAPI 文档。"
|
|
268
|
+
},
|
|
269
|
+
"messages": {
|
|
270
|
+
"empty": "没有可用的 Swagger 文档命名空间。",
|
|
271
|
+
"saved": "Swagger 文档已保存到 {{output}}。"
|
|
272
|
+
},
|
|
273
|
+
"table": {
|
|
274
|
+
"name": "名称",
|
|
275
|
+
"namespace": "命名空间",
|
|
276
|
+
"url": "URL",
|
|
277
|
+
"field": "字段",
|
|
278
|
+
"value": "值"
|
|
279
|
+
},
|
|
280
|
+
"fields": {
|
|
281
|
+
"namespace": "命名空间",
|
|
282
|
+
"title": "标题",
|
|
283
|
+
"version": "版本",
|
|
284
|
+
"openapi": "OpenAPI",
|
|
285
|
+
"paths": "路径数"
|
|
286
|
+
}
|
|
287
|
+
},
|
|
262
288
|
"portalInfo": {
|
|
263
289
|
"errors": {
|
|
264
290
|
"envNotConfigured": "env \"{{envName}}\" 尚未配置。请先运行 `nb env add {{envName}} --api-base-url <url>`。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.4",
|
|
4
4
|
"description": "NocoBase Command Line Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/generated/command-registry.js",
|
|
@@ -147,5 +147,5 @@
|
|
|
147
147
|
"type": "git",
|
|
148
148
|
"url": "git+https://github.com/nocobase/nocobase.git"
|
|
149
149
|
},
|
|
150
|
-
"gitHead": "
|
|
150
|
+
"gitHead": "6658768567378382de758c4e814ac510d688400c"
|
|
151
151
|
}
|