@nocobase/cli 2.1.0-beta.35 → 2.1.0-beta.37
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/bin/run.js +3 -2
- package/dist/commands/app/upgrade.js +38 -16
- 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/dist/commands/config/delete.js +4 -0
- package/dist/commands/config/get.js +4 -0
- package/dist/commands/config/set.js +5 -1
- package/dist/commands/env/add.js +129 -15
- package/dist/commands/env/auth.js +145 -12
- package/dist/commands/env/info.js +52 -8
- package/dist/commands/env/list.js +2 -2
- package/dist/commands/env/shared.js +41 -3
- package/dist/commands/init.js +254 -136
- package/dist/commands/install.js +447 -272
- package/dist/commands/license/activate.js +6 -4
- package/dist/commands/source/publish.js +17 -0
- package/dist/commands/v1.js +210 -0
- package/dist/lib/app-managed-resources.js +20 -1
- package/dist/lib/app-runtime.js +13 -4
- package/dist/lib/auth-store.js +69 -18
- package/dist/lib/backup.js +171 -0
- package/dist/lib/bootstrap.js +23 -13
- package/dist/lib/cli-config.js +99 -4
- package/dist/lib/cli-locale.js +19 -7
- package/dist/lib/db-connection-check.js +61 -0
- package/dist/lib/env-auth.js +79 -0
- package/dist/lib/env-config.js +8 -1
- package/dist/lib/prompt-validators.js +23 -5
- package/dist/lib/prompt-web-ui.js +143 -19
- package/dist/lib/run-npm.js +166 -30
- package/dist/lib/skills-manager.js +74 -4
- package/dist/lib/source-publish.js +20 -1
- package/dist/lib/source-registry.js +2 -2
- package/dist/locale/en-US.json +36 -5
- package/dist/locale/zh-CN.json +36 -5
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ When creating a new app, it can also install NocoBase AI coding skills
|
|
|
61
61
|
(`nocobase/skills`) globally.
|
|
62
62
|
|
|
63
63
|
Use `--skip-skills` if the skills are managed separately, or when running in CI
|
|
64
|
-
or offline environments where `nb init` should not install
|
|
64
|
+
or offline environments where `nb init` should not install them.
|
|
65
65
|
|
|
66
66
|
### Non-Interactive Setup
|
|
67
67
|
|
package/README.zh-CN.md
CHANGED
|
@@ -55,7 +55,7 @@ nb init --ui
|
|
|
55
55
|
|
|
56
56
|
`nb init` 可以连接已有的 NocoBase 应用,也可以安装一个新的 NocoBase 应用。创建新应用时,还可以全局安装 NocoBase AI coding skills (`nocobase/skills`)。
|
|
57
57
|
|
|
58
|
-
如果已经自行管理 skills,或在 CI、离线环境中运行,不希望 `nb init`
|
|
58
|
+
如果已经自行管理 skills,或在 CI、离线环境中运行,不希望 `nb init` 安装 skills,可以传入 `--skip-skills`。
|
|
59
59
|
|
|
60
60
|
### 非交互式初始化
|
|
61
61
|
|
package/bin/run.js
CHANGED
|
@@ -4,6 +4,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import pc from 'picocolors';
|
|
7
8
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
9
|
import { normalizeNodeOptions, normalizeSessionEnv } from './session-env.js';
|
|
9
10
|
|
|
@@ -130,6 +131,6 @@ try {
|
|
|
130
131
|
flush();
|
|
131
132
|
} catch (error) {
|
|
132
133
|
const message = formatCliEntryError(error, process.argv.slice(2));
|
|
133
|
-
console.error(message);
|
|
134
|
-
process.
|
|
134
|
+
console.error(pc.red(message));
|
|
135
|
+
process.exit(1);
|
|
135
136
|
}
|
|
@@ -24,6 +24,18 @@ const APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS = 5_000;
|
|
|
24
24
|
function trimValue(value) {
|
|
25
25
|
return String(value ?? '').trim();
|
|
26
26
|
}
|
|
27
|
+
function pushOptionalEnvArg(args, key, value) {
|
|
28
|
+
if (typeof value === 'string') {
|
|
29
|
+
if (!value) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
args.push('-e', `${key}=${value}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (typeof value === 'boolean') {
|
|
36
|
+
args.push('-e', `${key}=${String(value)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
27
39
|
function formatAppUrl(port) {
|
|
28
40
|
const value = trimValue(port);
|
|
29
41
|
return value ? `http://127.0.0.1:${value}` : undefined;
|
|
@@ -262,10 +274,13 @@ export default class AppUpgrade extends Command {
|
|
|
262
274
|
persistDownloadVersion: requestedVersion || undefined,
|
|
263
275
|
};
|
|
264
276
|
}
|
|
265
|
-
static buildLocalDownloadArgv(runtime, downloadVersion) {
|
|
277
|
+
static buildLocalDownloadArgv(runtime, downloadVersion, options) {
|
|
266
278
|
const argv = ['-y', '--no-intro', '--source', runtime.source, '--replace'];
|
|
267
279
|
const gitUrl = readEnvValue(runtime.env, 'gitUrl');
|
|
268
280
|
const npmRegistry = readEnvValue(runtime.env, 'npmRegistry');
|
|
281
|
+
if (options?.verbose) {
|
|
282
|
+
argv.push('--verbose');
|
|
283
|
+
}
|
|
269
284
|
argv.push('--version', downloadVersion, '--output-dir', runtime.projectRoot);
|
|
270
285
|
if (gitUrl) {
|
|
271
286
|
argv.push('--git-url', gitUrl);
|
|
@@ -284,18 +299,12 @@ export default class AppUpgrade extends Command {
|
|
|
284
299
|
}
|
|
285
300
|
return argv;
|
|
286
301
|
}
|
|
287
|
-
static buildDockerDownloadArgv(runtime, plan) {
|
|
288
|
-
const argv = [
|
|
289
|
-
|
|
290
|
-
'--
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
'--replace',
|
|
294
|
-
'--docker-registry',
|
|
295
|
-
plan.dockerRegistry,
|
|
296
|
-
'--version',
|
|
297
|
-
plan.downloadVersion,
|
|
298
|
-
];
|
|
302
|
+
static buildDockerDownloadArgv(runtime, plan, options) {
|
|
303
|
+
const argv = ['-y', '--no-intro'];
|
|
304
|
+
if (options?.verbose) {
|
|
305
|
+
argv.push('--verbose');
|
|
306
|
+
}
|
|
307
|
+
argv.push('--source', 'docker', '--replace', '--docker-registry', plan.dockerRegistry, '--version', plan.downloadVersion);
|
|
299
308
|
const dockerPlatform = normalizeDockerPlatform(runtime.env.config.dockerPlatform);
|
|
300
309
|
if (dockerPlatform) {
|
|
301
310
|
argv.push('--docker-platform', dockerPlatform);
|
|
@@ -329,6 +338,11 @@ export default class AppUpgrade extends Command {
|
|
|
329
338
|
const dbDatabase = readEnvValue(runtime.env, 'dbDatabase');
|
|
330
339
|
const dbUser = readEnvValue(runtime.env, 'dbUser');
|
|
331
340
|
const dbPassword = readEnvValue(runtime.env, 'dbPassword');
|
|
341
|
+
const dbSchema = readEnvValue(runtime.env, 'dbSchema');
|
|
342
|
+
const dbTablePrefix = readEnvValue(runtime.env, 'dbTablePrefix');
|
|
343
|
+
const dbUnderscored = typeof runtime.env.config.dbUnderscored === 'boolean'
|
|
344
|
+
? runtime.env.config.dbUnderscored
|
|
345
|
+
: undefined;
|
|
332
346
|
const networkName = trimValue(runtime.dockerNetworkName || runtime.workspaceName);
|
|
333
347
|
const missing = [];
|
|
334
348
|
if (!networkName) {
|
|
@@ -384,7 +398,11 @@ export default class AppUpgrade extends Command {
|
|
|
384
398
|
if (envFile) {
|
|
385
399
|
args.push('--env-file', envFile);
|
|
386
400
|
}
|
|
387
|
-
args.push('-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:${DOCKER_APP_STORAGE_DESTINATION}
|
|
401
|
+
args.push('-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:${DOCKER_APP_STORAGE_DESTINATION}`);
|
|
402
|
+
pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema || undefined);
|
|
403
|
+
pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix || undefined);
|
|
404
|
+
pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
|
|
405
|
+
args.push(imageRef);
|
|
388
406
|
return {
|
|
389
407
|
containerName: runtime.containerName,
|
|
390
408
|
networkName,
|
|
@@ -422,7 +440,9 @@ export default class AppUpgrade extends Command {
|
|
|
422
440
|
if (!flags['skip-code-update'] && (runtime.source === 'npm' || runtime.source === 'git')) {
|
|
423
441
|
startTask(`Refreshing NocoBase files for "${runtime.envName}" from the saved ${runtime.source} source...`);
|
|
424
442
|
try {
|
|
425
|
-
await runCommand('source:download', AppUpgrade.buildLocalDownloadArgv(runtime, downloadVersion
|
|
443
|
+
await runCommand('source:download', AppUpgrade.buildLocalDownloadArgv(runtime, downloadVersion, {
|
|
444
|
+
verbose: flags.verbose,
|
|
445
|
+
}));
|
|
426
446
|
succeedTask(`NocoBase files are up to date for "${runtime.envName}".`);
|
|
427
447
|
}
|
|
428
448
|
catch (error) {
|
|
@@ -468,7 +488,9 @@ export default class AppUpgrade extends Command {
|
|
|
468
488
|
const plan = await AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
469
489
|
startTask(`Refreshing the Docker image for "${runtime.envName}"...`);
|
|
470
490
|
try {
|
|
471
|
-
await runCommand('source:download', AppUpgrade.buildDockerDownloadArgv(runtime, plan
|
|
491
|
+
await runCommand('source:download', AppUpgrade.buildDockerDownloadArgv(runtime, plan, {
|
|
492
|
+
verbose: flags.verbose,
|
|
493
|
+
}));
|
|
472
494
|
succeedTask(`Docker image is ready for "${runtime.envName}".`);
|
|
473
495
|
}
|
|
474
496
|
catch (error) {
|
|
@@ -0,0 +1,147 @@
|
|
|
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 { BACKUP_CREATE_TIMEOUT_MS, BACKUP_POLL_INTERVAL_MS, BACKUP_RUNTIME_COMMANDS, buildBackupEnvArgv, ensureBackupRuntimeCommands, resolveBackupCreateOutputPath, resolveBackupTargetEnv, runBackupCliJsonCommand, sleep, } from '../../lib/backup.js';
|
|
11
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
12
|
+
import { announceTargetEnv, failTask, startTask, succeedTask, updateTask } from '../../lib/ui.js';
|
|
13
|
+
function formatBackupCreateTimeoutError(envName, name) {
|
|
14
|
+
return [
|
|
15
|
+
`Backup "${name}" did not finish in time for "${envName}".`,
|
|
16
|
+
`Waited ${Math.floor(BACKUP_CREATE_TIMEOUT_MS / 1000)}s but it still reports \`inProgress: true\`.`,
|
|
17
|
+
].join(' ');
|
|
18
|
+
}
|
|
19
|
+
function readBackupCreateResult(response) {
|
|
20
|
+
const name = String(response.data?.name ?? '').trim();
|
|
21
|
+
if (!name) {
|
|
22
|
+
throw new Error('Backup creation did not return a backup name.');
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
inProgress: Boolean(response.data?.inProgress),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function readBackupInProgress(response, name) {
|
|
30
|
+
const status = response.data?.[name];
|
|
31
|
+
if (!status || typeof status !== 'object') {
|
|
32
|
+
throw new Error(`Backup status did not include "${name}".`);
|
|
33
|
+
}
|
|
34
|
+
return Boolean(status.inProgress);
|
|
35
|
+
}
|
|
36
|
+
function readDownloadOutput(response) {
|
|
37
|
+
const output = String(response.data?.output ?? '').trim();
|
|
38
|
+
return output || undefined;
|
|
39
|
+
}
|
|
40
|
+
export default class BackupCreate extends Command {
|
|
41
|
+
static summary = 'Create a backup through the selected env and download it locally';
|
|
42
|
+
static examples = [
|
|
43
|
+
'<%= config.bin %> <%= command.id %>',
|
|
44
|
+
'<%= config.bin %> <%= command.id %> --output ./fixtures/base.nbdump',
|
|
45
|
+
'<%= config.bin %> <%= command.id %> --env e2e --output ./fixtures',
|
|
46
|
+
];
|
|
47
|
+
static flags = {
|
|
48
|
+
env: Flags.string({
|
|
49
|
+
char: 'e',
|
|
50
|
+
description: 'CLI env name to back up. Defaults to the current env when omitted',
|
|
51
|
+
}),
|
|
52
|
+
yes: Flags.boolean({
|
|
53
|
+
char: 'y',
|
|
54
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
55
|
+
default: false,
|
|
56
|
+
}),
|
|
57
|
+
output: Flags.string({
|
|
58
|
+
char: 'o',
|
|
59
|
+
description: 'Download path. When omitted, save to the current directory using the remote backup filename',
|
|
60
|
+
}),
|
|
61
|
+
'json-output': Flags.boolean({
|
|
62
|
+
char: 'j',
|
|
63
|
+
description: 'Print the final backup result as JSON',
|
|
64
|
+
default: false,
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
async run() {
|
|
68
|
+
const { flags } = await this.parse(BackupCreate);
|
|
69
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
70
|
+
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
71
|
+
const jsonOutput = Boolean(flags['json-output']);
|
|
72
|
+
if (explicitEnvSelection) {
|
|
73
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
74
|
+
command: this,
|
|
75
|
+
requestedEnv,
|
|
76
|
+
yes: flags.yes,
|
|
77
|
+
});
|
|
78
|
+
if (!confirmed) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const { envName, env } = await resolveBackupTargetEnv(requestedEnv);
|
|
83
|
+
const envArgv = buildBackupEnvArgv({
|
|
84
|
+
requestedEnv,
|
|
85
|
+
explicitEnvSelection,
|
|
86
|
+
yes: flags.yes,
|
|
87
|
+
});
|
|
88
|
+
if (!jsonOutput) {
|
|
89
|
+
announceTargetEnv(envName);
|
|
90
|
+
}
|
|
91
|
+
await ensureBackupRuntimeCommands({
|
|
92
|
+
envName,
|
|
93
|
+
env,
|
|
94
|
+
commandIds: [
|
|
95
|
+
BACKUP_RUNTIME_COMMANDS.create,
|
|
96
|
+
BACKUP_RUNTIME_COMMANDS.status,
|
|
97
|
+
BACKUP_RUNTIME_COMMANDS.download,
|
|
98
|
+
],
|
|
99
|
+
quiet: jsonOutput,
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
if (!jsonOutput) {
|
|
103
|
+
startTask(`Creating backup for "${envName}"...`);
|
|
104
|
+
}
|
|
105
|
+
const createResponse = await runBackupCliJsonCommand(['api', 'backup', 'create', ...envArgv], { errorName: 'nb api backup create' });
|
|
106
|
+
const { name, inProgress } = readBackupCreateResult(createResponse);
|
|
107
|
+
const outputPath = await resolveBackupCreateOutputPath(flags.output, name);
|
|
108
|
+
const startedAt = Date.now();
|
|
109
|
+
let pending = inProgress;
|
|
110
|
+
while (pending) {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const elapsedMs = now - startedAt;
|
|
113
|
+
if (elapsedMs >= BACKUP_CREATE_TIMEOUT_MS) {
|
|
114
|
+
throw new Error(formatBackupCreateTimeoutError(envName, name));
|
|
115
|
+
}
|
|
116
|
+
const elapsedSeconds = Math.max(1, Math.floor(elapsedMs / 1000));
|
|
117
|
+
if (!jsonOutput) {
|
|
118
|
+
updateTask(`Waiting for backup "${name}" to finish for "${envName}"... (${elapsedSeconds}s elapsed)`);
|
|
119
|
+
}
|
|
120
|
+
await sleep(BACKUP_POLL_INTERVAL_MS);
|
|
121
|
+
const statusResponse = await runBackupCliJsonCommand(['api', 'backup', 'status', '--name', name, ...envArgv], { errorName: 'nb api backup status' });
|
|
122
|
+
pending = readBackupInProgress(statusResponse, name);
|
|
123
|
+
}
|
|
124
|
+
if (!jsonOutput) {
|
|
125
|
+
updateTask(`Downloading backup "${name}" for "${envName}"...`);
|
|
126
|
+
}
|
|
127
|
+
const downloadResponse = await runBackupCliJsonCommand(['api', 'backup', 'download', '--name', name, '--output', outputPath, ...envArgv], { errorName: 'nb api backup download' });
|
|
128
|
+
const savedPath = readDownloadOutput(downloadResponse) ?? outputPath;
|
|
129
|
+
const result = {
|
|
130
|
+
env: envName,
|
|
131
|
+
name,
|
|
132
|
+
output: savedPath,
|
|
133
|
+
};
|
|
134
|
+
if (jsonOutput) {
|
|
135
|
+
this.log(JSON.stringify(result, null, 2));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
succeedTask(`Backup saved to ${savedPath}`);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (!jsonOutput) {
|
|
142
|
+
failTask(`Failed to create backup for "${envName}".`);
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -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 Backup extends Command {
|
|
11
|
+
static summary = 'Create or restore NocoBase backups';
|
|
12
|
+
async run() {
|
|
13
|
+
await this.parse(Backup);
|
|
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 ?? 'backup',
|
|
17
|
+
...this.argv,
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
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 { BACKUP_RUNTIME_COMMANDS, buildBackupEnvArgv, ensureBackupRuntimeCommands, resolveBackupRestoreFilePath, resolveBackupTargetEnv, resolveBackupWaitApiBaseUrl, runBackupCliCommand, } from '../../lib/backup.js';
|
|
11
|
+
import { waitForAppReady } from '../../lib/app-health.js';
|
|
12
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
13
|
+
import { confirm } from "../../lib/inquirer.js";
|
|
14
|
+
import { announceTargetEnv, failTask, isInteractiveTerminal, startTask, stopTask, succeedTask } from '../../lib/ui.js';
|
|
15
|
+
async function confirmBackupRestore(envName, filePath, force) {
|
|
16
|
+
if (force) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (!isInteractiveTerminal()) {
|
|
20
|
+
throw new Error(`\`nb backup restore\` needs confirmation. Re-run with \`--force\` to restore ${filePath} into "${envName}" in non-interactive mode.`);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return await confirm({
|
|
24
|
+
message: `Restore backup "${filePath}" into "${envName}"? This will overwrite application data.`,
|
|
25
|
+
default: false,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export default class BackupRestore extends Command {
|
|
33
|
+
static summary = 'Restore a backup file into the selected env';
|
|
34
|
+
static examples = [
|
|
35
|
+
'<%= config.bin %> <%= command.id %> --file ./fixtures/base.nbdump --force',
|
|
36
|
+
'<%= config.bin %> <%= command.id %> --env e2e --file ./fixtures/base.nbdump --yes --force',
|
|
37
|
+
];
|
|
38
|
+
static flags = {
|
|
39
|
+
env: Flags.string({
|
|
40
|
+
char: 'e',
|
|
41
|
+
description: 'CLI env name to restore into. Defaults to the current env when omitted',
|
|
42
|
+
}),
|
|
43
|
+
yes: Flags.boolean({
|
|
44
|
+
char: 'y',
|
|
45
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
46
|
+
default: false,
|
|
47
|
+
}),
|
|
48
|
+
file: Flags.string({
|
|
49
|
+
char: 'f',
|
|
50
|
+
description: 'Local backup file to upload and restore',
|
|
51
|
+
required: true,
|
|
52
|
+
}),
|
|
53
|
+
force: Flags.boolean({
|
|
54
|
+
description: 'Confirm overwriting application data during restore',
|
|
55
|
+
default: false,
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
async run() {
|
|
59
|
+
const { flags } = await this.parse(BackupRestore);
|
|
60
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
61
|
+
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
62
|
+
if (explicitEnvSelection) {
|
|
63
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
64
|
+
command: this,
|
|
65
|
+
requestedEnv,
|
|
66
|
+
yes: flags.yes,
|
|
67
|
+
});
|
|
68
|
+
if (!confirmed) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const filePath = await resolveBackupRestoreFilePath(flags.file);
|
|
73
|
+
const { envName, env } = await resolveBackupTargetEnv(requestedEnv);
|
|
74
|
+
const restoreConfirmed = await confirmBackupRestore(envName, filePath, flags.force);
|
|
75
|
+
if (!restoreConfirmed) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const envArgv = buildBackupEnvArgv({
|
|
79
|
+
requestedEnv,
|
|
80
|
+
explicitEnvSelection,
|
|
81
|
+
yes: flags.yes,
|
|
82
|
+
});
|
|
83
|
+
announceTargetEnv(envName);
|
|
84
|
+
await ensureBackupRuntimeCommands({
|
|
85
|
+
envName,
|
|
86
|
+
env,
|
|
87
|
+
commandIds: [BACKUP_RUNTIME_COMMANDS.restoreUpload],
|
|
88
|
+
});
|
|
89
|
+
startTask(`Restoring backup for "${envName}" from ${filePath}...`);
|
|
90
|
+
try {
|
|
91
|
+
await runBackupCliCommand(['api', 'backup', 'restore-upload', '--file', filePath, '--force', ...envArgv], { errorName: 'nb api backup restore-upload' });
|
|
92
|
+
stopTask();
|
|
93
|
+
await waitForAppReady({
|
|
94
|
+
envName,
|
|
95
|
+
apiBaseUrl: resolveBackupWaitApiBaseUrl(env),
|
|
96
|
+
});
|
|
97
|
+
succeedTask(`Backup restored for "${envName}" from ${filePath}`);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
stopTask();
|
|
101
|
+
failTask(`Failed to restore backup for "${envName}".`);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -11,9 +11,13 @@ import { assertSupportedCliConfigKey, deleteCliConfigValue } from '../../lib/cli
|
|
|
11
11
|
export default class ConfigDelete extends Command {
|
|
12
12
|
static summary = 'Delete an explicitly configured CLI setting';
|
|
13
13
|
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %> locale',
|
|
14
15
|
'<%= config.bin %> <%= command.id %> license.pkg-url',
|
|
15
16
|
'<%= config.bin %> <%= command.id %> docker.network',
|
|
16
17
|
'<%= config.bin %> <%= command.id %> docker.container-prefix',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> bin.docker',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> bin.git',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> bin.yarn',
|
|
17
21
|
];
|
|
18
22
|
static args = {
|
|
19
23
|
key: Args.string({
|
|
@@ -11,9 +11,13 @@ import { assertSupportedCliConfigKey, getCliConfigValue } from '../../lib/cli-co
|
|
|
11
11
|
export default class ConfigGet extends Command {
|
|
12
12
|
static summary = 'Get the effective CLI configuration value for a key';
|
|
13
13
|
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %> locale',
|
|
14
15
|
'<%= config.bin %> <%= command.id %> license.pkg-url',
|
|
15
16
|
'<%= config.bin %> <%= command.id %> docker.network',
|
|
16
17
|
'<%= config.bin %> <%= command.id %> docker.container-prefix',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> bin.docker',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> bin.git',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> bin.yarn',
|
|
17
21
|
];
|
|
18
22
|
static args = {
|
|
19
23
|
key: Args.string({
|
|
@@ -10,11 +10,15 @@ import { Args, Command } from '@oclif/core';
|
|
|
10
10
|
import { assertSupportedCliConfigKey, setCliConfigValue } from '../../lib/cli-config.js';
|
|
11
11
|
export default class ConfigSet extends Command {
|
|
12
12
|
static summary = 'Set a CLI configuration value';
|
|
13
|
-
static description = 'Set a supported CLI configuration key. Supported keys: license.pkg-url, docker.network, docker.container-prefix.';
|
|
13
|
+
static description = 'Set a supported CLI configuration key. Supported keys: locale, license.pkg-url, docker.network, docker.container-prefix, bin.docker, bin.git, bin.yarn.';
|
|
14
14
|
static examples = [
|
|
15
|
+
'<%= config.bin %> <%= command.id %> locale zh-CN',
|
|
15
16
|
'<%= config.bin %> <%= command.id %> license.pkg-url https://pkg.nocobase.com/',
|
|
16
17
|
'<%= config.bin %> <%= command.id %> docker.network nocobase',
|
|
17
18
|
'<%= config.bin %> <%= command.id %> docker.container-prefix nb',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> bin.docker /usr/local/bin/docker',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> bin.git /usr/bin/git',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> bin.yarn yarn',
|
|
18
22
|
];
|
|
19
23
|
static args = {
|
|
20
24
|
key: Args.string({
|