@nocobase/cli 2.1.0-beta.43 → 2.1.0-beta.44.test.1
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 +201 -0
- package/README.md +63 -380
- package/assets/env-proxy/nginx/app.conf.tpl +23 -0
- package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
- package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
- package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
- package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
- package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
- package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
- package/dist/commands/app/autostart/disable.js +55 -0
- package/dist/commands/app/autostart/enable.js +55 -0
- package/dist/commands/app/autostart/list.js +37 -0
- package/dist/commands/app/autostart/run.js +84 -0
- package/dist/commands/app/autostart/shared.js +49 -0
- package/dist/commands/app/destroy.js +8 -6
- package/dist/commands/app/down.js +2 -2
- package/dist/commands/app/logs.js +2 -1
- package/dist/commands/app/restart.js +79 -23
- package/dist/commands/app/shared.js +1 -1
- package/dist/commands/app/start.js +134 -38
- package/dist/commands/app/stop.js +31 -2
- package/dist/commands/app/upgrade.js +3 -1
- package/dist/commands/config/delete.js +4 -1
- package/dist/commands/config/get.js +4 -1
- package/dist/commands/config/set.js +5 -2
- package/dist/commands/env/add.js +19 -39
- package/dist/commands/env/info.js +3 -2
- package/dist/commands/env/proxy/caddy.js +28 -0
- package/dist/commands/env/proxy/index.js +353 -0
- package/dist/commands/env/proxy/nginx.js +28 -0
- package/dist/commands/env/remove.js +112 -22
- package/dist/commands/env/shared.js +17 -9
- package/dist/commands/env/update.js +385 -21
- package/dist/commands/init.js +233 -91
- package/dist/commands/install.js +174 -68
- package/dist/commands/license/activate.js +63 -244
- package/dist/commands/license/plugins/shared.js +64 -13
- package/dist/commands/plugin/import.js +108 -0
- package/dist/commands/revision/create.js +89 -0
- package/dist/locale/en-US.json +105 -19
- package/dist/locale/zh-CN.json +102 -16
- package/package.json +5 -8
- package/scripts/build.mjs +34 -0
- package/scripts/clean.mjs +9 -0
- package/tsconfig.json +19 -0
- package/LICENSE.txt +0 -107
- package/README.zh-CN.md +0 -355
- package/dist/lib/api-client.js +0 -335
- package/dist/lib/api-command-compat.js +0 -641
- package/dist/lib/app-health.js +0 -139
- package/dist/lib/app-managed-resources.js +0 -316
- package/dist/lib/app-runtime.js +0 -180
- package/dist/lib/auth-store.js +0 -405
- package/dist/lib/backup.js +0 -171
- package/dist/lib/bootstrap.js +0 -409
- package/dist/lib/build-config.js +0 -18
- package/dist/lib/builtin-db.js +0 -86
- package/dist/lib/cli-config.js +0 -309
- package/dist/lib/cli-entry-error.js +0 -44
- package/dist/lib/cli-home.js +0 -47
- package/dist/lib/cli-locale.js +0 -141
- package/dist/lib/command-discovery.js +0 -39
- package/dist/lib/db-connection-check.js +0 -219
- package/dist/lib/docker-env-file.js +0 -52
- package/dist/lib/docker-image.js +0 -37
- package/dist/lib/docker-log-stream.js +0 -45
- package/dist/lib/env-auth.js +0 -960
- package/dist/lib/env-config.js +0 -95
- package/dist/lib/env-guard.js +0 -62
- package/dist/lib/generated-command.js +0 -203
- package/dist/lib/http-request.js +0 -49
- package/dist/lib/inquirer-theme.js +0 -17
- package/dist/lib/inquirer.js +0 -244
- package/dist/lib/naming.js +0 -70
- package/dist/lib/object-utils.js +0 -76
- package/dist/lib/openapi.js +0 -62
- package/dist/lib/plugin-storage.js +0 -64
- package/dist/lib/post-processors.js +0 -23
- package/dist/lib/prompt-catalog-core.js +0 -185
- package/dist/lib/prompt-catalog-terminal.js +0 -375
- package/dist/lib/prompt-catalog.js +0 -10
- package/dist/lib/prompt-validators.js +0 -258
- package/dist/lib/prompt-web-ui.js +0 -2227
- package/dist/lib/resource-command.js +0 -357
- package/dist/lib/resource-request.js +0 -104
- package/dist/lib/run-npm.js +0 -385
- package/dist/lib/runtime-env-vars.js +0 -32
- package/dist/lib/runtime-generator.js +0 -498
- package/dist/lib/runtime-store.js +0 -56
- package/dist/lib/self-manager.js +0 -301
- package/dist/lib/session-id.js +0 -17
- package/dist/lib/session-integration.js +0 -703
- package/dist/lib/session-store.js +0 -118
- package/dist/lib/skills-manager.js +0 -436
- package/dist/lib/source-publish.js +0 -309
- package/dist/lib/source-registry.js +0 -188
- package/dist/lib/startup-update.js +0 -309
- package/dist/lib/ui.js +0 -158
|
@@ -7,26 +7,104 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { resolveManagedAppRuntime } from '../../lib/app-runtime.js';
|
|
10
11
|
import { getCurrentEnvName, loadAuthConfig, removeEnv } from '../../lib/auth-store.js';
|
|
11
12
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
|
-
import { confirm } from "../../lib/inquirer.js";
|
|
13
|
-
import { isInteractiveTerminal, printVerbose, setVerboseMode } from '../../lib/ui.js';
|
|
13
|
+
import { confirm, input } from "../../lib/inquirer.js";
|
|
14
|
+
import { isInteractiveTerminal, printInfo, printVerbose, setVerboseMode } from '../../lib/ui.js';
|
|
15
|
+
function formatRemoveForceRequiredMessage(envName, purge) {
|
|
16
|
+
if (purge) {
|
|
17
|
+
return [
|
|
18
|
+
`Refusing to purge env "${envName}" without confirmation in non-interactive mode.`,
|
|
19
|
+
'Re-run with `--purge --force` to continue.',
|
|
20
|
+
].join('\n');
|
|
21
|
+
}
|
|
22
|
+
return [
|
|
23
|
+
`Refusing to remove env "${envName}" without confirmation in non-interactive mode.`,
|
|
24
|
+
'Re-run with `--force` to continue.',
|
|
25
|
+
].join('\n');
|
|
26
|
+
}
|
|
27
|
+
function buildRemovePrompt(runtime, options) {
|
|
28
|
+
const subject = options.isCurrent ? `current env "${runtime.envName}"` : `env "${runtime.envName}"`;
|
|
29
|
+
if (options.purge) {
|
|
30
|
+
const lines = [`Purge ${subject}?`];
|
|
31
|
+
if (runtime.kind === 'local' || runtime.kind === 'docker') {
|
|
32
|
+
lines.push('This removes CLI-managed local runtime resources for this env on this machine.');
|
|
33
|
+
lines.push('Storage data will be removed, and downloaded local app files will be removed when applicable.');
|
|
34
|
+
lines.push('External database services are not managed by the CLI and will be left untouched.');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
lines.push('This env has no CLI-managed local runtime resources on this machine.');
|
|
38
|
+
lines.push('Only the saved CLI env config will be removed. External services are not touched.');
|
|
39
|
+
}
|
|
40
|
+
lines.push(`Type "${runtime.envName}" to confirm:`);
|
|
41
|
+
return lines.join('\n');
|
|
42
|
+
}
|
|
43
|
+
const lines = [`Remove ${subject}?`];
|
|
44
|
+
if (runtime.kind === 'local' || runtime.kind === 'docker') {
|
|
45
|
+
lines.push('NocoBase and any CLI-managed built-in database for this env will be stopped on this machine.');
|
|
46
|
+
lines.push('The saved CLI env config will then be removed. Storage data and local app files will be kept.');
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
lines.push('Only the saved CLI env config will be removed.');
|
|
50
|
+
}
|
|
51
|
+
return lines.join('\n');
|
|
52
|
+
}
|
|
53
|
+
async function confirmEnvRemoval(runtime, options) {
|
|
54
|
+
if (!isInteractiveTerminal()) {
|
|
55
|
+
if (options.force) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
throw new Error(formatRemoveForceRequiredMessage(runtime.envName, options.purge));
|
|
59
|
+
}
|
|
60
|
+
if (options.force) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (options.purge) {
|
|
64
|
+
try {
|
|
65
|
+
await input({
|
|
66
|
+
message: buildRemovePrompt(runtime, options),
|
|
67
|
+
required: true,
|
|
68
|
+
validate: (value) => (value.trim() === runtime.envName ? true : `Type "${runtime.envName}" to confirm.`),
|
|
69
|
+
placeholder: runtime.envName,
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
return await confirm({
|
|
79
|
+
message: buildRemovePrompt(runtime, options),
|
|
80
|
+
default: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
14
87
|
export default class EnvRemove extends Command {
|
|
15
88
|
static summary = 'Remove a configured environment';
|
|
16
|
-
static description = 'Remove
|
|
89
|
+
static description = 'Remove a configured env. Local and Docker envs stop CLI-managed runtime resources on this machine first; pass `--purge` to also delete managed local resources, storage data, and downloaded app files when applicable.';
|
|
17
90
|
static examples = [
|
|
18
91
|
'<%= config.bin %> <%= command.id %> staging',
|
|
19
|
-
'<%= config.bin %> <%= command.id %> staging --
|
|
92
|
+
'<%= config.bin %> <%= command.id %> staging --force',
|
|
93
|
+
'<%= config.bin %> <%= command.id %> staging --purge --force',
|
|
20
94
|
];
|
|
21
95
|
static flags = {
|
|
22
96
|
yes: Flags.boolean({
|
|
23
97
|
char: 'y',
|
|
24
|
-
|
|
98
|
+
hidden: true,
|
|
25
99
|
default: false,
|
|
26
100
|
}),
|
|
27
101
|
force: Flags.boolean({
|
|
28
102
|
char: 'f',
|
|
29
|
-
|
|
103
|
+
description: 'Skip confirmation for the selected remove mode',
|
|
104
|
+
default: false,
|
|
105
|
+
}),
|
|
106
|
+
purge: Flags.boolean({
|
|
107
|
+
description: 'Also remove CLI-managed local runtime resources, storage data, and downloaded app files when applicable. For remote API envs, only the saved CLI env config will be removed.',
|
|
30
108
|
default: false,
|
|
31
109
|
}),
|
|
32
110
|
verbose: Flags.boolean({
|
|
@@ -49,25 +127,37 @@ export default class EnvRemove extends Command {
|
|
|
49
127
|
this.error(`Env "${args.name}" is not configured`);
|
|
50
128
|
}
|
|
51
129
|
const currentEnv = await getCurrentEnvName({ scope });
|
|
130
|
+
const runtime = await resolveManagedAppRuntime(args.name);
|
|
131
|
+
if (!runtime) {
|
|
132
|
+
this.error(`Env "${args.name}" is not configured`);
|
|
133
|
+
}
|
|
52
134
|
const skipConfirmation = flags.yes || flags.force;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
135
|
+
let confirmed = false;
|
|
136
|
+
try {
|
|
137
|
+
confirmed = await confirmEnvRemoval(runtime, {
|
|
138
|
+
force: skipConfirmation,
|
|
139
|
+
isCurrent: args.name === currentEnv,
|
|
140
|
+
purge: flags.purge,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
145
|
+
}
|
|
146
|
+
if (!confirmed) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const runCommand = this.config.runCommand.bind(this.config);
|
|
150
|
+
const verboseArgv = flags.verbose ? ['--verbose'] : [];
|
|
151
|
+
if (flags.purge) {
|
|
152
|
+
if (runtime.kind === 'local' || runtime.kind === 'docker') {
|
|
153
|
+
await runCommand('app:destroy', ['--env', runtime.envName, '--force', ...verboseArgv]);
|
|
69
154
|
return;
|
|
70
155
|
}
|
|
156
|
+
printInfo(`No local CLI-managed resources were found for "${runtime.envName}". Removing the saved CLI env config only.`);
|
|
157
|
+
}
|
|
158
|
+
else if (runtime.kind === 'local' || runtime.kind === 'docker') {
|
|
159
|
+
printVerbose(`Stopping CLI-managed runtime resources for "${runtime.envName}" before removing the env config.`);
|
|
160
|
+
await runCommand('app:stop', ['--env', runtime.envName, '--with-db', '--yes', ...verboseArgv]);
|
|
71
161
|
}
|
|
72
162
|
printVerbose(`Removing env "${args.name}"`);
|
|
73
163
|
const result = await removeEnv(args.name, { scope });
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { buildDockerDbContainerName, dockerContainerExists, dockerContainerIsRunning, } from '../../lib/app-runtime.js';
|
|
10
10
|
import { executeRawApiRequest } from '../../lib/api-client.js';
|
|
11
|
+
import { buildLocalAppUrl } from '../../lib/app-public-path.js';
|
|
11
12
|
export function resolveApiBaseUrl(config) {
|
|
12
13
|
return String(config.apiBaseUrl ?? config.baseUrl ?? config.apibaseUrl ?? '').trim();
|
|
13
14
|
}
|
|
@@ -53,16 +54,23 @@ export function appUrl(runtime) {
|
|
|
53
54
|
}
|
|
54
55
|
const port = String(runtime.env.config.appPort ?? '').trim();
|
|
55
56
|
if (port) {
|
|
56
|
-
return
|
|
57
|
+
return buildLocalAppUrl(port, runtime.env.config?.appPublicPath) ?? '';
|
|
57
58
|
}
|
|
58
59
|
return '';
|
|
59
60
|
}
|
|
60
|
-
export function
|
|
61
|
+
export function appPath(runtime) {
|
|
62
|
+
if (runtime.kind === 'http') {
|
|
63
|
+
return '-';
|
|
64
|
+
}
|
|
65
|
+
const value = String(runtime.env.appPath ?? runtime.env.config.appPath ?? '').trim();
|
|
66
|
+
return value || '-';
|
|
67
|
+
}
|
|
68
|
+
export function sourcePath(runtime) {
|
|
61
69
|
if (runtime.kind === 'http' || runtime.kind === 'docker') {
|
|
62
70
|
return '-';
|
|
63
71
|
}
|
|
64
72
|
if (runtime.kind === 'local') {
|
|
65
|
-
return String(runtime.projectRoot ?? runtime.env.appRootPath ?? '').trim() || '-';
|
|
73
|
+
return String(runtime.projectRoot ?? runtime.env.sourcePath ?? runtime.env.appRootPath ?? '').trim() || '-';
|
|
66
74
|
}
|
|
67
75
|
return String(runtime.env.config.appRootPath ?? '').trim() || '-';
|
|
68
76
|
}
|
|
@@ -98,15 +106,15 @@ function isNetworkFailure(error) {
|
|
|
98
106
|
if (!(error instanceof Error)) {
|
|
99
107
|
return false;
|
|
100
108
|
}
|
|
101
|
-
return (error.name === 'AbortError'
|
|
102
|
-
|
|
109
|
+
return (error.name === 'AbortError' ||
|
|
110
|
+
/fetch failed|network|timeout|timed out|abort|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|EAI_AGAIN|ENETUNREACH/i.test(error.message));
|
|
103
111
|
}
|
|
104
112
|
function isUnconfiguredFailure(error) {
|
|
105
113
|
return error instanceof Error && /missing (a )?base url|missing base URL/i.test(error.message);
|
|
106
114
|
}
|
|
107
115
|
function isAuthFailure(error) {
|
|
108
|
-
return (error instanceof Error
|
|
109
|
-
|
|
116
|
+
return (error instanceof Error &&
|
|
117
|
+
/EMPTY_TOKEN|INVALID_TOKEN|EXPIRED_TOKEN|BLOCKED_TOKEN|EXPIRED_SESSION|NOT_EXIST_USER|invalid_grant|sign in|signin|authentication failed/i.test(error.message));
|
|
110
118
|
}
|
|
111
119
|
export async function apiStatus(envName, config, options = {}) {
|
|
112
120
|
if (!resolveApiBaseUrl(config)) {
|
|
@@ -166,7 +174,7 @@ async function dockerStatus(containerName) {
|
|
|
166
174
|
if (!(await dockerContainerExists(containerName))) {
|
|
167
175
|
return 'missing';
|
|
168
176
|
}
|
|
169
|
-
return await dockerContainerIsRunning(containerName) ? 'running' : 'stopped';
|
|
177
|
+
return (await dockerContainerIsRunning(containerName)) ? 'running' : 'stopped';
|
|
170
178
|
}
|
|
171
179
|
export async function dbStatus(runtime) {
|
|
172
180
|
if (!runtime.env.config.builtinDb) {
|
|
@@ -192,5 +200,5 @@ export async function runtimeStatus(runtime) {
|
|
|
192
200
|
if (runtime.kind === 'docker') {
|
|
193
201
|
return await dockerStatus(runtime.containerName);
|
|
194
202
|
}
|
|
195
|
-
return await isLocalAppHealthy(runtime) ? 'running' : 'stopped';
|
|
203
|
+
return (await isLocalAppHealthy(runtime)) ? 'running' : 'stopped';
|
|
196
204
|
}
|
|
@@ -9,20 +9,168 @@
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { Args, Command, Flags } from '@oclif/core';
|
|
12
|
-
import { getCurrentEnvName } from '../../lib/auth-store.js';
|
|
12
|
+
import { getCurrentEnvName, getEnv, replaceEnvConfig } from '../../lib/auth-store.js';
|
|
13
13
|
import { updateEnvRuntime } from '../../lib/bootstrap.js';
|
|
14
14
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
15
|
-
import {
|
|
15
|
+
import { ENV_BOOLEAN_CONFIG_FLAG_MAP, ENV_STRING_CONFIG_FLAG_MAP } from '../../lib/env-command-config.js';
|
|
16
|
+
import { buildStoredEnvConfig } from '../../lib/env-config.js';
|
|
17
|
+
import { failTask, printInfo, printVerbose, printWarningBlock, setVerboseMode, startTask, stopTask, succeedTask } from '../../lib/ui.js';
|
|
16
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const UPDATE_STRING_FLAGS = [
|
|
20
|
+
'source',
|
|
21
|
+
'download-version',
|
|
22
|
+
'docker-registry',
|
|
23
|
+
'docker-platform',
|
|
24
|
+
'git-url',
|
|
25
|
+
'npm-registry',
|
|
26
|
+
'app-path',
|
|
27
|
+
'app-root-path',
|
|
28
|
+
'storage-path',
|
|
29
|
+
'app-public-path',
|
|
30
|
+
'cdn-base-url',
|
|
31
|
+
'env-file',
|
|
32
|
+
'app-port',
|
|
33
|
+
'app-key',
|
|
34
|
+
'timezone',
|
|
35
|
+
'db-dialect',
|
|
36
|
+
'builtin-db-image',
|
|
37
|
+
'db-host',
|
|
38
|
+
'db-port',
|
|
39
|
+
'db-database',
|
|
40
|
+
'db-user',
|
|
41
|
+
'db-password',
|
|
42
|
+
'db-schema',
|
|
43
|
+
'db-table-prefix',
|
|
44
|
+
];
|
|
45
|
+
const UPDATE_BOOLEAN_FLAGS = ['builtin-db', 'dev-dependencies', 'build', 'build-dts', 'db-underscored'];
|
|
46
|
+
const UPDATE_SPECIAL_FIELDS = ['api-base-url', 'auth-type', 'access-token', 'username'];
|
|
47
|
+
const UNSETTABLE_FIELDS = new Set([...UPDATE_SPECIAL_FIELDS, ...UPDATE_STRING_FLAGS, ...UPDATE_BOOLEAN_FLAGS]);
|
|
48
|
+
const SOURCE_SETTING_FIELDS = new Set([
|
|
49
|
+
'source',
|
|
50
|
+
'download-version',
|
|
51
|
+
'docker-registry',
|
|
52
|
+
'docker-platform',
|
|
53
|
+
'git-url',
|
|
54
|
+
'npm-registry',
|
|
55
|
+
'dev-dependencies',
|
|
56
|
+
'build',
|
|
57
|
+
'build-dts',
|
|
58
|
+
]);
|
|
59
|
+
const APP_RESTART_FIELDS = new Set([
|
|
60
|
+
'app-path',
|
|
61
|
+
'app-root-path',
|
|
62
|
+
'storage-path',
|
|
63
|
+
'app-public-path',
|
|
64
|
+
'env-file',
|
|
65
|
+
'app-port',
|
|
66
|
+
'app-key',
|
|
67
|
+
'timezone',
|
|
68
|
+
'db-host',
|
|
69
|
+
'db-port',
|
|
70
|
+
'db-database',
|
|
71
|
+
'db-user',
|
|
72
|
+
'db-password',
|
|
73
|
+
'db-schema',
|
|
74
|
+
'db-table-prefix',
|
|
75
|
+
'db-underscored',
|
|
76
|
+
]);
|
|
77
|
+
const APP_RESTART_WITH_DB_FIELDS = new Set([
|
|
78
|
+
'builtin-db',
|
|
79
|
+
'db-dialect',
|
|
80
|
+
'builtin-db-image',
|
|
81
|
+
'db-port',
|
|
82
|
+
'db-database',
|
|
83
|
+
'db-user',
|
|
84
|
+
'db-password',
|
|
85
|
+
'storage-path',
|
|
86
|
+
]);
|
|
87
|
+
const __dirnameConfigFile = path.join(path.dirname(path.dirname(path.dirname(__dirname))), 'nocobase-ctl.config.json');
|
|
88
|
+
function hasTokenOverride(flags) {
|
|
89
|
+
return flags['access-token'] !== undefined || flags.token !== undefined;
|
|
90
|
+
}
|
|
91
|
+
function collectProvidedConfigFields(flags) {
|
|
92
|
+
const fields = new Set();
|
|
93
|
+
for (const field of UPDATE_SPECIAL_FIELDS) {
|
|
94
|
+
if (field === 'access-token') {
|
|
95
|
+
if (hasTokenOverride(flags)) {
|
|
96
|
+
fields.add(field);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (flags[field] !== undefined) {
|
|
101
|
+
fields.add(field);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const field of UPDATE_STRING_FLAGS) {
|
|
105
|
+
if (flags[field] !== undefined) {
|
|
106
|
+
fields.add(field);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const field of UPDATE_BOOLEAN_FLAGS) {
|
|
110
|
+
if (flags[field] !== undefined) {
|
|
111
|
+
fields.add(field);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return fields;
|
|
115
|
+
}
|
|
116
|
+
function normalizeUnsetFields(unset) {
|
|
117
|
+
const normalized = (unset ?? [])
|
|
118
|
+
.flatMap((value) => value.split(','))
|
|
119
|
+
.map((value) => value.trim())
|
|
120
|
+
.filter(Boolean);
|
|
121
|
+
for (const field of normalized) {
|
|
122
|
+
if (!UNSETTABLE_FIELDS.has(field)) {
|
|
123
|
+
throw new Error(`Unsupported --unset field "${field}". Supported fields: ${Array.from(UNSETTABLE_FIELDS).sort().join(', ')}.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return Array.from(new Set(normalized));
|
|
127
|
+
}
|
|
128
|
+
function buildCurrentConfigInput(env) {
|
|
129
|
+
return {
|
|
130
|
+
...env.config,
|
|
131
|
+
apiBaseUrl: env.apiBaseUrl,
|
|
132
|
+
authType: env.authType,
|
|
133
|
+
authUsername: env.config.authUsername,
|
|
134
|
+
accessToken: env.auth?.type === 'token' ? env.auth.accessToken : undefined,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function applyUnsetField(nextInput, field) {
|
|
138
|
+
switch (field) {
|
|
139
|
+
case 'api-base-url':
|
|
140
|
+
delete nextInput.apiBaseUrl;
|
|
141
|
+
return;
|
|
142
|
+
case 'auth-type':
|
|
143
|
+
delete nextInput.authType;
|
|
144
|
+
return;
|
|
145
|
+
case 'access-token':
|
|
146
|
+
delete nextInput.accessToken;
|
|
147
|
+
return;
|
|
148
|
+
case 'username':
|
|
149
|
+
delete nextInput.authUsername;
|
|
150
|
+
return;
|
|
151
|
+
default:
|
|
152
|
+
if (field in ENV_STRING_CONFIG_FLAG_MAP) {
|
|
153
|
+
delete nextInput[ENV_STRING_CONFIG_FLAG_MAP[field]];
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (field in ENV_BOOLEAN_CONFIG_FLAG_MAP) {
|
|
157
|
+
delete nextInput[ENV_BOOLEAN_CONFIG_FLAG_MAP[field]];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
17
161
|
export default class EnvUpdate extends Command {
|
|
18
|
-
static summary = 'Refresh an environment runtime from swagger:get
|
|
162
|
+
static summary = 'Refresh an environment runtime from swagger:get, or update the saved env config for one environment';
|
|
19
163
|
static examples = [
|
|
20
164
|
'<%= config.bin %> <%= command.id %>',
|
|
21
165
|
'<%= config.bin %> <%= command.id %> prod',
|
|
166
|
+
'<%= config.bin %> <%= command.id %> prod --api-base-url http://localhost:13000/api --access-token <token>',
|
|
167
|
+
'<%= config.bin %> <%= command.id %> local --app-port 13080 --timezone Asia/Shanghai',
|
|
168
|
+
'<%= config.bin %> <%= command.id %> local --cdn-base-url https://cdn.example.com/nocobase/',
|
|
169
|
+
'<%= config.bin %> <%= command.id %> local --unset git-url --unset npm-registry',
|
|
22
170
|
];
|
|
23
171
|
static args = {
|
|
24
172
|
name: Args.string({
|
|
25
|
-
description: 'Configured environment name to
|
|
173
|
+
description: 'Configured environment name to update. Defaults to the current env when omitted',
|
|
26
174
|
required: false,
|
|
27
175
|
}),
|
|
28
176
|
};
|
|
@@ -32,33 +180,138 @@ export default class EnvUpdate extends Command {
|
|
|
32
180
|
default: false,
|
|
33
181
|
}),
|
|
34
182
|
'api-base-url': Flags.string({
|
|
35
|
-
|
|
183
|
+
char: 'u',
|
|
184
|
+
description: 'Root URL for HTTP API calls, including the /api prefix (e.g. http://localhost:13000/api)',
|
|
36
185
|
}),
|
|
37
|
-
|
|
38
|
-
description: '
|
|
186
|
+
'auth-type': Flags.string({
|
|
187
|
+
description: 'Authentication: basic, token, or oauth',
|
|
188
|
+
options: ['basic', 'token', 'oauth'],
|
|
39
189
|
}),
|
|
40
|
-
token: Flags.string({
|
|
190
|
+
'access-token': Flags.string({
|
|
41
191
|
char: 't',
|
|
42
|
-
|
|
192
|
+
aliases: ['token'],
|
|
193
|
+
description: 'API key or access token for token-based authentication',
|
|
194
|
+
}),
|
|
195
|
+
username: Flags.string({
|
|
196
|
+
description: 'Username to save for basic authentication',
|
|
197
|
+
}),
|
|
198
|
+
source: Flags.string({
|
|
199
|
+
description: 'Saved application source type for this env',
|
|
200
|
+
options: ['docker', 'git', 'local', 'npm'],
|
|
201
|
+
}),
|
|
202
|
+
'download-version': Flags.string({
|
|
203
|
+
aliases: ['version'],
|
|
204
|
+
description: 'Saved downloaded app version for this env',
|
|
205
|
+
}),
|
|
206
|
+
'docker-registry': Flags.string({
|
|
207
|
+
description: 'Saved Docker registry for this env',
|
|
208
|
+
}),
|
|
209
|
+
'docker-platform': Flags.string({
|
|
210
|
+
description: 'Saved Docker image platform for this env',
|
|
211
|
+
options: ['auto', 'linux/amd64', 'linux/arm64'],
|
|
212
|
+
}),
|
|
213
|
+
'git-url': Flags.string({
|
|
214
|
+
description: 'Saved Git repository URL for this env',
|
|
215
|
+
}),
|
|
216
|
+
'npm-registry': Flags.string({
|
|
217
|
+
description: 'Saved npm registry for this env',
|
|
218
|
+
}),
|
|
219
|
+
'dev-dependencies': Flags.boolean({
|
|
220
|
+
allowNo: true,
|
|
221
|
+
description: 'Whether development dependencies are installed for this env',
|
|
222
|
+
}),
|
|
223
|
+
build: Flags.boolean({
|
|
224
|
+
allowNo: true,
|
|
225
|
+
description: 'Whether the app should be built after source download',
|
|
226
|
+
}),
|
|
227
|
+
'build-dts': Flags.boolean({
|
|
228
|
+
allowNo: true,
|
|
229
|
+
description: 'Whether declaration files should be emitted during build',
|
|
230
|
+
}),
|
|
231
|
+
'app-path': Flags.string({
|
|
232
|
+
description: 'Saved app path for this env',
|
|
233
|
+
}),
|
|
234
|
+
'app-root-path': Flags.string({
|
|
235
|
+
hidden: true,
|
|
236
|
+
description: 'Saved application root path for this env',
|
|
237
|
+
}),
|
|
238
|
+
'storage-path': Flags.string({
|
|
239
|
+
hidden: true,
|
|
240
|
+
description: 'Saved storage path for this env',
|
|
241
|
+
}),
|
|
242
|
+
'app-public-path': Flags.string({
|
|
243
|
+
description: 'Saved application public path for this env',
|
|
244
|
+
}),
|
|
245
|
+
'cdn-base-url': Flags.string({
|
|
246
|
+
description: 'Saved client asset CDN base URL (CDN_BASE_URL) for this env',
|
|
247
|
+
}),
|
|
248
|
+
'env-file': Flags.string({
|
|
249
|
+
hidden: true,
|
|
250
|
+
description: 'Saved Docker --env-file path for this env',
|
|
251
|
+
}),
|
|
252
|
+
'app-port': Flags.string({
|
|
253
|
+
description: 'Saved application HTTP port for this env',
|
|
254
|
+
}),
|
|
255
|
+
'app-key': Flags.string({
|
|
256
|
+
description: 'Saved application secret key for this env',
|
|
257
|
+
}),
|
|
258
|
+
timezone: Flags.string({
|
|
259
|
+
description: 'Saved application timezone for this env',
|
|
260
|
+
}),
|
|
261
|
+
'builtin-db': Flags.boolean({
|
|
262
|
+
allowNo: true,
|
|
263
|
+
description: 'Whether this env uses a CLI-managed built-in database',
|
|
264
|
+
}),
|
|
265
|
+
'db-dialect': Flags.string({
|
|
266
|
+
description: 'Saved database dialect for this env',
|
|
267
|
+
options: ['kingbase', 'mariadb', 'mysql', 'postgres'],
|
|
268
|
+
}),
|
|
269
|
+
'builtin-db-image': Flags.string({
|
|
270
|
+
description: 'Saved built-in database image for this env',
|
|
271
|
+
}),
|
|
272
|
+
'db-host': Flags.string({
|
|
273
|
+
description: 'Saved database host for this env',
|
|
274
|
+
}),
|
|
275
|
+
'db-port': Flags.string({
|
|
276
|
+
description: 'Saved database port for this env',
|
|
277
|
+
}),
|
|
278
|
+
'db-database': Flags.string({
|
|
279
|
+
description: 'Saved database name for this env',
|
|
280
|
+
}),
|
|
281
|
+
'db-user': Flags.string({
|
|
282
|
+
description: 'Saved database user for this env',
|
|
283
|
+
}),
|
|
284
|
+
'db-password': Flags.string({
|
|
285
|
+
description: 'Saved database password for this env',
|
|
286
|
+
}),
|
|
287
|
+
'db-schema': Flags.string({
|
|
288
|
+
description: 'Saved database schema for this env',
|
|
289
|
+
}),
|
|
290
|
+
'db-table-prefix': Flags.string({
|
|
291
|
+
description: 'Saved database table prefix for this env',
|
|
292
|
+
}),
|
|
293
|
+
'db-underscored': Flags.boolean({
|
|
294
|
+
allowNo: true,
|
|
295
|
+
description: 'Whether this env uses underscored database naming',
|
|
296
|
+
}),
|
|
297
|
+
unset: Flags.string({
|
|
298
|
+
multiple: true,
|
|
299
|
+
description: 'Unset one or more saved env config fields by canonical flag name',
|
|
43
300
|
}),
|
|
44
301
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
startTask(`Updating env runtime: ${envLabel}`);
|
|
302
|
+
buildRuntimeUpdateTaskMessage(envLabel) {
|
|
303
|
+
return `Updating env runtime: ${envLabel}`;
|
|
304
|
+
}
|
|
305
|
+
async refreshRuntime(envName, envLabel, verbose) {
|
|
306
|
+
startTask(this.buildRuntimeUpdateTaskMessage(envLabel));
|
|
51
307
|
try {
|
|
52
308
|
const runtime = await updateEnvRuntime({
|
|
53
309
|
envName,
|
|
54
310
|
scope: resolveDefaultConfigScope(),
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
token: flags.token,
|
|
58
|
-
configFile: path.join(path.dirname(path.dirname(path.dirname(__dirname))), 'nocobase-ctl.config.json'),
|
|
59
|
-
verbose: flags.verbose,
|
|
311
|
+
configFile: __dirnameConfigFile,
|
|
312
|
+
verbose,
|
|
60
313
|
});
|
|
61
|
-
if (
|
|
314
|
+
if (verbose) {
|
|
62
315
|
succeedTask(`Updated env "${envLabel}" to runtime "${runtime.version}".`);
|
|
63
316
|
}
|
|
64
317
|
else {
|
|
@@ -71,4 +324,115 @@ export default class EnvUpdate extends Command {
|
|
|
71
324
|
throw error;
|
|
72
325
|
}
|
|
73
326
|
}
|
|
327
|
+
printConfigUpdateHints(envName, changedFields, nextConfig) {
|
|
328
|
+
if (changedFields.size === 0) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
printInfo('Saved env config was updated. Runtime commands were not refreshed automatically.');
|
|
332
|
+
const shouldRestartWithDb = Array.from(changedFields).some((field) => APP_RESTART_WITH_DB_FIELDS.has(field)) &&
|
|
333
|
+
(nextConfig.builtinDb === true || changedFields.has('builtin-db'));
|
|
334
|
+
if (shouldRestartWithDb) {
|
|
335
|
+
printInfo(`Run \`nb app restart --env ${envName} --with-db\` when you're ready to apply these changes.`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (Array.from(changedFields).some((field) => APP_RESTART_FIELDS.has(field))) {
|
|
339
|
+
printInfo(`Run \`nb app restart --env ${envName}\` when you're ready to apply these changes.`);
|
|
340
|
+
}
|
|
341
|
+
if (Array.from(changedFields).some((field) => SOURCE_SETTING_FIELDS.has(field))) {
|
|
342
|
+
printInfo('Saved source settings were updated. Existing local source files are not replaced automatically.');
|
|
343
|
+
}
|
|
344
|
+
if (Array.from(changedFields).some((field) => field === 'auth-type' || field === 'access-token' || field === 'username') &&
|
|
345
|
+
(nextConfig.authType === 'basic' || nextConfig.authType === 'oauth' || !nextConfig.accessToken)) {
|
|
346
|
+
printInfo(`Run \`nb env auth ${envName}\` if you need to authenticate this env again before using runtime commands.`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async run() {
|
|
350
|
+
const { args, flags } = await this.parse(EnvUpdate);
|
|
351
|
+
const parsedFlags = flags;
|
|
352
|
+
setVerboseMode(Boolean(parsedFlags.verbose));
|
|
353
|
+
const unsetFields = normalizeUnsetFields(parsedFlags.unset);
|
|
354
|
+
const providedFields = collectProvidedConfigFields(parsedFlags);
|
|
355
|
+
const hasConfigChanges = providedFields.size > 0 || unsetFields.length > 0;
|
|
356
|
+
const tokenOverride = hasTokenOverride(parsedFlags);
|
|
357
|
+
for (const field of unsetFields) {
|
|
358
|
+
if (providedFields.has(field)) {
|
|
359
|
+
this.error(`Cannot combine --unset ${field} with an explicit update for the same field.`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (tokenOverride && parsedFlags['auth-type'] && parsedFlags['auth-type'] !== 'token') {
|
|
363
|
+
this.error('--access-token or --token can only be used with --auth-type token.');
|
|
364
|
+
}
|
|
365
|
+
if (!hasConfigChanges) {
|
|
366
|
+
const envName = args.name;
|
|
367
|
+
const envLabel = envName ?? (await getCurrentEnvName({ scope: resolveDefaultConfigScope() }));
|
|
368
|
+
await this.refreshRuntime(envName, envLabel, Boolean(parsedFlags.verbose));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const currentEnv = await getEnv(args.name, { scope: resolveDefaultConfigScope() });
|
|
372
|
+
if (!currentEnv) {
|
|
373
|
+
this.error(args.name?.trim()
|
|
374
|
+
? `Env "${args.name.trim()}" is not configured`
|
|
375
|
+
: 'No env is configured. Run `nb init --ui` or `nb env add <name> --api-base-url <url>` first.');
|
|
376
|
+
}
|
|
377
|
+
const envName = String(currentEnv.name ?? '').trim();
|
|
378
|
+
const effectiveAuthType = parsedFlags['auth-type'] ?? (tokenOverride ? 'token' : currentEnv.authType);
|
|
379
|
+
if (parsedFlags.username !== undefined && effectiveAuthType !== 'basic') {
|
|
380
|
+
this.error('--username can only be used when the env uses basic authentication.');
|
|
381
|
+
}
|
|
382
|
+
const nextInput = buildCurrentConfigInput(currentEnv);
|
|
383
|
+
nextInput.apiBaseUrl = currentEnv.apiBaseUrl;
|
|
384
|
+
nextInput.authType = currentEnv.authType;
|
|
385
|
+
nextInput.authUsername = currentEnv.config.authUsername;
|
|
386
|
+
if (parsedFlags['api-base-url'] !== undefined) {
|
|
387
|
+
nextInput.apiBaseUrl = parsedFlags['api-base-url'];
|
|
388
|
+
}
|
|
389
|
+
if (parsedFlags['auth-type'] !== undefined) {
|
|
390
|
+
nextInput.authType = parsedFlags['auth-type'];
|
|
391
|
+
}
|
|
392
|
+
if (parsedFlags.username !== undefined) {
|
|
393
|
+
nextInput.authUsername = String(parsedFlags.username ?? '').trim();
|
|
394
|
+
}
|
|
395
|
+
if (tokenOverride) {
|
|
396
|
+
nextInput.authType = 'token';
|
|
397
|
+
nextInput.accessToken = parsedFlags['access-token'] ?? parsedFlags.token;
|
|
398
|
+
}
|
|
399
|
+
for (const field of UPDATE_STRING_FLAGS) {
|
|
400
|
+
if (parsedFlags[field] !== undefined) {
|
|
401
|
+
nextInput[ENV_STRING_CONFIG_FLAG_MAP[field]] = parsedFlags[field];
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
for (const field of UPDATE_BOOLEAN_FLAGS) {
|
|
405
|
+
if (parsedFlags[field] !== undefined) {
|
|
406
|
+
nextInput[ENV_BOOLEAN_CONFIG_FLAG_MAP[field]] = parsedFlags[field];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (const field of unsetFields) {
|
|
410
|
+
applyUnsetField(nextInput, field);
|
|
411
|
+
}
|
|
412
|
+
const nextConfig = buildStoredEnvConfig(nextInput);
|
|
413
|
+
startTask(`Saving env config: ${envName}`);
|
|
414
|
+
try {
|
|
415
|
+
await replaceEnvConfig(envName, nextConfig, { scope: resolveDefaultConfigScope() });
|
|
416
|
+
succeedTask(`Saved env config for "${envName}".`);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
failTask(`Failed to save env config for "${envName}".`);
|
|
420
|
+
throw error;
|
|
421
|
+
}
|
|
422
|
+
const shouldRefreshRuntime = providedFields.has('api-base-url') || providedFields.has('access-token');
|
|
423
|
+
if (!shouldRefreshRuntime) {
|
|
424
|
+
this.printConfigUpdateHints(envName, new Set([...providedFields, ...unsetFields]), nextConfig);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
await this.refreshRuntime(envName, envName, Boolean(parsedFlags.verbose));
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
this.printConfigUpdateHints(envName, new Set([...providedFields, ...unsetFields]), nextConfig);
|
|
432
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
433
|
+
printWarningBlock(`Saved env config for "${envName}", but failed to refresh the runtime.\n${message}`);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
printVerbose(`Updated env "${envName}" config and refreshed the runtime.`);
|
|
437
|
+
}
|
|
74
438
|
}
|