@nocobase/cli 2.1.0-beta.24 → 2.1.0-beta.26
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 +19 -0
- package/dist/commands/app/down.js +12 -6
- package/dist/commands/app/logs.js +2 -2
- package/dist/commands/app/start.js +2 -1
- package/dist/commands/app/stop.js +2 -1
- package/dist/commands/app/upgrade.js +116 -129
- 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 +171 -65
- package/dist/commands/db/logs.js +2 -2
- package/dist/commands/db/shared.js +6 -5
- package/dist/commands/db/start.js +2 -1
- package/dist/commands/db/stop.js +2 -1
- package/dist/commands/env/info.js +6 -2
- package/dist/commands/env/shared.js +1 -1
- package/dist/commands/install.js +50 -35
- 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 +56 -0
- package/dist/commands/license/index.js +20 -0
- package/dist/commands/license/plugins/clean.js +101 -0
- package/dist/commands/license/plugins/index.js +20 -0
- package/dist/commands/license/plugins/list.js +50 -0
- package/dist/commands/license/plugins/shared.js +325 -0
- package/dist/commands/license/plugins/sync.js +269 -0
- package/dist/commands/license/shared.js +414 -0
- package/dist/commands/license/status.js +50 -0
- package/dist/commands/plugin/disable.js +2 -0
- package/dist/commands/plugin/enable.js +2 -0
- package/dist/commands/source/dev.js +2 -1
- package/dist/commands/source/test.js +11 -1
- package/dist/lib/api-client.js +74 -3
- package/dist/lib/app-managed-resources.js +10 -6
- package/dist/lib/app-runtime.js +29 -11
- package/dist/lib/auth-store.js +36 -68
- package/dist/lib/bootstrap.js +0 -4
- package/dist/lib/build-config.js +8 -0
- package/dist/lib/builtin-db.js +86 -0
- package/dist/lib/cli-config.js +176 -0
- package/dist/lib/cli-home.js +6 -21
- package/dist/lib/env-config.js +7 -0
- package/dist/lib/generated-command.js +24 -3
- package/dist/lib/plugin-storage.js +127 -0
- package/dist/lib/prompt-validators.js +4 -4
- package/dist/lib/run-npm.js +53 -0
- package/dist/lib/runtime-env-vars.js +32 -0
- package/dist/lib/runtime-generator.js +89 -10
- package/dist/lib/self-manager.js +57 -2
- package/dist/lib/skills-manager.js +2 -2
- package/dist/lib/startup-update.js +81 -6
- package/dist/lib/ui.js +3 -0
- package/dist/locale/en-US.json +0 -4
- package/dist/locale/zh-CN.json +0 -4
- package/nocobase-ctl.config.json +82 -0
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -336,6 +336,23 @@ nb api resource get --resource users --filter-by-tk 1 -e app1
|
|
|
336
336
|
nb api resource create --resource users --values '{"nickname":"Ada"}' -e app1
|
|
337
337
|
```
|
|
338
338
|
|
|
339
|
+
Create and download a backup:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
nb api backup create -e app1
|
|
343
|
+
nb api backup status --name backup_20260430_120000_1234.nbdata -e app1
|
|
344
|
+
nb api backup download --name backup_20260430_120000_1234.nbdata --output ./backup.nbdata -e app1
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Restore or run a migration package:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
nb api backup restore-upload --file ./backup.nbdata -e app1
|
|
351
|
+
nb api migration rules create --name default --user-defined-rule schema-only --system-defined-rule overwrite-first -e app1
|
|
352
|
+
nb api migration create --rule-id 1 --title release-20260430 -e app1
|
|
353
|
+
nb api migration execute --file ./migration.nbdata -e app1
|
|
354
|
+
```
|
|
355
|
+
|
|
339
356
|
Use `-j, --json-output` to print raw JSON when available:
|
|
340
357
|
|
|
341
358
|
```bash
|
|
@@ -350,9 +367,11 @@ Available API command topics:
|
|
|
350
367
|
| `nb api api-keys` | Manage API keys for HTTP API access. |
|
|
351
368
|
| `nb api app` | Manage application resources. |
|
|
352
369
|
| `nb api authenticators` | Manage user authentication, including password auth, SMS auth, SSO protocols, and extensible providers. |
|
|
370
|
+
| `nb api backup` | Create, download, remove, and restore backups. |
|
|
353
371
|
| `nb api data-modeling` | Manage data sources, collections, and database modeling resources. |
|
|
354
372
|
| `nb api file-manager` | Manage file storage services, file collections, and attachment fields. |
|
|
355
373
|
| `nb api flow-surfaces` | Compose and mutate page, tab, block, field, and action surfaces. |
|
|
374
|
+
| `nb api migration` | Create, check, execute, and inspect migration packages. |
|
|
356
375
|
| `nb api pm` | Manage plugins through API commands. |
|
|
357
376
|
| `nb api resource` | Work with generic collection resources. |
|
|
358
377
|
| `nb api system-settings` | Adjust system title, logo, language, and other global settings. |
|
|
@@ -92,21 +92,26 @@ function builtinDbContainerName(runtime) {
|
|
|
92
92
|
return undefined;
|
|
93
93
|
}
|
|
94
94
|
const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
|
|
95
|
-
|
|
96
|
-
return buildDockerDbContainerName(runtime.envName, dbDialect, workspaceName);
|
|
95
|
+
return buildDockerDbContainerName(runtime.envName, dbDialect, runtime.dockerContainerPrefix || runtime.workspaceName);
|
|
97
96
|
}
|
|
98
97
|
function managedDockerNetworkName(runtime) {
|
|
99
|
-
return runtime.workspaceName?.trim() || undefined;
|
|
98
|
+
return runtime.dockerNetworkName?.trim() || runtime.workspaceName?.trim() || undefined;
|
|
100
99
|
}
|
|
101
|
-
async function confirmDownAll(envName, yes) {
|
|
100
|
+
async function confirmDownAll(envName, yes, options) {
|
|
102
101
|
if (yes) {
|
|
103
102
|
return true;
|
|
104
103
|
}
|
|
104
|
+
const usedCurrentEnv = options?.explicitEnv === false;
|
|
105
105
|
if (!isInteractiveTerminal()) {
|
|
106
|
+
if (usedCurrentEnv) {
|
|
107
|
+
throw new Error(`\`nb app down --all\` is using the current env "${envName}". Re-run with --env ${envName} --yes to delete everything for that env in non-interactive mode.`);
|
|
108
|
+
}
|
|
106
109
|
throw new Error(`\`nb app down --all\` needs confirmation. Re-run with --yes to delete everything for "${envName}" in non-interactive mode.`);
|
|
107
110
|
}
|
|
108
111
|
const answer = await p.confirm({
|
|
109
|
-
message:
|
|
112
|
+
message: usedCurrentEnv
|
|
113
|
+
? `Delete everything for current env "${envName}"? This removes the app, managed containers, storage data, and the saved CLI env config.`
|
|
114
|
+
: `Delete everything for "${envName}"? This removes the app, managed containers, storage data, and the saved CLI env config.`,
|
|
110
115
|
active: 'yes',
|
|
111
116
|
inactive: 'no',
|
|
112
117
|
initialValue: false,
|
|
@@ -153,6 +158,7 @@ export default class AppDown extends Command {
|
|
|
153
158
|
async run() {
|
|
154
159
|
const { flags } = await this.parse(AppDown);
|
|
155
160
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
161
|
+
const explicitEnv = Boolean(requestedEnv);
|
|
156
162
|
const removeData = Boolean(flags.all);
|
|
157
163
|
const removeEnvConfig = Boolean(flags.all);
|
|
158
164
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
@@ -176,7 +182,7 @@ export default class AppDown extends Command {
|
|
|
176
182
|
if (flags.all) {
|
|
177
183
|
let confirmed = false;
|
|
178
184
|
try {
|
|
179
|
-
confirmed = await confirmDownAll(runtime.envName, flags.yes);
|
|
185
|
+
confirmed = await confirmDownAll(runtime.envName, flags.yes, { explicitEnv });
|
|
180
186
|
}
|
|
181
187
|
catch (error) {
|
|
182
188
|
this.error(error instanceof Error ? error.message : String(error));
|
|
@@ -39,7 +39,7 @@ export default class AppLogs extends Command {
|
|
|
39
39
|
follow: Flags.boolean({
|
|
40
40
|
char: 'f',
|
|
41
41
|
description: 'Keep streaming new log lines',
|
|
42
|
-
default:
|
|
42
|
+
default: false,
|
|
43
43
|
allowNo: true,
|
|
44
44
|
}),
|
|
45
45
|
};
|
|
@@ -65,7 +65,7 @@ export default class AppLogs extends Command {
|
|
|
65
65
|
].join('\n'));
|
|
66
66
|
}
|
|
67
67
|
const tail = String(flags.tail ?? 100);
|
|
68
|
-
const follow = flags.follow
|
|
68
|
+
const follow = flags.follow === true;
|
|
69
69
|
printInfo(follow
|
|
70
70
|
? `Showing logs for "${runtime.envName}" (press Ctrl+C to stop).`
|
|
71
71
|
: `Showing recent logs for "${runtime.envName}".`);
|
|
@@ -10,7 +10,7 @@ import { Command, Flags } from '@oclif/core';
|
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, startDockerContainer, } from '../../lib/app-runtime.js';
|
|
11
11
|
import { AppHealthCheckError, formatAppUrl, isAppReady, resolveManagedAppApiBaseUrl, waitForAppReady, } from '../../lib/app-health.js';
|
|
12
12
|
import { ensureBuiltinDbReady, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
|
|
13
|
-
import { failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
13
|
+
import { announceTargetEnv, failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
14
14
|
function argvHasToken(argv, tokens) {
|
|
15
15
|
return tokens.some((token) => argv.includes(token));
|
|
16
16
|
}
|
|
@@ -107,6 +107,7 @@ export default class AppStart extends Command {
|
|
|
107
107
|
'Use a local or Docker env if you need CLI-managed start and stop right now.',
|
|
108
108
|
].join('\n'));
|
|
109
109
|
}
|
|
110
|
+
announceTargetEnv(runtime.envName);
|
|
110
111
|
if (runtime.kind === 'docker') {
|
|
111
112
|
const unsupportedFlags = [
|
|
112
113
|
flags.quickstart ? '--quickstart' : undefined,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, stopDockerContainer, } from '../../lib/app-runtime.js';
|
|
11
|
-
import { failTask, startTask, succeedTask } from '../../lib/ui.js';
|
|
11
|
+
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
|
|
12
12
|
function formatStopFailure(envName, message) {
|
|
13
13
|
if (/does not exist/i.test(message)) {
|
|
14
14
|
return [
|
|
@@ -65,6 +65,7 @@ export default class AppStop extends Command {
|
|
|
65
65
|
'Use a local or Docker env if you need CLI-managed stop right now.',
|
|
66
66
|
].join('\n'));
|
|
67
67
|
}
|
|
68
|
+
announceTargetEnv(runtime.envName);
|
|
68
69
|
if (runtime.kind === 'docker') {
|
|
69
70
|
startTask(`Stopping NocoBase for "${runtime.envName}"...`);
|
|
70
71
|
try {
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { upsertEnv } from '../../lib/auth-store.js';
|
|
10
11
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, startDockerContainer, stopDockerContainer, } from '../../lib/app-runtime.js';
|
|
11
12
|
import { resolveConfiguredEnvPath } from '../../lib/cli-home.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
13
|
+
import { deriveBuiltinDbConnection } from '../../lib/builtin-db.js';
|
|
14
|
+
import { commandSucceeds, run } from '../../lib/run-npm.js';
|
|
15
|
+
import { announceTargetEnv, failTask, printInfo, startTask, stopTask, succeedTask, updateTask } from '../../lib/ui.js';
|
|
14
16
|
const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
|
|
15
|
-
const DEFAULT_DOWNLOAD_VERSION = 'alpha';
|
|
16
17
|
const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
|
|
17
18
|
const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
|
|
18
19
|
const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
|
|
@@ -92,27 +93,6 @@ function formatDockerStartFailure(envName, message) {
|
|
|
92
93
|
`Details: ${message}`,
|
|
93
94
|
].join('\n');
|
|
94
95
|
}
|
|
95
|
-
function parseDockerImageRef(imageRef) {
|
|
96
|
-
const cleaned = trimValue(imageRef).replace(/@.+$/, '');
|
|
97
|
-
if (!cleaned) {
|
|
98
|
-
return {
|
|
99
|
-
dockerRegistry: DEFAULT_DOCKER_REGISTRY,
|
|
100
|
-
version: DEFAULT_DOWNLOAD_VERSION,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const lastSlash = cleaned.lastIndexOf('/');
|
|
104
|
-
const lastColon = cleaned.lastIndexOf(':');
|
|
105
|
-
if (lastColon > lastSlash) {
|
|
106
|
-
return {
|
|
107
|
-
dockerRegistry: cleaned.slice(0, lastColon),
|
|
108
|
-
version: cleaned.slice(lastColon + 1) || DEFAULT_DOWNLOAD_VERSION,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
dockerRegistry: cleaned,
|
|
113
|
-
version: 'latest',
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
96
|
function normalizeDockerPlatform(value) {
|
|
117
97
|
const text = String(value ?? '').trim();
|
|
118
98
|
if (!text || text === 'auto') {
|
|
@@ -215,52 +195,14 @@ async function ensureDockerNetwork(name) {
|
|
|
215
195
|
stdio: 'ignore',
|
|
216
196
|
});
|
|
217
197
|
}
|
|
218
|
-
async function inspectDockerContainerEnv(name) {
|
|
219
|
-
const output = await commandOutput('docker', [
|
|
220
|
-
'inspect',
|
|
221
|
-
'--format',
|
|
222
|
-
'{{range .Config.Env}}{{println .}}{{end}}',
|
|
223
|
-
name,
|
|
224
|
-
], {
|
|
225
|
-
errorName: 'docker inspect',
|
|
226
|
-
});
|
|
227
|
-
const env = {};
|
|
228
|
-
for (const line of output.split(/\r?\n/)) {
|
|
229
|
-
const index = line.indexOf('=');
|
|
230
|
-
if (index <= 0) {
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
env[line.slice(0, index)] = line.slice(index + 1);
|
|
234
|
-
}
|
|
235
|
-
return env;
|
|
236
|
-
}
|
|
237
|
-
async function inspectDockerContainerImage(name) {
|
|
238
|
-
return await commandOutput('docker', [
|
|
239
|
-
'inspect',
|
|
240
|
-
'--format',
|
|
241
|
-
'{{.Config.Image}}',
|
|
242
|
-
name,
|
|
243
|
-
], {
|
|
244
|
-
errorName: 'docker inspect',
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
async function inspectDockerStoragePath(name) {
|
|
248
|
-
return await commandOutput('docker', [
|
|
249
|
-
'inspect',
|
|
250
|
-
'--format',
|
|
251
|
-
`{{range .Mounts}}{{if eq .Destination "${DOCKER_APP_STORAGE_DESTINATION}"}}{{println .Source}}{{end}}{{end}}`,
|
|
252
|
-
name,
|
|
253
|
-
], {
|
|
254
|
-
errorName: 'docker inspect',
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
198
|
export default class AppUpgrade extends Command {
|
|
258
199
|
static hidden = false;
|
|
259
|
-
static description = 'Upgrade the selected NocoBase app. Local npm/git installs refresh the saved source and restart with quickstart; Docker installs refresh the saved image and recreate the app container.';
|
|
200
|
+
static description = 'Upgrade the selected NocoBase app. Local npm/git installs refresh the saved source and restart with quickstart; Docker installs refresh the saved image and recreate the app container. Use --version to upgrade to a specific saved source version or image tag.';
|
|
260
201
|
static examples = [
|
|
261
202
|
'<%= config.bin %> <%= command.id %>',
|
|
262
203
|
'<%= config.bin %> <%= command.id %> --env local',
|
|
263
204
|
'<%= config.bin %> <%= command.id %> --env local -s',
|
|
205
|
+
'<%= config.bin %> <%= command.id %> --env local --version beta',
|
|
264
206
|
'<%= config.bin %> <%= command.id %> --env local --verbose',
|
|
265
207
|
'<%= config.bin %> <%= command.id %> --env local-docker -s',
|
|
266
208
|
];
|
|
@@ -274,23 +216,49 @@ export default class AppUpgrade extends Command {
|
|
|
274
216
|
description: 'Restart with the saved local code or Docker image without downloading updates first',
|
|
275
217
|
required: false,
|
|
276
218
|
}),
|
|
219
|
+
version: Flags.string({
|
|
220
|
+
description: 'Override the saved downloadVersion for this upgrade. When the upgrade succeeds, the new version is saved back to the env config.',
|
|
221
|
+
required: false,
|
|
222
|
+
}),
|
|
277
223
|
verbose: Flags.boolean({
|
|
278
224
|
description: 'Show raw output from the underlying local or Docker commands',
|
|
279
225
|
default: false,
|
|
280
226
|
}),
|
|
281
227
|
};
|
|
282
|
-
static
|
|
228
|
+
static resolveUpgradeVersion(runtime, flags) {
|
|
229
|
+
const requestedVersion = trimValue(flags.version);
|
|
230
|
+
if (requestedVersion && flags['skip-code-update']) {
|
|
231
|
+
throw new Error('`--version` and `--skip-code-update` cannot be used together. Use `--version` to download a specific upgrade target, or `--skip-code-update` to restart the saved code/image as-is.');
|
|
232
|
+
}
|
|
233
|
+
if (runtime.kind === 'local' && runtime.source === 'local') {
|
|
234
|
+
if (requestedVersion) {
|
|
235
|
+
throw new Error([
|
|
236
|
+
`Env "${runtime.envName}" is managed from an existing local app path.`,
|
|
237
|
+
'This source does not support `nb app upgrade --version` because the CLI does not manage that code checkout.',
|
|
238
|
+
'Update the local app path yourself, then run `nb app upgrade` to restart it.',
|
|
239
|
+
].join('\n'));
|
|
240
|
+
}
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
const savedVersion = readEnvValue(runtime.env, 'downloadVersion');
|
|
244
|
+
const downloadVersion = requestedVersion || savedVersion;
|
|
245
|
+
if (!downloadVersion) {
|
|
246
|
+
throw new Error([
|
|
247
|
+
`Env "${runtime.envName}" does not have a saved \`downloadVersion\`.`,
|
|
248
|
+
'This env cannot be upgraded until a source version is explicit.',
|
|
249
|
+
'Re-run `nb init` or `nb env add` for this env, or pass `--version` to `nb app upgrade`.',
|
|
250
|
+
].join('\n'));
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
downloadVersion,
|
|
254
|
+
persistDownloadVersion: requestedVersion || undefined,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
static buildLocalDownloadArgv(runtime, downloadVersion) {
|
|
283
258
|
const argv = ['-y', '--no-intro', '--source', runtime.source, '--replace'];
|
|
284
|
-
const version = readEnvValue(runtime.env, 'downloadVersion');
|
|
285
|
-
const outputDir = readEnvValue(runtime.env, 'appRootPath');
|
|
286
259
|
const gitUrl = readEnvValue(runtime.env, 'gitUrl');
|
|
287
260
|
const npmRegistry = readEnvValue(runtime.env, 'npmRegistry');
|
|
288
|
-
|
|
289
|
-
argv.push('--version', version);
|
|
290
|
-
}
|
|
291
|
-
if (outputDir) {
|
|
292
|
-
argv.push('--output-dir', outputDir);
|
|
293
|
-
}
|
|
261
|
+
argv.push('--version', downloadVersion, '--output-dir', runtime.projectRoot);
|
|
294
262
|
if (gitUrl) {
|
|
295
263
|
argv.push('--git-url', gitUrl);
|
|
296
264
|
}
|
|
@@ -308,6 +276,24 @@ export default class AppUpgrade extends Command {
|
|
|
308
276
|
}
|
|
309
277
|
return argv;
|
|
310
278
|
}
|
|
279
|
+
static buildDockerDownloadArgv(runtime, plan) {
|
|
280
|
+
const argv = [
|
|
281
|
+
'-y',
|
|
282
|
+
'--no-intro',
|
|
283
|
+
'--source',
|
|
284
|
+
'docker',
|
|
285
|
+
'--replace',
|
|
286
|
+
'--docker-registry',
|
|
287
|
+
plan.dockerRegistry,
|
|
288
|
+
'--version',
|
|
289
|
+
plan.downloadVersion,
|
|
290
|
+
];
|
|
291
|
+
const dockerPlatform = normalizeDockerPlatform(runtime.env.config.dockerPlatform);
|
|
292
|
+
if (dockerPlatform) {
|
|
293
|
+
argv.push('--docker-platform', dockerPlatform);
|
|
294
|
+
}
|
|
295
|
+
return argv;
|
|
296
|
+
}
|
|
311
297
|
static buildLocalStartArgv(runtime) {
|
|
312
298
|
const argv = ['start', '--quickstart'];
|
|
313
299
|
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
@@ -319,43 +305,26 @@ export default class AppUpgrade extends Command {
|
|
|
319
305
|
argv.push('--daemon');
|
|
320
306
|
return argv;
|
|
321
307
|
}
|
|
322
|
-
static
|
|
323
|
-
const
|
|
324
|
-
let inspectedEnv;
|
|
325
|
-
const readContainerEnv = async () => {
|
|
326
|
-
if (!containerExists) {
|
|
327
|
-
return {};
|
|
328
|
-
}
|
|
329
|
-
if (!inspectedEnv) {
|
|
330
|
-
inspectedEnv = await inspectDockerContainerEnv(runtime.containerName);
|
|
331
|
-
}
|
|
332
|
-
return inspectedEnv;
|
|
333
|
-
};
|
|
334
|
-
let dockerRegistry = readEnvValue(runtime.env, 'dockerRegistry');
|
|
335
|
-
let version = readEnvValue(runtime.env, 'downloadVersion');
|
|
336
|
-
if ((!dockerRegistry || !version) && containerExists) {
|
|
337
|
-
const imageRef = await inspectDockerContainerImage(runtime.containerName);
|
|
338
|
-
const parsed = parseDockerImageRef(imageRef);
|
|
339
|
-
dockerRegistry ||= parsed.dockerRegistry;
|
|
340
|
-
version ||= parsed.version;
|
|
341
|
-
}
|
|
342
|
-
const envVars = await readContainerEnv();
|
|
308
|
+
static buildDockerUpgradePlan(runtime, downloadVersion) {
|
|
309
|
+
const dockerRegistry = readEnvValue(runtime.env, 'dockerRegistry') || DEFAULT_DOCKER_REGISTRY;
|
|
343
310
|
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
344
311
|
? ''
|
|
345
312
|
: trimValue(runtime.env.appPort);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
const dbPassword = readEnvValue(runtime.env, 'dbPassword') || trimValue(envVars.DB_PASSWORD);
|
|
313
|
+
const storagePath = readEnvValue(runtime.env, 'storagePath');
|
|
314
|
+
const appKey = readEnvValue(runtime.env, 'appKey');
|
|
315
|
+
const timeZone = readEnvValue(runtime.env, 'timezone');
|
|
316
|
+
const builtinDbConnection = runtime.env.config.builtinDb ? deriveBuiltinDbConnection(runtime) : undefined;
|
|
317
|
+
const dbDialect = builtinDbConnection?.dbDialect || readEnvValue(runtime.env, 'dbDialect');
|
|
318
|
+
const dbHost = builtinDbConnection?.dbHost || readEnvValue(runtime.env, 'dbHost');
|
|
319
|
+
const dbPort = builtinDbConnection?.dbPort || readEnvValue(runtime.env, 'dbPort');
|
|
320
|
+
const dbDatabase = readEnvValue(runtime.env, 'dbDatabase');
|
|
321
|
+
const dbUser = readEnvValue(runtime.env, 'dbUser');
|
|
322
|
+
const dbPassword = readEnvValue(runtime.env, 'dbPassword');
|
|
323
|
+
const networkName = trimValue(runtime.dockerNetworkName || runtime.workspaceName);
|
|
358
324
|
const missing = [];
|
|
325
|
+
if (!networkName) {
|
|
326
|
+
missing.push('docker.network');
|
|
327
|
+
}
|
|
359
328
|
if (!storagePath) {
|
|
360
329
|
missing.push('storagePath');
|
|
361
330
|
}
|
|
@@ -386,9 +355,7 @@ export default class AppUpgrade extends Command {
|
|
|
386
355
|
if (missing.length > 0) {
|
|
387
356
|
throw new Error(`The saved Docker settings for "${runtime.envName}" are incomplete. Missing: ${missing.join(', ')}. Re-run \`nb init\` or \`nb env add\` to refresh this env config.`);
|
|
388
357
|
}
|
|
389
|
-
const
|
|
390
|
-
const resolvedVersion = version || DEFAULT_DOWNLOAD_VERSION;
|
|
391
|
-
const imageRef = `${resolvedRegistry}:${resolvedVersion}`;
|
|
358
|
+
const imageRef = `${dockerRegistry}:${downloadVersion}`;
|
|
392
359
|
const args = [
|
|
393
360
|
'run',
|
|
394
361
|
'-d',
|
|
@@ -397,7 +364,7 @@ export default class AppUpgrade extends Command {
|
|
|
397
364
|
'--restart',
|
|
398
365
|
'always',
|
|
399
366
|
'--network',
|
|
400
|
-
|
|
367
|
+
networkName,
|
|
401
368
|
];
|
|
402
369
|
if (appPort) {
|
|
403
370
|
args.push('-p', `${appPort}:80`);
|
|
@@ -405,7 +372,9 @@ export default class AppUpgrade extends Command {
|
|
|
405
372
|
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}`, imageRef);
|
|
406
373
|
return {
|
|
407
374
|
containerName: runtime.containerName,
|
|
408
|
-
networkName
|
|
375
|
+
networkName,
|
|
376
|
+
dockerRegistry,
|
|
377
|
+
downloadVersion,
|
|
409
378
|
imageRef,
|
|
410
379
|
appPort: appPort || undefined,
|
|
411
380
|
storagePath,
|
|
@@ -420,7 +389,7 @@ export default class AppUpgrade extends Command {
|
|
|
420
389
|
args,
|
|
421
390
|
};
|
|
422
391
|
}
|
|
423
|
-
static async upgradeLocal(runCommand, runtime, flags, commandStdio) {
|
|
392
|
+
static async upgradeLocal(runCommand, runtime, downloadVersion, flags, commandStdio) {
|
|
424
393
|
const displayUrl = formatDisplayUrl(resolveApiBaseUrl(runtime), trimValue(runtime.env.appPort));
|
|
425
394
|
startTask(`Stopping NocoBase for "${runtime.envName}" before upgrade...`);
|
|
426
395
|
try {
|
|
@@ -437,7 +406,7 @@ export default class AppUpgrade extends Command {
|
|
|
437
406
|
if (!flags['skip-code-update'] && (runtime.source === 'npm' || runtime.source === 'git')) {
|
|
438
407
|
startTask(`Refreshing NocoBase files for "${runtime.envName}" from the saved ${runtime.source} source...`);
|
|
439
408
|
try {
|
|
440
|
-
await runCommand('source:download', AppUpgrade.buildLocalDownloadArgv(runtime));
|
|
409
|
+
await runCommand('source:download', AppUpgrade.buildLocalDownloadArgv(runtime, downloadVersion));
|
|
441
410
|
succeedTask(`NocoBase files are up to date for "${runtime.envName}".`);
|
|
442
411
|
}
|
|
443
412
|
catch (error) {
|
|
@@ -468,22 +437,16 @@ export default class AppUpgrade extends Command {
|
|
|
468
437
|
envName: runtime.envName,
|
|
469
438
|
apiBaseUrl: resolveApiBaseUrl(runtime),
|
|
470
439
|
});
|
|
471
|
-
|
|
440
|
+
return displayUrl;
|
|
472
441
|
}
|
|
473
|
-
static async upgradeDocker(runCommand, runtime, flags, commandStdio) {
|
|
474
|
-
const plan = await AppUpgrade.buildDockerUpgradePlan(runtime);
|
|
442
|
+
static async upgradeDocker(runCommand, runtime, downloadVersion, flags, commandStdio) {
|
|
475
443
|
const apiBaseUrl = resolveApiBaseUrl(runtime);
|
|
476
|
-
const displayUrl = formatDisplayUrl(apiBaseUrl, plan.appPort);
|
|
477
444
|
const containerExists = await dockerContainerExists(runtime.containerName);
|
|
478
445
|
if (!flags['skip-code-update']) {
|
|
479
|
-
const
|
|
480
|
-
const dockerPlatform = normalizeDockerPlatform(runtime.env.config.dockerPlatform);
|
|
481
|
-
if (dockerPlatform) {
|
|
482
|
-
argv.push('--docker-platform', dockerPlatform);
|
|
483
|
-
}
|
|
446
|
+
const plan = AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
484
447
|
startTask(`Refreshing the Docker image for "${runtime.envName}"...`);
|
|
485
448
|
try {
|
|
486
|
-
await runCommand('source:download',
|
|
449
|
+
await runCommand('source:download', AppUpgrade.buildDockerDownloadArgv(runtime, plan));
|
|
487
450
|
succeedTask(`Docker image is ready for "${runtime.envName}".`);
|
|
488
451
|
}
|
|
489
452
|
catch (error) {
|
|
@@ -528,6 +491,8 @@ export default class AppUpgrade extends Command {
|
|
|
528
491
|
}
|
|
529
492
|
}
|
|
530
493
|
else {
|
|
494
|
+
const plan = AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
495
|
+
const displayUrl = formatDisplayUrl(apiBaseUrl, plan.appPort);
|
|
531
496
|
startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
|
|
532
497
|
try {
|
|
533
498
|
if (containerExists) {
|
|
@@ -548,13 +513,32 @@ export default class AppUpgrade extends Command {
|
|
|
548
513
|
failTask(`Failed to recreate the Docker app for "${runtime.envName}".`);
|
|
549
514
|
throw new Error(formatDockerStartFailure(runtime.envName, message));
|
|
550
515
|
}
|
|
516
|
+
await waitForAppHealthCheck({
|
|
517
|
+
envName: runtime.envName,
|
|
518
|
+
apiBaseUrl,
|
|
519
|
+
containerName: runtime.containerName,
|
|
520
|
+
});
|
|
521
|
+
return displayUrl;
|
|
551
522
|
}
|
|
552
523
|
await waitForAppHealthCheck({
|
|
553
524
|
envName: runtime.envName,
|
|
554
525
|
apiBaseUrl,
|
|
555
526
|
containerName: runtime.containerName,
|
|
556
527
|
});
|
|
557
|
-
|
|
528
|
+
return formatDisplayUrl(apiBaseUrl, trimValue(runtime.env.appPort));
|
|
529
|
+
}
|
|
530
|
+
static async persistDownloadVersion(runtime, downloadVersion) {
|
|
531
|
+
const { name: _name, ...envConfig } = runtime.env.config;
|
|
532
|
+
try {
|
|
533
|
+
await upsertEnv(runtime.envName, {
|
|
534
|
+
...envConfig,
|
|
535
|
+
downloadVersion,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
540
|
+
throw new Error(`NocoBase was upgraded for "${runtime.envName}", but the CLI could not save \`downloadVersion=${downloadVersion}\`. Details: ${message}`);
|
|
541
|
+
}
|
|
558
542
|
}
|
|
559
543
|
async run() {
|
|
560
544
|
const { flags } = await this.parse(AppUpgrade);
|
|
@@ -579,14 +563,17 @@ export default class AppUpgrade extends Command {
|
|
|
579
563
|
'Use a local or Docker env if you need CLI-managed upgrades right now.',
|
|
580
564
|
].join('\n'));
|
|
581
565
|
}
|
|
566
|
+
announceTargetEnv(runtime.envName);
|
|
582
567
|
try {
|
|
568
|
+
const resolvedVersion = AppUpgrade.resolveUpgradeVersion(runtime, parsed);
|
|
583
569
|
const runCommand = this.config.runCommand.bind(this.config);
|
|
584
|
-
|
|
585
|
-
await AppUpgrade.upgradeDocker(runCommand, runtime, parsed, commandStdio)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
await AppUpgrade.
|
|
570
|
+
const displayUrl = runtime.kind === 'docker'
|
|
571
|
+
? await AppUpgrade.upgradeDocker(runCommand, runtime, resolvedVersion.downloadVersion, parsed, commandStdio)
|
|
572
|
+
: await AppUpgrade.upgradeLocal(runCommand, runtime, resolvedVersion.downloadVersion, parsed, commandStdio);
|
|
573
|
+
if (resolvedVersion.persistDownloadVersion) {
|
|
574
|
+
await AppUpgrade.persistDownloadVersion(runtime, resolvedVersion.persistDownloadVersion);
|
|
589
575
|
}
|
|
576
|
+
succeedTask(`NocoBase has been upgraded for "${runtime.envName}"${displayUrl ? ` at ${displayUrl}` : ''}.`);
|
|
590
577
|
}
|
|
591
578
|
catch (error) {
|
|
592
579
|
this.error(error instanceof Error ? error.message : String(error));
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { Args, Command } from '@oclif/core';
|
|
10
|
+
import { assertSupportedCliConfigKey, deleteCliConfigValue } from '../../lib/cli-config.js';
|
|
11
|
+
export default class ConfigDelete extends Command {
|
|
12
|
+
static summary = 'Delete an explicitly configured CLI setting';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %> license.pkg-url',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> docker.network',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> docker.container-prefix',
|
|
17
|
+
];
|
|
18
|
+
static args = {
|
|
19
|
+
key: Args.string({
|
|
20
|
+
description: 'Configuration key',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { args } = await this.parse(ConfigDelete);
|
|
26
|
+
const key = assertSupportedCliConfigKey(args.key);
|
|
27
|
+
const removed = await deleteCliConfigValue(key);
|
|
28
|
+
this.log(removed ? `Deleted ${key}` : `${key} was not set`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Args, Command } from '@oclif/core';
|
|
10
|
+
import { assertSupportedCliConfigKey, getCliConfigValue } from '../../lib/cli-config.js';
|
|
11
|
+
export default class ConfigGet extends Command {
|
|
12
|
+
static summary = 'Get the effective CLI configuration value for a key';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %> license.pkg-url',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> docker.network',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> docker.container-prefix',
|
|
17
|
+
];
|
|
18
|
+
static args = {
|
|
19
|
+
key: Args.string({
|
|
20
|
+
description: 'Configuration key',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { args } = await this.parse(ConfigGet);
|
|
26
|
+
const key = assertSupportedCliConfigKey(args.key);
|
|
27
|
+
this.log(await getCliConfigValue(key));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -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 Config extends Command {
|
|
11
|
+
static summary = 'Manage CLI configuration defaults';
|
|
12
|
+
async run() {
|
|
13
|
+
await this.parse(Config);
|
|
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 ?? 'config',
|
|
17
|
+
...this.argv,
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from '@oclif/core';
|
|
10
|
+
import { listExplicitCliConfigValues, SUPPORTED_CLI_CONFIG_KEYS } from '../../lib/cli-config.js';
|
|
11
|
+
import { renderTable } from '../../lib/ui.js';
|
|
12
|
+
export default class ConfigList extends Command {
|
|
13
|
+
static summary = 'List explicitly configured CLI settings';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> <%= command.id %>',
|
|
16
|
+
];
|
|
17
|
+
async run() {
|
|
18
|
+
await this.parse(ConfigList);
|
|
19
|
+
const values = await listExplicitCliConfigValues();
|
|
20
|
+
const rows = SUPPORTED_CLI_CONFIG_KEYS
|
|
21
|
+
.filter((key) => Boolean(values[key]))
|
|
22
|
+
.map((key) => [key, values[key] ?? '']);
|
|
23
|
+
if (!rows.length) {
|
|
24
|
+
this.log('No CLI config values are set.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.log(renderTable(['Key', 'Value'], rows));
|
|
28
|
+
}
|
|
29
|
+
}
|