@nocobase/cli 2.1.0-beta.20 → 2.1.0-beta.22

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.
Files changed (74) hide show
  1. package/README.md +32 -50
  2. package/README.zh-CN.md +29 -46
  3. package/bin/run.js +15 -0
  4. package/dist/commands/app/down.js +260 -0
  5. package/dist/commands/app/info.js +140 -0
  6. package/dist/commands/app/logs.js +98 -0
  7. package/dist/commands/app/ps.js +60 -0
  8. package/dist/commands/app/restart.js +75 -0
  9. package/dist/commands/app/shared.js +95 -0
  10. package/dist/commands/app/start.js +252 -0
  11. package/dist/commands/app/stop.js +98 -0
  12. package/dist/commands/app/upgrade.js +595 -0
  13. package/dist/commands/build.js +3 -48
  14. package/dist/commands/db/shared.js +19 -5
  15. package/dist/commands/dev.js +3 -140
  16. package/dist/commands/down.js +3 -184
  17. package/dist/commands/download.js +4 -856
  18. package/dist/commands/env/add.js +33 -48
  19. package/dist/commands/env/auth.js +6 -13
  20. package/dist/commands/env/list.js +10 -15
  21. package/dist/commands/env/remove.js +4 -10
  22. package/dist/commands/env/update.js +7 -13
  23. package/dist/commands/env/use.js +5 -13
  24. package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
  25. package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
  26. package/dist/commands/init.js +262 -63
  27. package/dist/commands/install.js +352 -86
  28. package/dist/commands/logs.js +3 -81
  29. package/dist/commands/plugin/disable.js +64 -0
  30. package/dist/commands/plugin/enable.js +64 -0
  31. package/dist/commands/plugin/list.js +62 -0
  32. package/dist/commands/pm/disable.js +3 -54
  33. package/dist/commands/pm/enable.js +3 -54
  34. package/dist/commands/pm/list.js +3 -45
  35. package/dist/commands/ps.js +3 -107
  36. package/dist/commands/restart.js +3 -65
  37. package/dist/commands/scaffold/migration.js +1 -1
  38. package/dist/commands/scaffold/plugin.js +1 -1
  39. package/dist/commands/self/check.js +1 -1
  40. package/dist/commands/self/update.js +13 -3
  41. package/dist/commands/skills/check.js +11 -5
  42. package/dist/commands/skills/index.js +1 -1
  43. package/dist/commands/skills/install.js +20 -7
  44. package/dist/commands/skills/remove.js +71 -0
  45. package/dist/commands/skills/update.js +27 -7
  46. package/dist/commands/source/build.js +58 -0
  47. package/dist/commands/source/dev.js +157 -0
  48. package/dist/commands/source/download.js +866 -0
  49. package/dist/commands/source/test.js +467 -0
  50. package/dist/commands/start.js +3 -202
  51. package/dist/commands/stop.js +3 -81
  52. package/dist/commands/test.js +3 -457
  53. package/dist/commands/upgrade.js +3 -574
  54. package/dist/help/runtime-help.js +3 -0
  55. package/dist/lib/api-client.js +3 -2
  56. package/dist/lib/app-health.js +126 -0
  57. package/dist/lib/app-managed-resources.js +264 -0
  58. package/dist/lib/app-runtime.js +16 -5
  59. package/dist/lib/auth-store.js +162 -43
  60. package/dist/lib/bootstrap.js +13 -12
  61. package/dist/lib/cli-home.js +38 -6
  62. package/dist/lib/cli-locale.js +15 -1
  63. package/dist/lib/env-auth.js +3 -3
  64. package/dist/lib/env-config.js +80 -0
  65. package/dist/lib/generated-command.js +10 -2
  66. package/dist/lib/http-request.js +49 -0
  67. package/dist/lib/resource-command.js +10 -2
  68. package/dist/lib/runtime-generator.js +1 -1
  69. package/dist/lib/self-manager.js +1 -1
  70. package/dist/lib/skills-manager.js +173 -79
  71. package/dist/lib/startup-update.js +203 -0
  72. package/dist/locale/en-US.json +4 -1
  73. package/dist/locale/zh-CN.json +4 -1
  74. package/package.json +26 -3
@@ -6,578 +6,7 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { Command, Flags } from '@oclif/core';
10
- import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, startDockerContainer, stopDockerContainer, } from '../lib/app-runtime.js';
11
- import { commandOutput, commandSucceeds, run } from '../lib/run-npm.js';
12
- import { failTask, printInfo, startTask, stopTask, succeedTask, updateTask } from '../lib/ui.js';
13
- const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
14
- const DEFAULT_DOWNLOAD_VERSION = 'alpha';
15
- const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
16
- const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
17
- const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
18
- const APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS = 5_000;
19
- function trimValue(value) {
20
- return String(value ?? '').trim();
21
- }
22
- function formatAppUrl(port) {
23
- const value = trimValue(port);
24
- return value ? `http://127.0.0.1:${value}` : undefined;
25
- }
26
- function formatDisplayUrl(apiBaseUrl, appPort) {
27
- const appUrl = formatAppUrl(appPort);
28
- if (appUrl) {
29
- return appUrl;
30
- }
31
- const value = trimValue(apiBaseUrl);
32
- if (!value) {
33
- return undefined;
34
- }
35
- return value.replace(/\/api\/?$/, '');
36
- }
37
- function resolveApiBaseUrl(runtime) {
38
- const baseUrl = trimValue(runtime.env.baseUrl);
39
- if (baseUrl) {
40
- return baseUrl.replace(/\/+$/, '');
41
- }
42
- const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
43
- ? ''
44
- : trimValue(runtime.env.appPort);
45
- return appPort ? `http://127.0.0.1:${appPort}/api` : undefined;
46
- }
47
- function buildHealthCheckUrl(apiBaseUrl) {
48
- return `${apiBaseUrl.replace(/\/+$/, '')}/__health_check`;
49
- }
50
- function dockerRefLabel(source) {
51
- if (source === 'git') {
52
- return 'Git checkout';
53
- }
54
- if (source === 'npm') {
55
- return 'npm app';
56
- }
57
- return 'local app';
58
- }
59
- function formatLocalDownloadFailure(envName, source, message) {
60
- const sourceLabel = source === 'git' ? 'the saved Git checkout' : 'the saved npm app';
61
- return [
62
- `Couldn't refresh NocoBase for "${envName}".`,
63
- `The CLI was not able to update ${sourceLabel} before restarting it.`,
64
- 'Check the saved source settings for this env, then try again.',
65
- `Details: ${message}`,
66
- ].join('\n');
67
- }
68
- function formatLocalStartFailure(envName, source, port, message) {
69
- const sourceLabel = dockerRefLabel(source);
70
- const portHint = trimValue(port) ? ` Expected app port: ${trimValue(port)}.` : '';
71
- const details = trimValue(message) ? ` Details: ${trimValue(message)}` : '';
72
- return [
73
- `Couldn't finish the upgrade for "${envName}".`,
74
- `The CLI updated ${sourceLabel}, but it could not start the upgraded app successfully.`,
75
- `Check the local dependencies, database connection, and saved env settings, then try again.${portHint}${details}`,
76
- ].join('\n');
77
- }
78
- function formatDockerDownloadFailure(envName, message) {
79
- return [
80
- `Couldn't refresh the Docker image for "${envName}".`,
81
- 'The CLI was not able to pull the latest image for this env.',
82
- 'Check the saved Docker source settings and your Docker network access, then try again.',
83
- `Details: ${message}`,
84
- ].join('\n');
85
- }
86
- function formatDockerStartFailure(envName, message) {
87
- return [
88
- `Couldn't finish the upgrade for "${envName}".`,
89
- 'The CLI was not able to start the upgraded Docker app successfully.',
90
- 'Check that the saved Docker image, container settings, and database connection are still valid, then try again.',
91
- `Details: ${message}`,
92
- ].join('\n');
93
- }
94
- function parseDockerImageRef(imageRef) {
95
- const cleaned = trimValue(imageRef).replace(/@.+$/, '');
96
- if (!cleaned) {
97
- return {
98
- dockerRegistry: DEFAULT_DOCKER_REGISTRY,
99
- version: DEFAULT_DOWNLOAD_VERSION,
100
- };
101
- }
102
- const lastSlash = cleaned.lastIndexOf('/');
103
- const lastColon = cleaned.lastIndexOf(':');
104
- if (lastColon > lastSlash) {
105
- return {
106
- dockerRegistry: cleaned.slice(0, lastColon),
107
- version: cleaned.slice(lastColon + 1) || DEFAULT_DOWNLOAD_VERSION,
108
- };
109
- }
110
- return {
111
- dockerRegistry: cleaned,
112
- version: 'latest',
113
- };
114
- }
115
- function normalizeDockerPlatform(value) {
116
- const text = String(value ?? '').trim();
117
- if (!text || text === 'auto') {
118
- return undefined;
119
- }
120
- if (text === 'linux/amd64' || text === 'linux/arm64') {
121
- return text;
122
- }
123
- return undefined;
124
- }
125
- function readEnvValue(env, key) {
126
- return trimValue(env.config[key]);
127
- }
128
- async function sleep(ms) {
129
- await new Promise((resolve) => setTimeout(resolve, ms));
130
- }
131
- async function requestAppHealthCheck(params) {
132
- const controller = new AbortController();
133
- const timeout = setTimeout(() => {
134
- controller.abort();
135
- }, params.requestTimeoutMs ?? APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS);
136
- try {
137
- const response = await (params.fetchImpl ?? fetch)(params.healthCheckUrl, {
138
- method: 'GET',
139
- signal: controller.signal,
140
- });
141
- const text = await response.text().catch(() => '');
142
- const body = text.replace(/\s+/g, ' ').trim() || 'No response yet';
143
- return {
144
- ok: response.ok && text.trim().toLowerCase() === 'ok',
145
- message: `HTTP ${response.status}: ${body}`,
146
- };
147
- }
148
- catch (error) {
149
- if (error instanceof Error && error.name === 'AbortError') {
150
- return {
151
- ok: false,
152
- message: `No response within ${Math.ceil((params.requestTimeoutMs ?? APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS) / 1000)}s`,
153
- };
154
- }
155
- return {
156
- ok: false,
157
- message: error instanceof Error ? error.message : String(error),
158
- };
159
- }
160
- finally {
161
- clearTimeout(timeout);
162
- }
163
- }
164
- async function waitForAppHealthCheck(params) {
165
- if (!params.apiBaseUrl) {
166
- printInfo(`Skipping health check for "${params.envName}" because no local API URL is saved for this env.`);
167
- return;
168
- }
169
- const healthCheckUrl = buildHealthCheckUrl(params.apiBaseUrl);
170
- const startedAt = Date.now();
171
- let lastMessage = 'No response yet';
172
- let spinnerActive = true;
173
- startTask(`Waiting for NocoBase to become ready for "${params.envName}"...`);
174
- try {
175
- while (Date.now() - startedAt < APP_HEALTH_CHECK_TIMEOUT_MS) {
176
- const result = await requestAppHealthCheck({
177
- healthCheckUrl,
178
- fetchImpl: params.fetchImpl,
179
- });
180
- if (result.ok) {
181
- stopTask();
182
- spinnerActive = false;
183
- return;
184
- }
185
- lastMessage = result.message;
186
- const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
187
- updateTask(`Waiting for NocoBase to become ready for "${params.envName}"... (${elapsedSeconds}s elapsed, last status: ${lastMessage})`);
188
- await sleep(APP_HEALTH_CHECK_INTERVAL_MS);
189
- }
190
- }
191
- finally {
192
- if (spinnerActive) {
193
- stopTask();
194
- }
195
- }
196
- const logHint = params.containerName
197
- ? ` You can inspect startup logs with: docker logs ${params.containerName}`
198
- : '';
199
- throw new Error(`The upgraded app for "${params.envName}" did not become ready in time. Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${lastMessage}.${logHint}`);
200
- }
201
- async function dockerContainerExists(containerName) {
202
- return await commandSucceeds('docker', ['container', 'inspect', containerName]);
203
- }
204
- async function ensureDockerNetwork(name) {
205
- const exists = await commandSucceeds('docker', ['network', 'inspect', name]);
206
- if (exists) {
207
- return;
208
- }
209
- await run('docker', ['network', 'create', name], {
210
- errorName: 'docker network create',
211
- stdio: 'ignore',
212
- });
213
- }
214
- async function inspectDockerContainerEnv(name) {
215
- const output = await commandOutput('docker', [
216
- 'inspect',
217
- '--format',
218
- '{{range .Config.Env}}{{println .}}{{end}}',
219
- name,
220
- ], {
221
- errorName: 'docker inspect',
222
- });
223
- const env = {};
224
- for (const line of output.split(/\r?\n/)) {
225
- const index = line.indexOf('=');
226
- if (index <= 0) {
227
- continue;
228
- }
229
- env[line.slice(0, index)] = line.slice(index + 1);
230
- }
231
- return env;
232
- }
233
- async function inspectDockerContainerImage(name) {
234
- return await commandOutput('docker', [
235
- 'inspect',
236
- '--format',
237
- '{{.Config.Image}}',
238
- name,
239
- ], {
240
- errorName: 'docker inspect',
241
- });
242
- }
243
- async function inspectDockerStoragePath(name) {
244
- return await commandOutput('docker', [
245
- 'inspect',
246
- '--format',
247
- `{{range .Mounts}}{{if eq .Destination "${DOCKER_APP_STORAGE_DESTINATION}"}}{{println .Source}}{{end}}{{end}}`,
248
- name,
249
- ], {
250
- errorName: 'docker inspect',
251
- });
252
- }
253
- export default class Upgrade extends Command {
254
- 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.';
255
- static examples = [
256
- '<%= config.bin %> <%= command.id %>',
257
- '<%= config.bin %> <%= command.id %> --env local',
258
- '<%= config.bin %> <%= command.id %> --env local -s',
259
- '<%= config.bin %> <%= command.id %> --env local --verbose',
260
- '<%= config.bin %> <%= command.id %> --env local-docker -s',
261
- ];
262
- static flags = {
263
- env: Flags.string({
264
- char: 'e',
265
- description: 'CLI env name to upgrade. Defaults to the current env when omitted',
266
- }),
267
- 'skip-code-update': Flags.boolean({
268
- char: 's',
269
- description: 'Restart with the saved local code or Docker image without downloading updates first',
270
- required: false,
271
- }),
272
- verbose: Flags.boolean({
273
- description: 'Show raw output from the underlying local or Docker commands',
274
- default: false,
275
- }),
276
- };
277
- static buildLocalDownloadArgv(runtime) {
278
- const argv = ['-y', '--no-intro', '--source', runtime.source, '--replace'];
279
- const version = readEnvValue(runtime.env, 'downloadVersion');
280
- const outputDir = readEnvValue(runtime.env, 'appRootPath');
281
- const gitUrl = readEnvValue(runtime.env, 'gitUrl');
282
- const npmRegistry = readEnvValue(runtime.env, 'npmRegistry');
283
- if (version) {
284
- argv.push('--version', version);
285
- }
286
- if (outputDir) {
287
- argv.push('--output-dir', outputDir);
288
- }
289
- if (gitUrl) {
290
- argv.push('--git-url', gitUrl);
291
- }
292
- if (npmRegistry) {
293
- argv.push('--npm-registry', npmRegistry);
294
- }
295
- if (runtime.env.config.devDependencies === true) {
296
- argv.push('--dev-dependencies');
297
- }
298
- if (runtime.env.config.build === false) {
299
- argv.push('--no-build');
300
- }
301
- if (runtime.env.config.buildDts === true) {
302
- argv.push('--build-dts');
303
- }
304
- return argv;
305
- }
306
- static buildLocalStartArgv(runtime) {
307
- const argv = ['start', '--quickstart'];
308
- const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
309
- ? ''
310
- : trimValue(runtime.env.appPort);
311
- if (appPort) {
312
- argv.push('--port', appPort);
313
- }
314
- argv.push('--daemon');
315
- return argv;
316
- }
317
- static async buildDockerUpgradePlan(runtime) {
318
- const containerExists = await dockerContainerExists(runtime.containerName);
319
- let inspectedEnv;
320
- const readContainerEnv = async () => {
321
- if (!containerExists) {
322
- return {};
323
- }
324
- if (!inspectedEnv) {
325
- inspectedEnv = await inspectDockerContainerEnv(runtime.containerName);
326
- }
327
- return inspectedEnv;
328
- };
329
- let dockerRegistry = readEnvValue(runtime.env, 'dockerRegistry');
330
- let version = readEnvValue(runtime.env, 'downloadVersion');
331
- if ((!dockerRegistry || !version) && containerExists) {
332
- const imageRef = await inspectDockerContainerImage(runtime.containerName);
333
- const parsed = parseDockerImageRef(imageRef);
334
- dockerRegistry ||= parsed.dockerRegistry;
335
- version ||= parsed.version;
336
- }
337
- const envVars = await readContainerEnv();
338
- const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
339
- ? ''
340
- : trimValue(runtime.env.appPort);
341
- let storagePath = readEnvValue(runtime.env, 'storagePath');
342
- if (!storagePath && containerExists) {
343
- storagePath = trimValue(await inspectDockerStoragePath(runtime.containerName));
344
- }
345
- const appKey = readEnvValue(runtime.env, 'appKey') || trimValue(envVars.APP_KEY);
346
- const timeZone = readEnvValue(runtime.env, 'timezone') || trimValue(envVars.TZ);
347
- const dbDialect = readEnvValue(runtime.env, 'dbDialect') || trimValue(envVars.DB_DIALECT);
348
- const dbHost = readEnvValue(runtime.env, 'dbHost') || trimValue(envVars.DB_HOST);
349
- const dbPort = readEnvValue(runtime.env, 'dbPort') || trimValue(envVars.DB_PORT);
350
- const dbDatabase = readEnvValue(runtime.env, 'dbDatabase') || trimValue(envVars.DB_DATABASE);
351
- const dbUser = readEnvValue(runtime.env, 'dbUser') || trimValue(envVars.DB_USER);
352
- const dbPassword = readEnvValue(runtime.env, 'dbPassword') || trimValue(envVars.DB_PASSWORD);
353
- const missing = [];
354
- if (!storagePath) {
355
- missing.push('storagePath');
356
- }
357
- if (!appKey) {
358
- missing.push('appKey');
359
- }
360
- if (!timeZone) {
361
- missing.push('timezone');
362
- }
363
- if (!dbDialect) {
364
- missing.push('dbDialect');
365
- }
366
- if (!dbHost) {
367
- missing.push('dbHost');
368
- }
369
- if (!dbPort) {
370
- missing.push('dbPort');
371
- }
372
- if (!dbDatabase) {
373
- missing.push('dbDatabase');
374
- }
375
- if (!dbUser) {
376
- missing.push('dbUser');
377
- }
378
- if (!dbPassword) {
379
- missing.push('dbPassword');
380
- }
381
- if (missing.length > 0) {
382
- throw new Error(`The saved Docker settings for "${runtime.envName}" are incomplete. Missing: ${missing.join(', ')}. Re-run \`nb install\` or \`nb env add\` to refresh this env config.`);
383
- }
384
- const resolvedRegistry = dockerRegistry || DEFAULT_DOCKER_REGISTRY;
385
- const resolvedVersion = version || DEFAULT_DOWNLOAD_VERSION;
386
- const imageRef = `${resolvedRegistry}:${resolvedVersion}`;
387
- const args = [
388
- 'run',
389
- '-d',
390
- '--name',
391
- runtime.containerName,
392
- '--restart',
393
- 'always',
394
- '--network',
395
- runtime.workspaceName,
396
- ];
397
- if (appPort) {
398
- args.push('-p', `${appPort}:80`);
399
- }
400
- 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);
401
- return {
402
- containerName: runtime.containerName,
403
- networkName: runtime.workspaceName,
404
- imageRef,
405
- appPort: appPort || undefined,
406
- storagePath,
407
- appKey,
408
- timeZone,
409
- dbDialect,
410
- dbHost,
411
- dbPort,
412
- dbDatabase,
413
- dbUser,
414
- dbPassword,
415
- args,
416
- };
417
- }
418
- static async upgradeLocal(runCommand, runtime, flags, commandStdio) {
419
- const displayUrl = formatDisplayUrl(resolveApiBaseUrl(runtime), trimValue(runtime.env.appPort));
420
- startTask(`Stopping NocoBase for "${runtime.envName}" before upgrade...`);
421
- try {
422
- await runLocalNocoBaseCommand(runtime, ['pm2', 'kill'], {
423
- stdio: commandStdio,
424
- });
425
- succeedTask(`Stopped the current NocoBase process for "${runtime.envName}".`);
426
- }
427
- catch (error) {
428
- stopTask();
429
- const message = error instanceof Error ? error.message : String(error);
430
- printInfo(`No running background process was stopped for "${runtime.envName}". Continuing with the upgrade. (${message})`);
431
- }
432
- if (!flags['skip-code-update'] && (runtime.source === 'npm' || runtime.source === 'git')) {
433
- startTask(`Refreshing NocoBase files for "${runtime.envName}" from the saved ${runtime.source} source...`);
434
- try {
435
- await runCommand('download', Upgrade.buildLocalDownloadArgv(runtime));
436
- succeedTask(`NocoBase files are up to date for "${runtime.envName}".`);
437
- }
438
- catch (error) {
439
- const message = error instanceof Error ? error.message : String(error);
440
- failTask(`Failed to refresh NocoBase files for "${runtime.envName}".`);
441
- throw new Error(formatLocalDownloadFailure(runtime.envName, runtime.source, message));
442
- }
443
- }
444
- else if (flags['skip-code-update']) {
445
- printInfo(`Skipping code download for "${runtime.envName}" (--skip-code-update).`);
446
- }
447
- else {
448
- printInfo(`Skipping code download for "${runtime.envName}" because this env is managed from an existing local app path.`);
449
- }
450
- startTask(`Starting upgraded NocoBase for "${runtime.envName}"...`);
451
- try {
452
- await runLocalNocoBaseCommand(runtime, Upgrade.buildLocalStartArgv(runtime), {
453
- stdio: commandStdio,
454
- });
455
- succeedTask(`Upgraded NocoBase is starting for "${runtime.envName}".`);
456
- }
457
- catch (error) {
458
- const message = error instanceof Error ? error.message : String(error);
459
- failTask(`Failed to start upgraded NocoBase for "${runtime.envName}".`);
460
- throw new Error(formatLocalStartFailure(runtime.envName, runtime.source, trimValue(runtime.env.appPort), message));
461
- }
462
- await waitForAppHealthCheck({
463
- envName: runtime.envName,
464
- apiBaseUrl: resolveApiBaseUrl(runtime),
465
- });
466
- succeedTask(`NocoBase has been upgraded for "${runtime.envName}"${displayUrl ? ` at ${displayUrl}` : ''}.`);
467
- }
468
- static async upgradeDocker(runCommand, runtime, flags, commandStdio) {
469
- const plan = await Upgrade.buildDockerUpgradePlan(runtime);
470
- const apiBaseUrl = resolveApiBaseUrl(runtime);
471
- const displayUrl = formatDisplayUrl(apiBaseUrl, plan.appPort);
472
- const containerExists = await dockerContainerExists(runtime.containerName);
473
- if (!flags['skip-code-update']) {
474
- const argv = ['-y', '--no-intro', '--source', 'docker', '--replace', '--docker-registry', parseDockerImageRef(plan.imageRef).dockerRegistry, '--version', parseDockerImageRef(plan.imageRef).version];
475
- const dockerPlatform = normalizeDockerPlatform(runtime.env.config.dockerPlatform);
476
- if (dockerPlatform) {
477
- argv.push('--docker-platform', dockerPlatform);
478
- }
479
- startTask(`Refreshing the Docker image for "${runtime.envName}"...`);
480
- try {
481
- await runCommand('download', argv);
482
- succeedTask(`Docker image is ready for "${runtime.envName}".`);
483
- }
484
- catch (error) {
485
- const message = error instanceof Error ? error.message : String(error);
486
- failTask(`Failed to refresh the Docker image for "${runtime.envName}".`);
487
- throw new Error(formatDockerDownloadFailure(runtime.envName, message));
488
- }
489
- }
490
- else {
491
- printInfo(`Skipping image download for "${runtime.envName}" (--skip-code-update).`);
492
- }
493
- if (containerExists) {
494
- startTask(`Stopping the current Docker app for "${runtime.envName}"...`);
495
- try {
496
- const state = await stopDockerContainer(runtime.containerName, {
497
- stdio: commandStdio,
498
- });
499
- succeedTask(state === 'already-stopped'
500
- ? `The current Docker app was already stopped for "${runtime.envName}".`
501
- : `Stopped the current Docker app for "${runtime.envName}".`);
502
- }
503
- catch (error) {
504
- stopTask();
505
- const message = error instanceof Error ? error.message : String(error);
506
- printInfo(`Could not stop the existing Docker container for "${runtime.envName}" cleanly. Continuing with container recreation. (${message})`);
507
- }
508
- }
509
- if (flags['skip-code-update'] && containerExists) {
510
- startTask(`Starting NocoBase for "${runtime.envName}" with the saved Docker image...`);
511
- try {
512
- const state = await startDockerContainer(runtime.containerName, {
513
- stdio: commandStdio,
514
- });
515
- succeedTask(state === 'already-running'
516
- ? `NocoBase is already running for "${runtime.envName}".`
517
- : `NocoBase is starting for "${runtime.envName}".`);
518
- }
519
- catch (error) {
520
- const message = error instanceof Error ? error.message : String(error);
521
- failTask(`Failed to start the Docker app for "${runtime.envName}".`);
522
- throw new Error(formatDockerStartFailure(runtime.envName, message));
523
- }
524
- }
525
- else {
526
- startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
527
- try {
528
- if (containerExists) {
529
- await run('docker', ['rm', '-f', runtime.containerName], {
530
- errorName: 'docker rm',
531
- stdio: commandStdio,
532
- });
533
- }
534
- await ensureDockerNetwork(plan.networkName);
535
- await run('docker', plan.args, {
536
- errorName: 'docker run',
537
- stdio: commandStdio,
538
- });
539
- succeedTask(`Docker app container is ready for "${runtime.envName}".`);
540
- }
541
- catch (error) {
542
- const message = error instanceof Error ? error.message : String(error);
543
- failTask(`Failed to recreate the Docker app for "${runtime.envName}".`);
544
- throw new Error(formatDockerStartFailure(runtime.envName, message));
545
- }
546
- }
547
- await waitForAppHealthCheck({
548
- envName: runtime.envName,
549
- apiBaseUrl,
550
- containerName: runtime.containerName,
551
- });
552
- succeedTask(`NocoBase has been upgraded for "${runtime.envName}"${displayUrl ? ` at ${displayUrl}` : ''}.`);
553
- }
554
- async run() {
555
- const { flags } = await this.parse(Upgrade);
556
- const parsed = flags;
557
- const requestedEnv = parsed.env?.trim() || undefined;
558
- const commandStdio = parsed.verbose ? 'inherit' : 'ignore';
559
- const runtime = await resolveManagedAppRuntime(requestedEnv);
560
- if (!runtime) {
561
- this.error(formatMissingManagedAppEnvMessage(requestedEnv));
562
- }
563
- if (runtime.kind === 'remote') {
564
- this.error([
565
- `Can't upgrade "${runtime.envName}" from this machine.`,
566
- 'This env only has an API connection, so there is no saved local app or Docker runtime to upgrade here.',
567
- 'If you want a local NocoBase AI environment that the CLI can upgrade, run `nb init` first.',
568
- ].join('\n'));
569
- }
570
- try {
571
- const runCommand = this.config.runCommand.bind(this.config);
572
- if (runtime.kind === 'docker') {
573
- await Upgrade.upgradeDocker(runCommand, runtime, parsed, commandStdio);
574
- }
575
- else {
576
- await Upgrade.upgradeLocal(runCommand, runtime, parsed, commandStdio);
577
- }
578
- }
579
- catch (error) {
580
- this.error(error instanceof Error ? error.message : String(error));
581
- }
582
- }
9
+ import AppUpgrade from './app/upgrade.js';
10
+ export default class Upgrade extends AppUpgrade {
11
+ static hidden = true;
583
12
  }
@@ -17,4 +17,7 @@ export default class RuntimeHelp extends Help {
17
17
  get sortedCommands() {
18
18
  return super.sortedCommands.filter((command) => !isTopicIndexCommand(command.id, this.config.topics));
19
19
  }
20
+ get sortedTopics() {
21
+ return super.sortedTopics.filter((topic) => !topic.hidden);
22
+ }
20
23
  }
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { promises as fs } from 'node:fs';
10
10
  import { resolveServerRequestTarget } from './env-auth.js';
11
+ import { fetchWithPreservedAuthRedirect } from './http-request.js';
11
12
  const CLI_REQUEST_SOURCE_HEADER = 'x-request-source';
12
13
  const CLI_REQUEST_SOURCE_VALUE = 'cli';
13
14
  function stripUtf8Bom(text) {
@@ -195,7 +196,7 @@ export async function executeApiRequest(options) {
195
196
  }
196
197
  const url = new URL(`${normalizeBaseUrl(baseUrl)}${requestPath}`);
197
198
  query.forEach((value, key) => url.searchParams.append(key, value));
198
- const response = await fetch(url, {
199
+ const response = await fetchWithPreservedAuthRedirect(url.toString(), {
199
200
  method: options.operation.method.toUpperCase(),
200
201
  headers,
201
202
  body: body === undefined ? undefined : JSON.stringify(body),
@@ -234,7 +235,7 @@ export async function executeRawApiRequest(options) {
234
235
  }
235
236
  url.searchParams.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
236
237
  }
237
- const response = await fetch(url, {
238
+ const response = await fetchWithPreservedAuthRedirect(url.toString(), {
238
239
  method: options.method.toUpperCase(),
239
240
  headers,
240
241
  body: options.body === undefined ? undefined : JSON.stringify(options.body),