@nocobase/cli 2.1.0-alpha.26 → 2.1.0-alpha.27
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/upgrade.js +112 -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 +230 -0
- package/dist/commands/db/shared.js +1 -1
- 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 +411 -0
- package/dist/commands/license/status.js +50 -0
- package/dist/lib/api-client.js +74 -3
- package/dist/lib/app-runtime.js +26 -10
- package/dist/lib/auth-store.js +29 -66
- package/dist/lib/build-config.js +8 -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/generated-command.js +23 -3
- package/dist/lib/plugin-storage.js +127 -0
- package/dist/lib/prompt-validators.js +4 -4
- package/dist/lib/runtime-generator.js +89 -10
- package/dist/lib/self-manager.js +57 -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) {
|
|
@@ -7,12 +7,12 @@
|
|
|
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 { commandSucceeds, run } from '../../lib/run-npm.js';
|
|
13
14
|
import { failTask, printInfo, startTask, stopTask, succeedTask, updateTask } from '../../lib/ui.js';
|
|
14
15
|
const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
|
|
15
|
-
const DEFAULT_DOWNLOAD_VERSION = 'alpha';
|
|
16
16
|
const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
|
|
17
17
|
const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
|
|
18
18
|
const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
|
|
@@ -92,27 +92,6 @@ function formatDockerStartFailure(envName, message) {
|
|
|
92
92
|
`Details: ${message}`,
|
|
93
93
|
].join('\n');
|
|
94
94
|
}
|
|
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
95
|
function normalizeDockerPlatform(value) {
|
|
117
96
|
const text = String(value ?? '').trim();
|
|
118
97
|
if (!text || text === 'auto') {
|
|
@@ -215,52 +194,14 @@ async function ensureDockerNetwork(name) {
|
|
|
215
194
|
stdio: 'ignore',
|
|
216
195
|
});
|
|
217
196
|
}
|
|
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
197
|
export default class AppUpgrade extends Command {
|
|
258
198
|
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.';
|
|
199
|
+
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
200
|
static examples = [
|
|
261
201
|
'<%= config.bin %> <%= command.id %>',
|
|
262
202
|
'<%= config.bin %> <%= command.id %> --env local',
|
|
263
203
|
'<%= config.bin %> <%= command.id %> --env local -s',
|
|
204
|
+
'<%= config.bin %> <%= command.id %> --env local --version beta',
|
|
264
205
|
'<%= config.bin %> <%= command.id %> --env local --verbose',
|
|
265
206
|
'<%= config.bin %> <%= command.id %> --env local-docker -s',
|
|
266
207
|
];
|
|
@@ -274,23 +215,49 @@ export default class AppUpgrade extends Command {
|
|
|
274
215
|
description: 'Restart with the saved local code or Docker image without downloading updates first',
|
|
275
216
|
required: false,
|
|
276
217
|
}),
|
|
218
|
+
version: Flags.string({
|
|
219
|
+
description: 'Override the saved downloadVersion for this upgrade. When the upgrade succeeds, the new version is saved back to the env config.',
|
|
220
|
+
required: false,
|
|
221
|
+
}),
|
|
277
222
|
verbose: Flags.boolean({
|
|
278
223
|
description: 'Show raw output from the underlying local or Docker commands',
|
|
279
224
|
default: false,
|
|
280
225
|
}),
|
|
281
226
|
};
|
|
282
|
-
static
|
|
227
|
+
static resolveUpgradeVersion(runtime, flags) {
|
|
228
|
+
const requestedVersion = trimValue(flags.version);
|
|
229
|
+
if (requestedVersion && flags['skip-code-update']) {
|
|
230
|
+
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.');
|
|
231
|
+
}
|
|
232
|
+
if (runtime.kind === 'local' && runtime.source === 'local') {
|
|
233
|
+
if (requestedVersion) {
|
|
234
|
+
throw new Error([
|
|
235
|
+
`Env "${runtime.envName}" is managed from an existing local app path.`,
|
|
236
|
+
'This source does not support `nb app upgrade --version` because the CLI does not manage that code checkout.',
|
|
237
|
+
'Update the local app path yourself, then run `nb app upgrade` to restart it.',
|
|
238
|
+
].join('\n'));
|
|
239
|
+
}
|
|
240
|
+
return {};
|
|
241
|
+
}
|
|
242
|
+
const savedVersion = readEnvValue(runtime.env, 'downloadVersion');
|
|
243
|
+
const downloadVersion = requestedVersion || savedVersion;
|
|
244
|
+
if (!downloadVersion) {
|
|
245
|
+
throw new Error([
|
|
246
|
+
`Env "${runtime.envName}" does not have a saved \`downloadVersion\`.`,
|
|
247
|
+
'This env cannot be upgraded until a source version is explicit.',
|
|
248
|
+
'Re-run `nb init` or `nb env add` for this env, or pass `--version` to `nb app upgrade`.',
|
|
249
|
+
].join('\n'));
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
downloadVersion,
|
|
253
|
+
persistDownloadVersion: requestedVersion || undefined,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
static buildLocalDownloadArgv(runtime, downloadVersion) {
|
|
283
257
|
const argv = ['-y', '--no-intro', '--source', runtime.source, '--replace'];
|
|
284
|
-
const version = readEnvValue(runtime.env, 'downloadVersion');
|
|
285
|
-
const outputDir = readEnvValue(runtime.env, 'appRootPath');
|
|
286
258
|
const gitUrl = readEnvValue(runtime.env, 'gitUrl');
|
|
287
259
|
const npmRegistry = readEnvValue(runtime.env, 'npmRegistry');
|
|
288
|
-
|
|
289
|
-
argv.push('--version', version);
|
|
290
|
-
}
|
|
291
|
-
if (outputDir) {
|
|
292
|
-
argv.push('--output-dir', outputDir);
|
|
293
|
-
}
|
|
260
|
+
argv.push('--version', downloadVersion, '--output-dir', runtime.projectRoot);
|
|
294
261
|
if (gitUrl) {
|
|
295
262
|
argv.push('--git-url', gitUrl);
|
|
296
263
|
}
|
|
@@ -308,6 +275,24 @@ export default class AppUpgrade extends Command {
|
|
|
308
275
|
}
|
|
309
276
|
return argv;
|
|
310
277
|
}
|
|
278
|
+
static buildDockerDownloadArgv(runtime, plan) {
|
|
279
|
+
const argv = [
|
|
280
|
+
'-y',
|
|
281
|
+
'--no-intro',
|
|
282
|
+
'--source',
|
|
283
|
+
'docker',
|
|
284
|
+
'--replace',
|
|
285
|
+
'--docker-registry',
|
|
286
|
+
plan.dockerRegistry,
|
|
287
|
+
'--version',
|
|
288
|
+
plan.downloadVersion,
|
|
289
|
+
];
|
|
290
|
+
const dockerPlatform = normalizeDockerPlatform(runtime.env.config.dockerPlatform);
|
|
291
|
+
if (dockerPlatform) {
|
|
292
|
+
argv.push('--docker-platform', dockerPlatform);
|
|
293
|
+
}
|
|
294
|
+
return argv;
|
|
295
|
+
}
|
|
311
296
|
static buildLocalStartArgv(runtime) {
|
|
312
297
|
const argv = ['start', '--quickstart'];
|
|
313
298
|
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
@@ -319,43 +304,25 @@ export default class AppUpgrade extends Command {
|
|
|
319
304
|
argv.push('--daemon');
|
|
320
305
|
return argv;
|
|
321
306
|
}
|
|
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();
|
|
307
|
+
static buildDockerUpgradePlan(runtime, downloadVersion) {
|
|
308
|
+
const dockerRegistry = readEnvValue(runtime.env, 'dockerRegistry') || DEFAULT_DOCKER_REGISTRY;
|
|
343
309
|
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
344
310
|
? ''
|
|
345
311
|
: trimValue(runtime.env.appPort);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const dbUser = readEnvValue(runtime.env, 'dbUser') || trimValue(envVars.DB_USER);
|
|
357
|
-
const dbPassword = readEnvValue(runtime.env, 'dbPassword') || trimValue(envVars.DB_PASSWORD);
|
|
312
|
+
const storagePath = readEnvValue(runtime.env, 'storagePath');
|
|
313
|
+
const appKey = readEnvValue(runtime.env, 'appKey');
|
|
314
|
+
const timeZone = readEnvValue(runtime.env, 'timezone');
|
|
315
|
+
const dbDialect = readEnvValue(runtime.env, 'dbDialect');
|
|
316
|
+
const dbHost = readEnvValue(runtime.env, 'dbHost');
|
|
317
|
+
const dbPort = readEnvValue(runtime.env, 'dbPort');
|
|
318
|
+
const dbDatabase = readEnvValue(runtime.env, 'dbDatabase');
|
|
319
|
+
const dbUser = readEnvValue(runtime.env, 'dbUser');
|
|
320
|
+
const dbPassword = readEnvValue(runtime.env, 'dbPassword');
|
|
321
|
+
const networkName = trimValue(runtime.dockerNetworkName || runtime.workspaceName);
|
|
358
322
|
const missing = [];
|
|
323
|
+
if (!networkName) {
|
|
324
|
+
missing.push('docker.network');
|
|
325
|
+
}
|
|
359
326
|
if (!storagePath) {
|
|
360
327
|
missing.push('storagePath');
|
|
361
328
|
}
|
|
@@ -386,9 +353,7 @@ export default class AppUpgrade extends Command {
|
|
|
386
353
|
if (missing.length > 0) {
|
|
387
354
|
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
355
|
}
|
|
389
|
-
const
|
|
390
|
-
const resolvedVersion = version || DEFAULT_DOWNLOAD_VERSION;
|
|
391
|
-
const imageRef = `${resolvedRegistry}:${resolvedVersion}`;
|
|
356
|
+
const imageRef = `${dockerRegistry}:${downloadVersion}`;
|
|
392
357
|
const args = [
|
|
393
358
|
'run',
|
|
394
359
|
'-d',
|
|
@@ -397,7 +362,7 @@ export default class AppUpgrade extends Command {
|
|
|
397
362
|
'--restart',
|
|
398
363
|
'always',
|
|
399
364
|
'--network',
|
|
400
|
-
|
|
365
|
+
networkName,
|
|
401
366
|
];
|
|
402
367
|
if (appPort) {
|
|
403
368
|
args.push('-p', `${appPort}:80`);
|
|
@@ -405,7 +370,9 @@ export default class AppUpgrade extends Command {
|
|
|
405
370
|
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
371
|
return {
|
|
407
372
|
containerName: runtime.containerName,
|
|
408
|
-
networkName
|
|
373
|
+
networkName,
|
|
374
|
+
dockerRegistry,
|
|
375
|
+
downloadVersion,
|
|
409
376
|
imageRef,
|
|
410
377
|
appPort: appPort || undefined,
|
|
411
378
|
storagePath,
|
|
@@ -420,7 +387,7 @@ export default class AppUpgrade extends Command {
|
|
|
420
387
|
args,
|
|
421
388
|
};
|
|
422
389
|
}
|
|
423
|
-
static async upgradeLocal(runCommand, runtime, flags, commandStdio) {
|
|
390
|
+
static async upgradeLocal(runCommand, runtime, downloadVersion, flags, commandStdio) {
|
|
424
391
|
const displayUrl = formatDisplayUrl(resolveApiBaseUrl(runtime), trimValue(runtime.env.appPort));
|
|
425
392
|
startTask(`Stopping NocoBase for "${runtime.envName}" before upgrade...`);
|
|
426
393
|
try {
|
|
@@ -437,7 +404,7 @@ export default class AppUpgrade extends Command {
|
|
|
437
404
|
if (!flags['skip-code-update'] && (runtime.source === 'npm' || runtime.source === 'git')) {
|
|
438
405
|
startTask(`Refreshing NocoBase files for "${runtime.envName}" from the saved ${runtime.source} source...`);
|
|
439
406
|
try {
|
|
440
|
-
await runCommand('source:download', AppUpgrade.buildLocalDownloadArgv(runtime));
|
|
407
|
+
await runCommand('source:download', AppUpgrade.buildLocalDownloadArgv(runtime, downloadVersion));
|
|
441
408
|
succeedTask(`NocoBase files are up to date for "${runtime.envName}".`);
|
|
442
409
|
}
|
|
443
410
|
catch (error) {
|
|
@@ -468,22 +435,16 @@ export default class AppUpgrade extends Command {
|
|
|
468
435
|
envName: runtime.envName,
|
|
469
436
|
apiBaseUrl: resolveApiBaseUrl(runtime),
|
|
470
437
|
});
|
|
471
|
-
|
|
438
|
+
return displayUrl;
|
|
472
439
|
}
|
|
473
|
-
static async upgradeDocker(runCommand, runtime, flags, commandStdio) {
|
|
474
|
-
const plan = await AppUpgrade.buildDockerUpgradePlan(runtime);
|
|
440
|
+
static async upgradeDocker(runCommand, runtime, downloadVersion, flags, commandStdio) {
|
|
475
441
|
const apiBaseUrl = resolveApiBaseUrl(runtime);
|
|
476
|
-
const displayUrl = formatDisplayUrl(apiBaseUrl, plan.appPort);
|
|
477
442
|
const containerExists = await dockerContainerExists(runtime.containerName);
|
|
478
443
|
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
|
-
}
|
|
444
|
+
const plan = AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
484
445
|
startTask(`Refreshing the Docker image for "${runtime.envName}"...`);
|
|
485
446
|
try {
|
|
486
|
-
await runCommand('source:download',
|
|
447
|
+
await runCommand('source:download', AppUpgrade.buildDockerDownloadArgv(runtime, plan));
|
|
487
448
|
succeedTask(`Docker image is ready for "${runtime.envName}".`);
|
|
488
449
|
}
|
|
489
450
|
catch (error) {
|
|
@@ -528,6 +489,8 @@ export default class AppUpgrade extends Command {
|
|
|
528
489
|
}
|
|
529
490
|
}
|
|
530
491
|
else {
|
|
492
|
+
const plan = AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
493
|
+
const displayUrl = formatDisplayUrl(apiBaseUrl, plan.appPort);
|
|
531
494
|
startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
|
|
532
495
|
try {
|
|
533
496
|
if (containerExists) {
|
|
@@ -548,13 +511,32 @@ export default class AppUpgrade extends Command {
|
|
|
548
511
|
failTask(`Failed to recreate the Docker app for "${runtime.envName}".`);
|
|
549
512
|
throw new Error(formatDockerStartFailure(runtime.envName, message));
|
|
550
513
|
}
|
|
514
|
+
await waitForAppHealthCheck({
|
|
515
|
+
envName: runtime.envName,
|
|
516
|
+
apiBaseUrl,
|
|
517
|
+
containerName: runtime.containerName,
|
|
518
|
+
});
|
|
519
|
+
return displayUrl;
|
|
551
520
|
}
|
|
552
521
|
await waitForAppHealthCheck({
|
|
553
522
|
envName: runtime.envName,
|
|
554
523
|
apiBaseUrl,
|
|
555
524
|
containerName: runtime.containerName,
|
|
556
525
|
});
|
|
557
|
-
|
|
526
|
+
return formatDisplayUrl(apiBaseUrl, trimValue(runtime.env.appPort));
|
|
527
|
+
}
|
|
528
|
+
static async persistDownloadVersion(runtime, downloadVersion) {
|
|
529
|
+
const { name: _name, ...envConfig } = runtime.env.config;
|
|
530
|
+
try {
|
|
531
|
+
await upsertEnv(runtime.envName, {
|
|
532
|
+
...envConfig,
|
|
533
|
+
downloadVersion,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
538
|
+
throw new Error(`NocoBase was upgraded for "${runtime.envName}", but the CLI could not save \`downloadVersion=${downloadVersion}\`. Details: ${message}`);
|
|
539
|
+
}
|
|
558
540
|
}
|
|
559
541
|
async run() {
|
|
560
542
|
const { flags } = await this.parse(AppUpgrade);
|
|
@@ -580,13 +562,15 @@ export default class AppUpgrade extends Command {
|
|
|
580
562
|
].join('\n'));
|
|
581
563
|
}
|
|
582
564
|
try {
|
|
565
|
+
const resolvedVersion = AppUpgrade.resolveUpgradeVersion(runtime, parsed);
|
|
583
566
|
const runCommand = this.config.runCommand.bind(this.config);
|
|
584
|
-
|
|
585
|
-
await AppUpgrade.upgradeDocker(runCommand, runtime, parsed, commandStdio)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
await AppUpgrade.
|
|
567
|
+
const displayUrl = runtime.kind === 'docker'
|
|
568
|
+
? await AppUpgrade.upgradeDocker(runCommand, runtime, resolvedVersion.downloadVersion, parsed, commandStdio)
|
|
569
|
+
: await AppUpgrade.upgradeLocal(runCommand, runtime, resolvedVersion.downloadVersion, parsed, commandStdio);
|
|
570
|
+
if (resolvedVersion.persistDownloadVersion) {
|
|
571
|
+
await AppUpgrade.persistDownloadVersion(runtime, resolvedVersion.persistDownloadVersion);
|
|
589
572
|
}
|
|
573
|
+
succeedTask(`NocoBase has been upgraded for "${runtime.envName}"${displayUrl ? ` at ${displayUrl}` : ''}.`);
|
|
590
574
|
}
|
|
591
575
|
catch (error) {
|
|
592
576
|
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
|
+
}
|