@nocobase/cli 2.1.0-alpha.4 → 2.1.0-alpha.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +107 -0
- package/README.md +393 -19
- package/README.zh-CN.md +343 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +135 -0
- package/bin/session-env.js +39 -0
- package/dist/commands/api/resource/create.js +15 -0
- package/dist/commands/api/resource/destroy.js +15 -0
- package/dist/commands/api/resource/get.js +15 -0
- package/dist/commands/api/resource/index.js +20 -0
- package/dist/commands/api/resource/list.js +16 -0
- package/dist/commands/api/resource/query.js +15 -0
- package/dist/commands/api/resource/update.js +15 -0
- package/dist/commands/app/down.js +301 -0
- package/dist/commands/app/logs.js +114 -0
- package/dist/commands/app/restart.js +158 -0
- package/dist/commands/app/start.js +305 -0
- package/dist/commands/app/stop.js +115 -0
- package/dist/commands/app/upgrade.js +636 -0
- package/dist/commands/backup/create.js +147 -0
- package/dist/commands/backup/index.js +20 -0
- package/dist/commands/backup/restore.js +105 -0
- package/{src/cli.js → dist/commands/build.js} +4 -11
- package/dist/commands/config/delete.js +30 -0
- package/dist/commands/config/get.js +29 -0
- package/dist/commands/config/index.js +20 -0
- package/dist/commands/config/list.js +29 -0
- package/dist/commands/config/set.js +35 -0
- package/dist/commands/db/check.js +240 -0
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +60 -0
- package/dist/commands/db/shared.js +96 -0
- package/dist/commands/db/start.js +71 -0
- package/dist/commands/db/stop.js +71 -0
- package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
- package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
- package/dist/commands/download.js +13 -0
- package/dist/commands/env/add.js +366 -0
- package/dist/commands/env/auth.js +130 -0
- package/dist/commands/env/current.js +21 -0
- package/dist/commands/env/info.js +157 -0
- package/dist/commands/env/list.js +44 -0
- package/dist/commands/env/remove.js +84 -0
- package/dist/commands/env/shared.js +158 -0
- package/dist/commands/env/status.js +90 -0
- package/dist/commands/env/update.js +74 -0
- package/dist/commands/env/use.js +38 -0
- package/dist/commands/examples/prompts-stages.js +150 -0
- package/dist/commands/examples/prompts-test.js +181 -0
- package/dist/commands/init.js +1092 -0
- package/dist/commands/install.js +2378 -0
- package/dist/commands/license/activate.js +360 -0
- package/dist/commands/license/env.js +94 -0
- package/dist/commands/license/generate-id.js +108 -0
- package/dist/commands/license/id.js +70 -0
- package/dist/commands/license/index.js +20 -0
- package/dist/commands/license/plugins/clean.js +115 -0
- package/dist/commands/license/plugins/index.js +20 -0
- package/dist/commands/license/plugins/list.js +64 -0
- package/dist/commands/license/plugins/shared.js +325 -0
- package/dist/commands/license/plugins/sync.js +285 -0
- package/dist/commands/license/shared.js +423 -0
- package/dist/commands/license/status.js +64 -0
- package/dist/commands/logs.js +12 -0
- package/dist/commands/plugin/disable.js +86 -0
- package/dist/commands/plugin/enable.js +86 -0
- package/dist/commands/plugin/list.js +82 -0
- package/dist/commands/pm/disable.js +12 -0
- package/dist/commands/pm/enable.js +12 -0
- package/dist/commands/pm/list.js +12 -0
- package/dist/commands/restart.js +12 -0
- package/dist/commands/scaffold/migration.js +38 -0
- package/dist/commands/scaffold/plugin.js +37 -0
- package/dist/commands/self/check.js +71 -0
- package/dist/commands/self/index.js +20 -0
- package/dist/commands/self/update.js +95 -0
- package/dist/commands/session/id.js +24 -0
- package/dist/commands/session/remove.js +57 -0
- package/dist/commands/session/setup.js +62 -0
- package/dist/commands/skills/check.js +69 -0
- package/dist/commands/skills/index.js +20 -0
- package/dist/commands/skills/install.js +80 -0
- package/dist/commands/skills/remove.js +80 -0
- package/dist/commands/skills/update.js +87 -0
- package/dist/commands/source/build.js +58 -0
- package/dist/commands/source/dev.js +182 -0
- package/dist/commands/source/download.js +880 -0
- package/dist/commands/source/publish.js +109 -0
- package/dist/commands/source/registry/logs.js +70 -0
- package/dist/commands/source/registry/start.js +57 -0
- package/dist/commands/source/registry/status.js +33 -0
- package/dist/commands/source/registry/stop.js +48 -0
- package/dist/commands/source/test.js +477 -0
- package/dist/commands/start.js +12 -0
- package/dist/commands/stop.js +12 -0
- package/dist/commands/test.js +12 -0
- package/dist/commands/upgrade.js +12 -0
- package/dist/commands/v1.js +210 -0
- package/dist/generated/command-registry.js +133 -0
- package/dist/help/runtime-help.js +23 -0
- package/dist/lib/api-client.js +329 -0
- package/dist/lib/app-health.js +126 -0
- package/dist/lib/app-managed-resources.js +316 -0
- package/dist/lib/app-runtime.js +180 -0
- package/dist/lib/auth-store.js +368 -0
- package/dist/lib/backup.js +171 -0
- package/dist/lib/bootstrap.js +403 -0
- package/dist/lib/build-config.js +18 -0
- package/dist/lib/builtin-db.js +86 -0
- package/dist/lib/cli-config.js +176 -0
- package/dist/lib/cli-home.js +47 -0
- package/dist/lib/cli-locale.js +129 -0
- package/dist/lib/command-discovery.js +39 -0
- package/dist/lib/db-connection-check.js +158 -0
- package/dist/lib/docker-env-file.js +52 -0
- package/dist/lib/docker-image.js +37 -0
- package/dist/lib/env-auth.js +873 -0
- package/dist/lib/env-config.js +94 -0
- package/dist/lib/env-guard.js +62 -0
- package/dist/lib/generated-command.js +186 -0
- package/dist/lib/http-request.js +49 -0
- package/dist/lib/inquirer-theme.js +17 -0
- package/dist/lib/inquirer.js +244 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/object-utils.js +76 -0
- package/dist/lib/openapi.js +62 -0
- package/dist/lib/plugin-storage.js +64 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/prompt-catalog-core.js +185 -0
- package/dist/lib/prompt-catalog-terminal.js +375 -0
- package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
- package/dist/lib/prompt-validators.js +240 -0
- package/dist/lib/prompt-web-ui.js +2103 -0
- package/dist/lib/resource-command.js +357 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/run-npm.js +275 -0
- package/dist/lib/runtime-env-vars.js +32 -0
- package/dist/lib/runtime-generator.js +498 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/self-manager.js +301 -0
- package/dist/lib/session-id.js +17 -0
- package/dist/lib/session-integration.js +703 -0
- package/dist/lib/session-store.js +118 -0
- package/dist/lib/skills-manager.js +360 -0
- package/dist/lib/source-publish.js +306 -0
- package/dist/lib/source-registry.js +188 -0
- package/dist/lib/startup-update.js +285 -0
- package/dist/lib/ui.js +155 -0
- package/dist/locale/en-US.json +344 -0
- package/dist/locale/zh-CN.json +344 -0
- package/dist/post-processors/data-modeling.js +84 -0
- package/dist/post-processors/data-source-manager.js +138 -0
- package/dist/post-processors/index.js +19 -0
- package/nocobase-ctl.config.json +388 -0
- package/package.json +100 -26
- package/LICENSE +0 -661
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -95
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -49
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -166
- package/src/commands/create-nginx-conf.js +0 -37
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -200
- 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/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/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -517
- 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.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -11
- 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/plugin.tsx.tpl +0 -10
- 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,94 @@
|
|
|
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
|
+
const STRING_ENV_CONFIG_KEYS = [
|
|
10
|
+
'source',
|
|
11
|
+
'downloadVersion',
|
|
12
|
+
'dockerRegistry',
|
|
13
|
+
'dockerPlatform',
|
|
14
|
+
'gitUrl',
|
|
15
|
+
'npmRegistry',
|
|
16
|
+
'appRootPath',
|
|
17
|
+
'storagePath',
|
|
18
|
+
'envFile',
|
|
19
|
+
'appPort',
|
|
20
|
+
'appKey',
|
|
21
|
+
'timezone',
|
|
22
|
+
'dbDialect',
|
|
23
|
+
'builtinDbImage',
|
|
24
|
+
'dbHost',
|
|
25
|
+
'dbPort',
|
|
26
|
+
'dbDatabase',
|
|
27
|
+
'dbUser',
|
|
28
|
+
'dbPassword',
|
|
29
|
+
'dbSchema',
|
|
30
|
+
'dbTablePrefix',
|
|
31
|
+
'rootUsername',
|
|
32
|
+
'rootEmail',
|
|
33
|
+
'rootPassword',
|
|
34
|
+
'rootNickname',
|
|
35
|
+
];
|
|
36
|
+
const BOOLEAN_ENV_CONFIG_KEYS = [
|
|
37
|
+
'builtinDb',
|
|
38
|
+
'devDependencies',
|
|
39
|
+
'build',
|
|
40
|
+
'buildDts',
|
|
41
|
+
'dbUnderscored',
|
|
42
|
+
];
|
|
43
|
+
function trimConfigValue(value) {
|
|
44
|
+
const text = String(value ?? '').trim();
|
|
45
|
+
return text || undefined;
|
|
46
|
+
}
|
|
47
|
+
function resolveEnvKind(input) {
|
|
48
|
+
const source = trimConfigValue(input.source);
|
|
49
|
+
const appRootPath = trimConfigValue(input.appRootPath);
|
|
50
|
+
if (source === 'docker') {
|
|
51
|
+
return 'docker';
|
|
52
|
+
}
|
|
53
|
+
if (source === 'npm' || source === 'git' || source === 'local' || appRootPath) {
|
|
54
|
+
return 'local';
|
|
55
|
+
}
|
|
56
|
+
return 'http';
|
|
57
|
+
}
|
|
58
|
+
export function buildStoredEnvConfig(input) {
|
|
59
|
+
const envConfig = {
|
|
60
|
+
kind: resolveEnvKind(input),
|
|
61
|
+
apiBaseUrl: trimConfigValue(input.apiBaseUrl) ?? '',
|
|
62
|
+
};
|
|
63
|
+
for (const key of STRING_ENV_CONFIG_KEYS) {
|
|
64
|
+
const value = trimConfigValue(input[key]);
|
|
65
|
+
if (value) {
|
|
66
|
+
envConfig[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const key of BOOLEAN_ENV_CONFIG_KEYS) {
|
|
70
|
+
const value = input[key];
|
|
71
|
+
if (typeof value === 'boolean') {
|
|
72
|
+
envConfig[key] = value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (input.builtinDb === false) {
|
|
76
|
+
envConfig.builtinDbImage = undefined;
|
|
77
|
+
}
|
|
78
|
+
if (input.builtinDb === true) {
|
|
79
|
+
delete envConfig.dbHost;
|
|
80
|
+
const source = trimConfigValue(input.source);
|
|
81
|
+
if (source === 'docker') {
|
|
82
|
+
delete envConfig.dbPort;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const authType = trimConfigValue(input.authType);
|
|
86
|
+
if (authType === 'token' || authType === 'oauth') {
|
|
87
|
+
envConfig.authType = authType;
|
|
88
|
+
}
|
|
89
|
+
const accessToken = trimConfigValue(input.accessToken);
|
|
90
|
+
if (authType === 'token' && accessToken) {
|
|
91
|
+
envConfig.accessToken = accessToken;
|
|
92
|
+
}
|
|
93
|
+
return envConfig;
|
|
94
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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 { stdin as input, stdout as output } from 'node:process';
|
|
10
|
+
import { getCurrentEnvName } from './auth-store.js';
|
|
11
|
+
import { confirm } from "./inquirer.js";
|
|
12
|
+
function normalizeEnvName(value) {
|
|
13
|
+
const text = String(value ?? '').trim();
|
|
14
|
+
return text || undefined;
|
|
15
|
+
}
|
|
16
|
+
export function hasExplicitEnvSelection(argv) {
|
|
17
|
+
return argv.some((token, index) => (token === '--env'
|
|
18
|
+
|| token === '-e'
|
|
19
|
+
|| token.startsWith('--env=')
|
|
20
|
+
|| (token.startsWith('-e') && token.length > 2 && index >= 0)));
|
|
21
|
+
}
|
|
22
|
+
function isInteractiveTerminal() {
|
|
23
|
+
return Boolean(input.isTTY && output.isTTY);
|
|
24
|
+
}
|
|
25
|
+
function formatCrossEnvPromptMessage(currentEnv, requestedEnv) {
|
|
26
|
+
return `Current env is "${currentEnv}", but this command targets "${requestedEnv}" via --env. Continue without switching the current env?`;
|
|
27
|
+
}
|
|
28
|
+
export function formatCrossEnvRefusalMessage(currentEnv, requestedEnv) {
|
|
29
|
+
return [
|
|
30
|
+
`Refusing to run against env "${requestedEnv}" because the current env is "${currentEnv}" and interactive confirmation is unavailable in the current agent session.`,
|
|
31
|
+
'',
|
|
32
|
+
'For safety, the agent will not switch envs automatically and will not add --yes on your behalf.',
|
|
33
|
+
'',
|
|
34
|
+
'To continue:',
|
|
35
|
+
`- run \`nb env use ${requestedEnv}\` yourself and then re-run the command, or`,
|
|
36
|
+
`- re-run the same command with \`--env ${requestedEnv} --yes\` to confirm this one-off cross-env operation.`,
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
39
|
+
export async function ensureCrossEnvConfirmed(options) {
|
|
40
|
+
const requestedEnv = normalizeEnvName(options.requestedEnv);
|
|
41
|
+
if (!requestedEnv) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const currentEnv = normalizeEnvName(await getCurrentEnvName());
|
|
45
|
+
const interactiveTerminal = isInteractiveTerminal();
|
|
46
|
+
const bypassInteractivePrompt = interactiveTerminal && Boolean(options.yes);
|
|
47
|
+
if (!currentEnv || currentEnv === requestedEnv || bypassInteractivePrompt) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (!interactiveTerminal) {
|
|
51
|
+
options.command.error(formatCrossEnvRefusalMessage(currentEnv, requestedEnv));
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return Boolean(await confirm({
|
|
55
|
+
message: formatCrossEnvPromptMessage(currentEnv, requestedEnv),
|
|
56
|
+
default: false,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* This file is part of the NocoBase (R) project.
|
|
11
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
12
|
+
* Authors: NocoBase Team.
|
|
13
|
+
*
|
|
14
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
15
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
16
|
+
*/
|
|
17
|
+
import { Command, Flags } from '@oclif/core';
|
|
18
|
+
import { executeApiRequest } from './api-client.js';
|
|
19
|
+
import { ensureCrossEnvConfirmed } from './env-guard.js';
|
|
20
|
+
import { applyPostProcessor } from './post-processors.js';
|
|
21
|
+
import { registerPostProcessors } from '../post-processors/index.js';
|
|
22
|
+
function buildParameterFlag(parameter, options) {
|
|
23
|
+
const hints = [parameter.in];
|
|
24
|
+
if (parameter.isFile) {
|
|
25
|
+
hints.push('file path');
|
|
26
|
+
}
|
|
27
|
+
else if (parameter.type === 'object' || parameter.type === 'array' || parameter.jsonEncoded) {
|
|
28
|
+
hints.push('JSON');
|
|
29
|
+
}
|
|
30
|
+
else if (parameter.isArray) {
|
|
31
|
+
hints.push('repeatable');
|
|
32
|
+
}
|
|
33
|
+
else if (parameter.type) {
|
|
34
|
+
hints.push(parameter.type);
|
|
35
|
+
}
|
|
36
|
+
const description = [
|
|
37
|
+
`${parameter.description ?? ''}${parameter.description ? ' ' : ''}[${hints.join(', ')}]`.trim(),
|
|
38
|
+
parameter.jsonShape ? `Shape: ${parameter.jsonShape}` : undefined,
|
|
39
|
+
]
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join('\n');
|
|
42
|
+
const required = options?.required ?? parameter.required;
|
|
43
|
+
const helpGroup = parameter.in === 'body'
|
|
44
|
+
? 'Body Field'
|
|
45
|
+
: parameter.in === 'path'
|
|
46
|
+
? 'Path Parameter'
|
|
47
|
+
: parameter.in === 'query'
|
|
48
|
+
? 'Query Parameter'
|
|
49
|
+
: parameter.in === 'header'
|
|
50
|
+
? 'Header Parameter'
|
|
51
|
+
: parameter.in === 'cookie'
|
|
52
|
+
? 'Cookie Parameter'
|
|
53
|
+
: undefined;
|
|
54
|
+
if (parameter.type === 'boolean') {
|
|
55
|
+
return Flags.boolean({
|
|
56
|
+
description,
|
|
57
|
+
allowNo: true,
|
|
58
|
+
...(helpGroup ? { helpGroup } : {}),
|
|
59
|
+
...(required ? { required: true } : {}),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (parameter.isArray && !parameter.jsonEncoded) {
|
|
63
|
+
return Flags.string({
|
|
64
|
+
description,
|
|
65
|
+
multiple: true,
|
|
66
|
+
...(helpGroup ? { helpGroup } : {}),
|
|
67
|
+
...(required ? { required: true } : {}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return Flags.string({
|
|
71
|
+
description,
|
|
72
|
+
...(helpGroup ? { helpGroup } : {}),
|
|
73
|
+
...(required ? { required: true } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function createGeneratedFlags(operation) {
|
|
77
|
+
const flags = {};
|
|
78
|
+
for (const parameter of operation.parameters) {
|
|
79
|
+
flags[parameter.flagName] = buildParameterFlag(parameter, {
|
|
80
|
+
// Body flags are an alternative authoring path to --body/--body-file.
|
|
81
|
+
// Enforce required body semantics later in parseBody(), after we know
|
|
82
|
+
// which input mode the user chose.
|
|
83
|
+
required: parameter.in === 'body' && !parameter.isFile ? false : parameter.required,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (operation.hasBody && operation.requestContentType !== 'multipart/form-data') {
|
|
87
|
+
flags.body = Flags.string({
|
|
88
|
+
description: 'Full JSON request body string. Do not combine with body field flags.',
|
|
89
|
+
helpGroup: 'Raw JSON Body',
|
|
90
|
+
exclusive: ['body-file'],
|
|
91
|
+
});
|
|
92
|
+
flags['body-file'] = Flags.string({
|
|
93
|
+
description: 'Path to a JSON file containing the full request body. Do not combine with body field flags.',
|
|
94
|
+
helpGroup: 'Raw JSON Body',
|
|
95
|
+
exclusive: ['body'],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (operation.responseType === 'binary') {
|
|
99
|
+
flags.output = Flags.string({
|
|
100
|
+
description: 'Path where the downloaded response should be written.',
|
|
101
|
+
helpGroup: 'Output',
|
|
102
|
+
required: true,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
flags['api-base-url'] = Flags.string({
|
|
106
|
+
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
107
|
+
helpGroup: 'Global',
|
|
108
|
+
});
|
|
109
|
+
flags.verbose = Flags.boolean({
|
|
110
|
+
description: 'Show detailed progress output',
|
|
111
|
+
default: false,
|
|
112
|
+
helpGroup: 'Global',
|
|
113
|
+
});
|
|
114
|
+
flags.yes = Flags.boolean({
|
|
115
|
+
char: 'y',
|
|
116
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
117
|
+
default: false,
|
|
118
|
+
helpGroup: 'Global',
|
|
119
|
+
});
|
|
120
|
+
flags.env = Flags.string({
|
|
121
|
+
char: 'e',
|
|
122
|
+
description: 'Environment name',
|
|
123
|
+
helpGroup: 'Global',
|
|
124
|
+
});
|
|
125
|
+
flags.role = Flags.string({
|
|
126
|
+
description: 'Role override, sent as X-Role',
|
|
127
|
+
helpGroup: 'Global',
|
|
128
|
+
});
|
|
129
|
+
flags.token = Flags.string({
|
|
130
|
+
char: 't',
|
|
131
|
+
description: 'API key override',
|
|
132
|
+
helpGroup: 'Global',
|
|
133
|
+
});
|
|
134
|
+
flags['json-output'] = Flags.boolean({
|
|
135
|
+
char: 'j',
|
|
136
|
+
description: 'Print raw JSON response',
|
|
137
|
+
default: true,
|
|
138
|
+
allowNo: true,
|
|
139
|
+
helpGroup: 'Global',
|
|
140
|
+
});
|
|
141
|
+
return flags;
|
|
142
|
+
}
|
|
143
|
+
export class GeneratedApiCommand extends Command {
|
|
144
|
+
static operation;
|
|
145
|
+
async run() {
|
|
146
|
+
registerPostProcessors();
|
|
147
|
+
const ctor = this.constructor;
|
|
148
|
+
const { flags } = await this.parse(ctor);
|
|
149
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
150
|
+
command: this,
|
|
151
|
+
requestedEnv: flags.env,
|
|
152
|
+
yes: flags.yes,
|
|
153
|
+
});
|
|
154
|
+
if (!confirmed) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const response = await executeApiRequest({
|
|
158
|
+
envName: flags.env,
|
|
159
|
+
baseUrl: flags['api-base-url'],
|
|
160
|
+
role: flags.role,
|
|
161
|
+
token: flags.token,
|
|
162
|
+
flags,
|
|
163
|
+
operation: {
|
|
164
|
+
method: ctor.operation.method,
|
|
165
|
+
pathTemplate: ctor.operation.pathTemplate,
|
|
166
|
+
parameters: ctor.operation.parameters,
|
|
167
|
+
hasBody: ctor.operation.hasBody,
|
|
168
|
+
bodyRequired: ctor.operation.bodyRequired,
|
|
169
|
+
requestContentType: ctor.operation.requestContentType,
|
|
170
|
+
responseType: ctor.operation.responseType,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
this.error(`Request failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`);
|
|
175
|
+
}
|
|
176
|
+
const processedData = await applyPostProcessor(response.data, {
|
|
177
|
+
flags,
|
|
178
|
+
operation: ctor.operation,
|
|
179
|
+
});
|
|
180
|
+
if (flags['json-output']) {
|
|
181
|
+
this.log(JSON.stringify(processedData, null, 2));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
this.log(`HTTP ${response.status}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
function normalizeLocationUrl(location, currentUrl) {
|
|
10
|
+
try {
|
|
11
|
+
return new URL(location, currentUrl).toString();
|
|
12
|
+
}
|
|
13
|
+
catch (_error) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function shouldPreserveAuthorizationRedirect(fromUrl, toUrl) {
|
|
18
|
+
try {
|
|
19
|
+
const from = new URL(fromUrl);
|
|
20
|
+
const to = new URL(toUrl);
|
|
21
|
+
return (from.hostname === to.hostname &&
|
|
22
|
+
from.port === to.port &&
|
|
23
|
+
from.pathname === to.pathname &&
|
|
24
|
+
from.search === to.search &&
|
|
25
|
+
from.protocol === 'http:' &&
|
|
26
|
+
to.protocol === 'https:');
|
|
27
|
+
}
|
|
28
|
+
catch (_error) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function fetchWithPreservedAuthRedirect(url, init = {}) {
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
...init,
|
|
35
|
+
redirect: 'manual',
|
|
36
|
+
});
|
|
37
|
+
const location = response.headers.get('location');
|
|
38
|
+
if (!location || ![301, 302, 307, 308].includes(response.status)) {
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
const nextUrl = normalizeLocationUrl(location, url);
|
|
42
|
+
if (!nextUrl || !shouldPreserveAuthorizationRedirect(url, nextUrl)) {
|
|
43
|
+
return response;
|
|
44
|
+
}
|
|
45
|
+
return fetch(nextUrl, {
|
|
46
|
+
...init,
|
|
47
|
+
redirect: 'manual',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
export function buildPlainMessageTheme(extra) {
|
|
10
|
+
return {
|
|
11
|
+
...extra,
|
|
12
|
+
style: {
|
|
13
|
+
...extra?.style,
|
|
14
|
+
message: (text) => text,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
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 { cursorTo } from '@inquirer/ansi';
|
|
10
|
+
import pc from 'picocolors';
|
|
11
|
+
import { createPrompt, isBackspaceKey, isEnterKey, makeTheme, useEffect, useKeypress, usePrefix, useState, } from '@inquirer/core';
|
|
12
|
+
let confirmModulePromise;
|
|
13
|
+
let selectModulePromise;
|
|
14
|
+
function loadConfirmModule() {
|
|
15
|
+
confirmModulePromise ??= import('@inquirer/confirm');
|
|
16
|
+
return confirmModulePromise;
|
|
17
|
+
}
|
|
18
|
+
function loadSelectModule() {
|
|
19
|
+
selectModulePromise ??= import('@inquirer/select');
|
|
20
|
+
return selectModulePromise;
|
|
21
|
+
}
|
|
22
|
+
export function buildPlainMessageTheme(extra) {
|
|
23
|
+
return {
|
|
24
|
+
...extra,
|
|
25
|
+
style: {
|
|
26
|
+
...extra?.style,
|
|
27
|
+
message: (text) => text,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function buildSelectAnswer(text) {
|
|
32
|
+
return `\n${pc.cyan('❯')} ${pc.cyan(text)}`;
|
|
33
|
+
}
|
|
34
|
+
function buildConfirmAnswer(text) {
|
|
35
|
+
return `\n${pc.cyan('❯')} ${pc.cyan(text)}`;
|
|
36
|
+
}
|
|
37
|
+
function isFullWidthCodePoint(codePoint) {
|
|
38
|
+
return (codePoint >= 0x1100 &&
|
|
39
|
+
(codePoint <= 0x115f ||
|
|
40
|
+
codePoint === 0x2329 ||
|
|
41
|
+
codePoint === 0x232a ||
|
|
42
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
43
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
44
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
45
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
46
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
47
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
48
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
49
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
50
|
+
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
51
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd)));
|
|
52
|
+
}
|
|
53
|
+
function stringWidth(value) {
|
|
54
|
+
let width = 0;
|
|
55
|
+
for (const char of value) {
|
|
56
|
+
const codePoint = char.codePointAt(0);
|
|
57
|
+
if (codePoint == null)
|
|
58
|
+
continue;
|
|
59
|
+
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f))
|
|
60
|
+
continue;
|
|
61
|
+
width += isFullWidthCodePoint(codePoint) ? 2 : 1;
|
|
62
|
+
}
|
|
63
|
+
return width;
|
|
64
|
+
}
|
|
65
|
+
function stripAnsi(value) {
|
|
66
|
+
return value.replace(new RegExp(String.raw `\\u001B\\[[0-9;]*m`, 'g'), '');
|
|
67
|
+
}
|
|
68
|
+
function lastLineWidth(value) {
|
|
69
|
+
const lines = stripAnsi(value).split('\n');
|
|
70
|
+
return stringWidth(lines[lines.length - 1] ?? '');
|
|
71
|
+
}
|
|
72
|
+
function resolveRequiredError(required) {
|
|
73
|
+
if (typeof required === 'string') {
|
|
74
|
+
return required;
|
|
75
|
+
}
|
|
76
|
+
return '此项为必填';
|
|
77
|
+
}
|
|
78
|
+
function hasUsableDefault(value) {
|
|
79
|
+
return value != null && value !== '';
|
|
80
|
+
}
|
|
81
|
+
function buildIdleHint(config) {
|
|
82
|
+
const placeholder = config.placeholder?.trim();
|
|
83
|
+
if (placeholder) {
|
|
84
|
+
return placeholder;
|
|
85
|
+
}
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
function buildInputLine(value) {
|
|
89
|
+
return `${pc.cyan('❯')} ${value}`;
|
|
90
|
+
}
|
|
91
|
+
function buildErrorLine(error) {
|
|
92
|
+
return pc.red(`✖ ${error}`);
|
|
93
|
+
}
|
|
94
|
+
export const input = createPrompt((config, done) => {
|
|
95
|
+
const theme = makeTheme(config.theme);
|
|
96
|
+
const [status, setStatus] = useState('idle');
|
|
97
|
+
const [defaultValue, setDefaultValue] = useState(config.default ?? '');
|
|
98
|
+
const [value, setValue] = useState('');
|
|
99
|
+
const [error, setError] = useState();
|
|
100
|
+
const prefix = usePrefix({ status, theme });
|
|
101
|
+
useEffect((rl) => {
|
|
102
|
+
if (config.default) {
|
|
103
|
+
rl.write(config.default);
|
|
104
|
+
setValue(config.default);
|
|
105
|
+
}
|
|
106
|
+
}, []);
|
|
107
|
+
useKeypress(async (key, rl) => {
|
|
108
|
+
if (status !== 'idle')
|
|
109
|
+
return;
|
|
110
|
+
if (isBackspaceKey(key) && !value) {
|
|
111
|
+
setDefaultValue('');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (isBackspaceKey(key)) {
|
|
115
|
+
setValue(rl.line.slice(0, -1));
|
|
116
|
+
setError(undefined);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!isEnterKey(key)) {
|
|
120
|
+
setValue(rl.line);
|
|
121
|
+
setError(undefined);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const answer = value || defaultValue;
|
|
125
|
+
setStatus('loading');
|
|
126
|
+
if (!answer && config.required) {
|
|
127
|
+
rl.write(value);
|
|
128
|
+
setError(resolveRequiredError(config.required));
|
|
129
|
+
setStatus('idle');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (config.validate) {
|
|
133
|
+
const result = await config.validate(answer);
|
|
134
|
+
if (result !== true) {
|
|
135
|
+
rl.write(value);
|
|
136
|
+
setError(typeof result === 'string' ? result : 'Invalid input');
|
|
137
|
+
setStatus('idle');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
setValue(answer);
|
|
142
|
+
setStatus('done');
|
|
143
|
+
done(answer);
|
|
144
|
+
});
|
|
145
|
+
const message = theme.style.message(config.message, status);
|
|
146
|
+
const isTyping = value.length > 0;
|
|
147
|
+
const hasDefault = hasUsableDefault(defaultValue);
|
|
148
|
+
const idleHint = buildIdleHint(config);
|
|
149
|
+
const showIdleHint = !isTyping && status === 'idle' && idleHint !== '';
|
|
150
|
+
const showDefaultValue = !isTyping && status === 'idle' && hasDefault;
|
|
151
|
+
const displayValue = status === 'done'
|
|
152
|
+
? theme.style.answer(value)
|
|
153
|
+
: isTyping
|
|
154
|
+
? value
|
|
155
|
+
: showDefaultValue
|
|
156
|
+
? defaultValue
|
|
157
|
+
: showIdleHint
|
|
158
|
+
? pc.dim(idleHint)
|
|
159
|
+
: '';
|
|
160
|
+
const headerLine = [prefix, message].filter(Boolean).join(' ');
|
|
161
|
+
const inputLine = buildInputLine(displayValue);
|
|
162
|
+
const prompt = headerLine.endsWith('\n') ? `${headerLine}${inputLine}` : `${headerLine}\n${inputLine}`;
|
|
163
|
+
const promptWithCursorFix = showIdleHint
|
|
164
|
+
? prompt + cursorTo(lastLineWidth(buildInputLine('')))
|
|
165
|
+
: showDefaultValue
|
|
166
|
+
? prompt + cursorTo(lastLineWidth(buildInputLine(defaultValue)))
|
|
167
|
+
: prompt;
|
|
168
|
+
return [promptWithCursorFix, error ? buildErrorLine(error) : ''];
|
|
169
|
+
});
|
|
170
|
+
export async function confirm(options) {
|
|
171
|
+
const module = await loadConfirmModule();
|
|
172
|
+
return module.default({
|
|
173
|
+
...options,
|
|
174
|
+
theme: buildPlainMessageTheme({
|
|
175
|
+
style: {
|
|
176
|
+
answer: buildConfirmAnswer,
|
|
177
|
+
},
|
|
178
|
+
...options.theme,
|
|
179
|
+
}),
|
|
180
|
+
transformer: options.transformer ?? ((value) => (value ? 'Yes' : 'No')),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
export async function select(options) {
|
|
184
|
+
const module = await loadSelectModule();
|
|
185
|
+
return module.default({
|
|
186
|
+
...options,
|
|
187
|
+
theme: buildPlainMessageTheme({
|
|
188
|
+
style: {
|
|
189
|
+
answer: buildSelectAnswer,
|
|
190
|
+
},
|
|
191
|
+
...options.theme,
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function resolveMaskChar(mask) {
|
|
196
|
+
if (typeof mask === 'string') {
|
|
197
|
+
return mask;
|
|
198
|
+
}
|
|
199
|
+
if (mask === false) {
|
|
200
|
+
return '';
|
|
201
|
+
}
|
|
202
|
+
return '•';
|
|
203
|
+
}
|
|
204
|
+
export const password = createPrompt((config, done) => {
|
|
205
|
+
const theme = makeTheme(config.theme);
|
|
206
|
+
const [status, setStatus] = useState('idle');
|
|
207
|
+
const [value, setValue] = useState('');
|
|
208
|
+
const [error, setError] = useState();
|
|
209
|
+
const prefix = usePrefix({ status, theme });
|
|
210
|
+
useKeypress(async (key, rl) => {
|
|
211
|
+
if (status !== 'idle')
|
|
212
|
+
return;
|
|
213
|
+
if (!isEnterKey(key)) {
|
|
214
|
+
setValue(rl.line);
|
|
215
|
+
setError(undefined);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const answer = value;
|
|
219
|
+
setStatus('loading');
|
|
220
|
+
if (config.validate) {
|
|
221
|
+
const result = await config.validate(answer);
|
|
222
|
+
if (result !== true) {
|
|
223
|
+
rl.write(value);
|
|
224
|
+
setError(typeof result === 'string' ? result : 'Invalid input');
|
|
225
|
+
setStatus('idle');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setValue(answer);
|
|
230
|
+
setStatus('done');
|
|
231
|
+
done(answer);
|
|
232
|
+
});
|
|
233
|
+
const message = theme.style.message(config.message, status);
|
|
234
|
+
const maskChar = resolveMaskChar(config.mask);
|
|
235
|
+
const displayValue = status === 'done'
|
|
236
|
+
? theme.style.answer(maskChar.repeat(value.length))
|
|
237
|
+
: maskChar
|
|
238
|
+
? maskChar.repeat(value.length)
|
|
239
|
+
: '';
|
|
240
|
+
const headerLine = [prefix, message].filter(Boolean).join(' ');
|
|
241
|
+
const inputLine = buildInputLine(displayValue);
|
|
242
|
+
const prompt = headerLine.endsWith('\n') ? `${headerLine}${inputLine}` : `${headerLine}\n${inputLine}`;
|
|
243
|
+
return [prompt, error ? buildErrorLine(error) : ''];
|
|
244
|
+
});
|