@nocobase/cli 2.1.0-alpha.26 → 2.1.0-alpha.28
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 +24 -0
- package/README.zh-CN.md +4 -0
- package/dist/commands/app/down.js +2 -3
- package/dist/commands/app/logs.js +2 -2
- package/dist/commands/app/upgrade.js +114 -128
- 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 +238 -0
- package/dist/commands/db/logs.js +2 -2
- package/dist/commands/db/shared.js +6 -5
- package/dist/commands/env/info.js +6 -2
- package/dist/commands/env/shared.js +1 -1
- package/dist/commands/init.js +0 -1
- package/dist/commands/install.js +87 -35
- package/dist/commands/license/activate.js +357 -0
- package/dist/commands/license/env.js +94 -0
- package/dist/commands/license/generate-id.js +107 -0
- package/dist/commands/license/id.js +52 -0
- package/dist/commands/license/index.js +20 -0
- package/dist/commands/license/plugins/clean.js +98 -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 +267 -0
- package/dist/commands/license/shared.js +414 -0
- package/dist/commands/license/status.js +50 -0
- 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/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/db-connection-check.js +178 -0
- package/dist/lib/env-config.js +7 -0
- package/dist/lib/generated-command.js +23 -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 +85 -7
- package/dist/locale/en-US.json +16 -13
- package/dist/locale/zh-CN.json +16 -13
- package/nocobase-ctl.config.json +82 -0
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -60,12 +60,16 @@ nb init --ui
|
|
|
60
60
|
When creating a new app, it can also install NocoBase AI coding skills
|
|
61
61
|
(`nocobase/skills`) globally.
|
|
62
62
|
|
|
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 or update them.
|
|
65
|
+
|
|
63
66
|
### Non-Interactive Setup
|
|
64
67
|
|
|
65
68
|
When prompts are skipped, an app/env name is required:
|
|
66
69
|
|
|
67
70
|
```bash
|
|
68
71
|
nb init --env app1 --yes
|
|
72
|
+
nb init --env app1 --yes --skip-skills
|
|
69
73
|
```
|
|
70
74
|
|
|
71
75
|
Install with Docker:
|
|
@@ -111,6 +115,7 @@ If `nb init` was interrupted after the env config had already been saved, you ca
|
|
|
111
115
|
|
|
112
116
|
```bash
|
|
113
117
|
nb init --env app1 --resume
|
|
118
|
+
nb init --env app1 --resume --skip-skills
|
|
114
119
|
```
|
|
115
120
|
|
|
116
121
|
`--resume` reuses the saved workspace env config for app, source, database, and env connection settings. In interactive mode, it only asks for any missing setup-only values.
|
|
@@ -331,6 +336,23 @@ nb api resource get --resource users --filter-by-tk 1 -e app1
|
|
|
331
336
|
nb api resource create --resource users --values '{"nickname":"Ada"}' -e app1
|
|
332
337
|
```
|
|
333
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
|
+
|
|
334
356
|
Use `-j, --json-output` to print raw JSON when available:
|
|
335
357
|
|
|
336
358
|
```bash
|
|
@@ -345,9 +367,11 @@ Available API command topics:
|
|
|
345
367
|
| `nb api api-keys` | Manage API keys for HTTP API access. |
|
|
346
368
|
| `nb api app` | Manage application resources. |
|
|
347
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. |
|
|
348
371
|
| `nb api data-modeling` | Manage data sources, collections, and database modeling resources. |
|
|
349
372
|
| `nb api file-manager` | Manage file storage services, file collections, and attachment fields. |
|
|
350
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. |
|
|
351
375
|
| `nb api pm` | Manage plugins through API commands. |
|
|
352
376
|
| `nb api resource` | Work with generic collection resources. |
|
|
353
377
|
| `nb api system-settings` | Adjust system title, logo, language, and other global settings. |
|
package/README.zh-CN.md
CHANGED
|
@@ -55,12 +55,15 @@ nb init --ui
|
|
|
55
55
|
|
|
56
56
|
`nb init` 可以连接已有的 NocoBase 应用,也可以安装一个新的 NocoBase 应用。创建新应用时,还可以全局安装 NocoBase AI coding skills (`nocobase/skills`)。
|
|
57
57
|
|
|
58
|
+
如果已经自行管理 skills,或在 CI、离线环境中运行,不希望 `nb init` 安装或更新 skills,可以传入 `--skip-skills`。
|
|
59
|
+
|
|
58
60
|
### 非交互式初始化
|
|
59
61
|
|
|
60
62
|
跳过交互提示时,必须提供 app/env name:
|
|
61
63
|
|
|
62
64
|
```bash
|
|
63
65
|
nb init --env app1 --yes
|
|
66
|
+
nb init --env app1 --yes --skip-skills
|
|
64
67
|
```
|
|
65
68
|
|
|
66
69
|
使用 Docker 安装:
|
|
@@ -106,6 +109,7 @@ nb init --env app1 --yes --source git --version fix/cli-v2
|
|
|
106
109
|
|
|
107
110
|
```bash
|
|
108
111
|
nb init --env app1 --resume
|
|
112
|
+
nb init --env app1 --resume --skip-skills
|
|
109
113
|
```
|
|
110
114
|
|
|
111
115
|
`--resume` 会复用工作区里已保存的 env config,包括应用、source、数据库和 env 连接相关配置。在交互模式下,只会继续补齐缺失的初始化参数。
|
|
@@ -92,11 +92,10 @@ 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
100
|
async function confirmDownAll(envName, yes) {
|
|
102
101
|
if (yes) {
|
|
@@ -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}".`);
|
|
@@ -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 { deriveBuiltinDbConnection } from '../../lib/builtin-db.js';
|
|
14
|
+
import { commandSucceeds, run } from '../../lib/run-npm.js';
|
|
13
15
|
import { 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);
|
|
@@ -580,13 +564,15 @@ export default class AppUpgrade extends Command {
|
|
|
580
564
|
].join('\n'));
|
|
581
565
|
}
|
|
582
566
|
try {
|
|
567
|
+
const resolvedVersion = AppUpgrade.resolveUpgradeVersion(runtime, parsed);
|
|
583
568
|
const runCommand = this.config.runCommand.bind(this.config);
|
|
584
|
-
|
|
585
|
-
await AppUpgrade.upgradeDocker(runCommand, runtime, parsed, commandStdio)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
await AppUpgrade.
|
|
569
|
+
const displayUrl = runtime.kind === 'docker'
|
|
570
|
+
? await AppUpgrade.upgradeDocker(runCommand, runtime, resolvedVersion.downloadVersion, parsed, commandStdio)
|
|
571
|
+
: await AppUpgrade.upgradeLocal(runCommand, runtime, resolvedVersion.downloadVersion, parsed, commandStdio);
|
|
572
|
+
if (resolvedVersion.persistDownloadVersion) {
|
|
573
|
+
await AppUpgrade.persistDownloadVersion(runtime, resolvedVersion.persistDownloadVersion);
|
|
589
574
|
}
|
|
575
|
+
succeedTask(`NocoBase has been upgraded for "${runtime.envName}"${displayUrl ? ` at ${displayUrl}` : ''}.`);
|
|
590
576
|
}
|
|
591
577
|
catch (error) {
|
|
592
578
|
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
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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, setCliConfigValue } from '../../lib/cli-config.js';
|
|
11
|
+
export default class ConfigSet extends Command {
|
|
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.';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> <%= command.id %> license.pkg-url https://pkg.nocobase.com/',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> docker.network nocobase',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> docker.container-prefix nb',
|
|
18
|
+
];
|
|
19
|
+
static args = {
|
|
20
|
+
key: Args.string({
|
|
21
|
+
description: 'Configuration key',
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
value: Args.string({
|
|
25
|
+
description: 'Configuration value',
|
|
26
|
+
required: true,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const { args } = await this.parse(ConfigSet);
|
|
31
|
+
const key = assertSupportedCliConfigKey(args.key);
|
|
32
|
+
const value = await setCliConfigValue(key, args.value);
|
|
33
|
+
this.log(`${key}=${value}`);
|
|
34
|
+
}
|
|
35
|
+
}
|