@nocobase/cli 2.1.0-beta.41 → 2.1.0-beta.42-test.1
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/bin/run.js +6 -35
- package/dist/commands/app/restart.js +12 -5
- package/dist/commands/app/start.js +1 -0
- package/dist/commands/app/upgrade.js +2 -2
- package/dist/commands/config/delete.js +4 -1
- package/dist/commands/config/get.js +1 -0
- package/dist/commands/config/set.js +6 -1
- package/dist/commands/db/ps.js +5 -18
- package/dist/commands/env/auth.js +1 -1
- package/dist/commands/env/list.js +4 -5
- package/dist/commands/env/status.js +10 -7
- package/dist/commands/install.js +33 -25
- package/dist/commands/self/update.js +58 -1
- package/dist/commands/source/dev.js +5 -5
- package/dist/lib/app-health.js +34 -27
- package/dist/lib/app-managed-resources.js +1 -1
- package/dist/lib/app-runtime.js +2 -2
- package/dist/lib/auth-store.js +9 -0
- package/dist/lib/backup.js +3 -3
- package/dist/lib/bootstrap.js +14 -8
- package/dist/lib/cli-config.js +38 -0
- package/dist/lib/cli-entry-error.js +44 -0
- package/dist/lib/docker-log-stream.js +45 -0
- package/dist/lib/env-auth.js +21 -13
- package/dist/lib/source-publish.js +4 -1
- package/dist/lib/startup-update.js +74 -50
- package/dist/locale/en-US.json +6 -0
- package/dist/locale/zh-CN.json +6 -0
- package/package.json +2 -3
- package/LICENSE.txt +0 -107
package/bin/run.js
CHANGED
|
@@ -69,49 +69,20 @@ const { ensureRuntimeFromArgv } = await import(pathToFileURL(bootstrapPath).href
|
|
|
69
69
|
const startupUpdatePath = isDev
|
|
70
70
|
? path.join(root, 'src/lib/startup-update.ts')
|
|
71
71
|
: path.join(root, 'dist/lib/startup-update.js');
|
|
72
|
-
const {
|
|
72
|
+
const { maybeRunStartupUpdate } = await import(pathToFileURL(startupUpdatePath).href);
|
|
73
|
+
const cliEntryErrorPath = isDev
|
|
74
|
+
? path.join(root, 'src/lib/cli-entry-error.ts')
|
|
75
|
+
: path.join(root, 'dist/lib/cli-entry-error.js');
|
|
76
|
+
const { formatCliEntryError } = await import(pathToFileURL(cliEntryErrorPath).href);
|
|
73
77
|
const { flush, run, settings } = await import('@oclif/core');
|
|
74
78
|
|
|
75
79
|
if (isDev) {
|
|
76
80
|
settings.debug = true;
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
function getCommandToken(argv) {
|
|
80
|
-
const tokens = [];
|
|
81
|
-
|
|
82
|
-
for (const token of argv) {
|
|
83
|
-
if (!token || token.startsWith('-')) {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
tokens.push(token);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (tokens[0] === 'api') {
|
|
91
|
-
return tokens[1] ?? tokens[0];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return tokens[0];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function formatCliEntryError(error, argv) {
|
|
98
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
-
const missingCommandMatch = message.match(/^Command (.+) not found\.$/);
|
|
100
|
-
if (missingCommandMatch) {
|
|
101
|
-
const commandToken = getCommandToken(argv) ?? missingCommandMatch[1];
|
|
102
|
-
return [
|
|
103
|
-
`Unknown command: \`${commandToken}\`.`,
|
|
104
|
-
'If this is a built-in command or a typo, run `nb --help` to inspect available commands.',
|
|
105
|
-
`If \`${commandToken}\` should be a runtime command from your NocoBase app, run \`nb env update\` and try again.`,
|
|
106
|
-
].join('\n');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return message;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
83
|
try {
|
|
113
84
|
const argv = process.argv.slice(2);
|
|
114
|
-
const startupUpdate = await
|
|
85
|
+
const startupUpdate = await maybeRunStartupUpdate(argv);
|
|
115
86
|
if (startupUpdate.kind === 'updated') {
|
|
116
87
|
const result = spawnSync(process.execPath, process.argv.slice(1), {
|
|
117
88
|
stdio: 'inherit',
|
|
@@ -55,7 +55,11 @@ export default class AppRestart extends Command {
|
|
|
55
55
|
default: false,
|
|
56
56
|
}),
|
|
57
57
|
quickstart: Flags.boolean({ description: 'Quickstart the application after stopping it', required: false }),
|
|
58
|
-
port: Flags.string({
|
|
58
|
+
port: Flags.string({
|
|
59
|
+
description: 'Port (overrides appPort from env config when set)',
|
|
60
|
+
char: 'p',
|
|
61
|
+
required: false,
|
|
62
|
+
}),
|
|
59
63
|
daemon: Flags.boolean({
|
|
60
64
|
description: 'Run the application as a daemon after stopping it (default: true; use --no-daemon to stay in the foreground)',
|
|
61
65
|
char: 'd',
|
|
@@ -63,7 +67,11 @@ export default class AppRestart extends Command {
|
|
|
63
67
|
default: true,
|
|
64
68
|
allowNo: true,
|
|
65
69
|
}),
|
|
66
|
-
instances: Flags.integer({
|
|
70
|
+
instances: Flags.integer({
|
|
71
|
+
description: 'Number of instances to run after stopping it',
|
|
72
|
+
char: 'i',
|
|
73
|
+
required: false,
|
|
74
|
+
}),
|
|
67
75
|
'launch-mode': Flags.string({ description: 'Launch Mode', required: false, options: ['pm2', 'node'] }),
|
|
68
76
|
verbose: Flags.boolean({
|
|
69
77
|
description: 'Show raw shutdown/startup output from the underlying local or Docker command',
|
|
@@ -121,14 +129,13 @@ export default class AppRestart extends Command {
|
|
|
121
129
|
failTask(`Failed to recreate NocoBase for "${runtime.envName}".`);
|
|
122
130
|
this.error(formatDockerRestartFailure(runtime.envName, message));
|
|
123
131
|
}
|
|
124
|
-
const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
125
|
-
? undefined
|
|
126
|
-
: String(runtime.env.appPort));
|
|
132
|
+
const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null ? undefined : String(runtime.env.appPort));
|
|
127
133
|
await waitForAppReady({
|
|
128
134
|
envName: runtime.envName,
|
|
129
135
|
apiBaseUrl: resolveManagedAppApiBaseUrl(runtime),
|
|
130
136
|
containerName: runtime.containerName,
|
|
131
137
|
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
138
|
+
...(flags.verbose ? { verbose: true } : {}),
|
|
132
139
|
});
|
|
133
140
|
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
134
141
|
return;
|
|
@@ -175,6 +175,7 @@ export default class AppStart extends Command {
|
|
|
175
175
|
apiBaseUrl,
|
|
176
176
|
containerName: runtime.containerName,
|
|
177
177
|
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
178
|
+
...(flags.verbose ? { verbose: true } : {}),
|
|
178
179
|
});
|
|
179
180
|
if (shouldPrintStartSuccess()) {
|
|
180
181
|
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
@@ -312,7 +312,7 @@ export default class AppUpgrade extends Command {
|
|
|
312
312
|
throw new Error([
|
|
313
313
|
`Env "${runtime.envName}" does not have a saved \`downloadVersion\`.`,
|
|
314
314
|
'This env cannot be upgraded until a source version is explicit.',
|
|
315
|
-
|
|
315
|
+
`Re-run \`nb init --ui --env ${runtime.envName}\` for this env, or pass \`--version\` to \`nb app upgrade\`.`,
|
|
316
316
|
].join('\n'));
|
|
317
317
|
}
|
|
318
318
|
return {
|
|
@@ -383,7 +383,7 @@ export default class AppUpgrade extends Command {
|
|
|
383
383
|
this.error([
|
|
384
384
|
`Can't upgrade "${runtime.envName}" from this machine.`,
|
|
385
385
|
'This env only has an API connection, so there is no saved local app or Docker runtime to upgrade here.',
|
|
386
|
-
'If you want a local NocoBase AI environment that the CLI can upgrade, run `nb init` first.',
|
|
386
|
+
'If you want a local NocoBase AI environment that the CLI can upgrade, run `nb init --ui` first.',
|
|
387
387
|
].join('\n'));
|
|
388
388
|
}
|
|
389
389
|
if (runtime.kind === 'ssh') {
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Args, Command } from '@oclif/core';
|
|
10
10
|
import { assertSupportedCliConfigKey, deleteCliConfigValue } from '../../lib/cli-config.js';
|
|
11
|
+
import { clearLegacyStartupUpdatePolicyForCurrentInstall } from '../../lib/startup-update.js';
|
|
11
12
|
export default class ConfigDelete extends Command {
|
|
12
13
|
static summary = 'Delete an explicitly configured CLI setting';
|
|
13
14
|
static examples = [
|
|
14
15
|
'<%= config.bin %> <%= command.id %> locale',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> update.policy',
|
|
15
17
|
'<%= config.bin %> <%= command.id %> license.pkg-url',
|
|
16
18
|
'<%= config.bin %> <%= command.id %> docker.network',
|
|
17
19
|
'<%= config.bin %> <%= command.id %> docker.container-prefix',
|
|
@@ -29,6 +31,7 @@ export default class ConfigDelete extends Command {
|
|
|
29
31
|
const { args } = await this.parse(ConfigDelete);
|
|
30
32
|
const key = assertSupportedCliConfigKey(args.key);
|
|
31
33
|
const removed = await deleteCliConfigValue(key);
|
|
32
|
-
|
|
34
|
+
const clearedLegacy = key === 'update.policy' ? await clearLegacyStartupUpdatePolicyForCurrentInstall() : false;
|
|
35
|
+
this.log(removed || clearedLegacy ? `Deleted ${key}` : `${key} was not set`);
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -12,6 +12,7 @@ export default class ConfigGet extends Command {
|
|
|
12
12
|
static summary = 'Get the effective CLI configuration value for a key';
|
|
13
13
|
static examples = [
|
|
14
14
|
'<%= config.bin %> <%= command.id %> locale',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> update.policy',
|
|
15
16
|
'<%= config.bin %> <%= command.id %> license.pkg-url',
|
|
16
17
|
'<%= config.bin %> <%= command.id %> docker.network',
|
|
17
18
|
'<%= config.bin %> <%= command.id %> docker.container-prefix',
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Args, Command } from '@oclif/core';
|
|
10
10
|
import { assertSupportedCliConfigKey, setCliConfigValue } from '../../lib/cli-config.js';
|
|
11
|
+
import { clearLegacyStartupUpdatePolicyForCurrentInstall } from '../../lib/startup-update.js';
|
|
11
12
|
export default class ConfigSet extends Command {
|
|
12
13
|
static summary = 'Set a CLI configuration value';
|
|
13
|
-
static description = 'Set a supported CLI configuration key. Supported keys: locale, license.pkg-url, docker.network, docker.container-prefix, bin.docker, bin.git, bin.yarn.';
|
|
14
|
+
static description = 'Set a supported CLI configuration key. Supported keys: locale, update.policy, license.pkg-url, docker.network, docker.container-prefix, bin.docker, bin.git, bin.yarn.';
|
|
14
15
|
static examples = [
|
|
15
16
|
'<%= config.bin %> <%= command.id %> locale zh-CN',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> update.policy prompt',
|
|
16
18
|
'<%= config.bin %> <%= command.id %> license.pkg-url https://pkg.nocobase.com/',
|
|
17
19
|
'<%= config.bin %> <%= command.id %> docker.network nocobase',
|
|
18
20
|
'<%= config.bin %> <%= command.id %> docker.container-prefix nb',
|
|
@@ -34,6 +36,9 @@ export default class ConfigSet extends Command {
|
|
|
34
36
|
const { args } = await this.parse(ConfigSet);
|
|
35
37
|
const key = assertSupportedCliConfigKey(args.key);
|
|
36
38
|
const value = await setCliConfigValue(key, args.value);
|
|
39
|
+
if (key === 'update.policy') {
|
|
40
|
+
await clearLegacyStartupUpdatePolicyForCurrentInstall();
|
|
41
|
+
}
|
|
37
42
|
this.log(`${key}=${value}`);
|
|
38
43
|
}
|
|
39
44
|
}
|
package/dist/commands/db/ps.js
CHANGED
|
@@ -13,10 +13,7 @@ import { renderTable } from '../../lib/ui.js';
|
|
|
13
13
|
import { builtinDbStatus, resolveDbRuntime } from './shared.js';
|
|
14
14
|
export default class DbPs extends Command {
|
|
15
15
|
static description = 'Show built-in database runtime status for configured envs without starting or stopping anything.';
|
|
16
|
-
static examples = [
|
|
17
|
-
'<%= config.bin %> <%= command.id %>',
|
|
18
|
-
'<%= config.bin %> <%= command.id %> --env app1',
|
|
19
|
-
];
|
|
16
|
+
static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --env app1'];
|
|
20
17
|
static flags = {
|
|
21
18
|
env: Flags.string({
|
|
22
19
|
char: 'e',
|
|
@@ -26,11 +23,9 @@ export default class DbPs extends Command {
|
|
|
26
23
|
async run() {
|
|
27
24
|
const { flags } = await this.parse(DbPs);
|
|
28
25
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
29
|
-
const envNames = requestedEnv
|
|
30
|
-
? [requestedEnv]
|
|
31
|
-
: Object.keys((await listEnvs()).envs).sort();
|
|
26
|
+
const envNames = requestedEnv ? [requestedEnv] : Object.keys((await listEnvs()).envs).sort();
|
|
32
27
|
if (!envNames.length) {
|
|
33
|
-
this.log('No NocoBase env is configured yet. Run `nb init` to create one first.');
|
|
28
|
+
this.log('No NocoBase env is configured yet. Run `nb init --ui` to create one first.');
|
|
34
29
|
return;
|
|
35
30
|
}
|
|
36
31
|
const rows = [];
|
|
@@ -44,16 +39,8 @@ export default class DbPs extends Command {
|
|
|
44
39
|
continue;
|
|
45
40
|
}
|
|
46
41
|
const type = runtime.kind === 'builtin' ? 'builtin' : runtime.status;
|
|
47
|
-
const status = runtime.kind === 'builtin'
|
|
48
|
-
|
|
49
|
-
: runtime.status;
|
|
50
|
-
rows.push([
|
|
51
|
-
runtime.envName,
|
|
52
|
-
type,
|
|
53
|
-
runtime.dbDialect,
|
|
54
|
-
status,
|
|
55
|
-
runtime.address,
|
|
56
|
-
]);
|
|
42
|
+
const status = runtime.kind === 'builtin' ? await builtinDbStatus(runtime.containerName) : runtime.status;
|
|
43
|
+
rows.push([runtime.envName, type, runtime.dbDialect, status, runtime.address]);
|
|
57
44
|
}
|
|
58
45
|
this.log(renderTable(['Env', 'Type', 'Dialect', 'Status', 'Address'], rows));
|
|
59
46
|
}
|
|
@@ -23,7 +23,7 @@ function resolveExplicitAuthType(value) {
|
|
|
23
23
|
return value === 'basic' || value === 'token' || value === 'oauth' ? value : undefined;
|
|
24
24
|
}
|
|
25
25
|
function formatMissingEnvMessage(envName) {
|
|
26
|
-
return [`Env "${envName}" is not configured.`, `Run \`nb env
|
|
26
|
+
return [`Env "${envName}" is not configured.`, `Run \`nb init --ui --env ${envName}\` first.`].join('\n');
|
|
27
27
|
}
|
|
28
28
|
export default class EnvAuth extends Command {
|
|
29
29
|
static summary = 'Authenticate a saved NocoBase environment with basic login, a token, or OAuth';
|
|
@@ -9,13 +9,12 @@
|
|
|
9
9
|
import { Command } from '@oclif/core';
|
|
10
10
|
import { getCurrentEnvName, listEnvs, resolveConfiguredAuthType } from '../../lib/auth-store.js';
|
|
11
11
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { translateCli } from '../../lib/cli-locale.js';
|
|
12
13
|
import { renderTable } from '../../lib/ui.js';
|
|
13
14
|
import { resolveApiBaseUrl } from './shared.js';
|
|
14
15
|
export default class EnvList extends Command {
|
|
15
16
|
static summary = 'List configured environments';
|
|
16
|
-
static examples = [
|
|
17
|
-
'<%= config.bin %> <%= command.id %>',
|
|
18
|
-
];
|
|
17
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
19
18
|
async run() {
|
|
20
19
|
await this.parse(EnvList);
|
|
21
20
|
const scope = resolveDefaultConfigScope();
|
|
@@ -23,8 +22,8 @@ export default class EnvList extends Command {
|
|
|
23
22
|
const currentEnv = await getCurrentEnvName({ scope });
|
|
24
23
|
const names = Object.keys(envs).sort();
|
|
25
24
|
if (!names.length) {
|
|
26
|
-
this.log('
|
|
27
|
-
this.log('
|
|
25
|
+
this.log(translateCli('commands.env.messages.noEnvsConfigured'));
|
|
26
|
+
this.log(translateCli('commands.env.messages.noEnvsConfiguredHelp'));
|
|
28
27
|
return;
|
|
29
28
|
}
|
|
30
29
|
const rows = [];
|
|
@@ -10,6 +10,7 @@ import { Args, Command, Flags } from '@oclif/core';
|
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
|
|
11
11
|
import { getCurrentEnvName, listEnvs } from '../../lib/auth-store.js';
|
|
12
12
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
13
|
+
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
14
|
import { renderTable } from '../../lib/ui.js';
|
|
14
15
|
import { apiStatus, runtimeStatus } from './shared.js';
|
|
15
16
|
export default class EnvStatus extends Command {
|
|
@@ -46,13 +47,11 @@ export default class EnvStatus extends Command {
|
|
|
46
47
|
const { envs } = await listEnvs({ scope });
|
|
47
48
|
const configuredEnvNames = Object.keys(envs).sort();
|
|
48
49
|
if (!configuredEnvNames.length) {
|
|
49
|
-
this.log('
|
|
50
|
-
this.log('
|
|
50
|
+
this.log(translateCli('commands.env.messages.noEnvsConfigured'));
|
|
51
|
+
this.log(translateCli('commands.env.messages.noEnvsConfiguredHelp'));
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
|
-
const envNames = flags.all
|
|
54
|
-
? configuredEnvNames
|
|
55
|
-
: [requestedEnv || (await getCurrentEnvName({ scope }))];
|
|
54
|
+
const envNames = flags.all ? configuredEnvNames : [requestedEnv || (await getCurrentEnvName({ scope }))];
|
|
56
55
|
const rows = [];
|
|
57
56
|
for (const envName of envNames) {
|
|
58
57
|
const runtime = await resolveManagedAppRuntime(envName);
|
|
@@ -76,8 +75,12 @@ export default class EnvStatus extends Command {
|
|
|
76
75
|
rows.push({
|
|
77
76
|
env: runtime.envName,
|
|
78
77
|
status,
|
|
79
|
-
apiBaseUrl: runtime.env.apiBaseUrl
|
|
80
|
-
|
|
78
|
+
apiBaseUrl: runtime.env.apiBaseUrl ||
|
|
79
|
+
String(runtime.env.config.apiBaseUrl ??
|
|
80
|
+
runtime.env.config.baseUrl ??
|
|
81
|
+
envs[envName]?.apiBaseUrl ??
|
|
82
|
+
envs[envName]?.baseUrl ??
|
|
83
|
+
'').trim(),
|
|
81
84
|
});
|
|
82
85
|
}
|
|
83
86
|
if (flags['json-output']) {
|
package/dist/commands/install.js
CHANGED
|
@@ -26,6 +26,7 @@ import { omitKeys, upperFirst } from "../lib/object-utils.js";
|
|
|
26
26
|
import { getEnv, setCurrentEnv, upsertEnv } from '../lib/auth-store.js';
|
|
27
27
|
import { buildStoredEnvConfig } from '../lib/env-config.js';
|
|
28
28
|
import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
|
|
29
|
+
import { startDockerLogFollower } from '../lib/docker-log-stream.js';
|
|
29
30
|
import Download, { defaultDockerRegistryForLang } from './download.js';
|
|
30
31
|
import EnvAdd from "./env/add.js";
|
|
31
32
|
const DEFAULT_INSTALL_ENV_NAME = 'local';
|
|
@@ -1112,7 +1113,7 @@ export default class Install extends Command {
|
|
|
1112
1113
|
throw new Error([
|
|
1113
1114
|
`Cannot continue setup for "${env.name}" in non-interactive resume mode yet.`,
|
|
1114
1115
|
`These setup-only flags are not saved in the env config: ${missingFlags.join(', ')}`,
|
|
1115
|
-
`Run \`nb init --env ${env.name} --resume\` without \`--yes\`, or pass those flags again.`,
|
|
1116
|
+
`Run \`nb init --ui --env ${env.name} --resume\` without \`--yes\`, or pass those flags again.`,
|
|
1116
1117
|
].join('\n'));
|
|
1117
1118
|
}
|
|
1118
1119
|
}
|
|
@@ -2010,32 +2011,38 @@ export default class Install extends Command {
|
|
|
2010
2011
|
let lastMessage = 'No response yet';
|
|
2011
2012
|
let lastLoggedStatus = '';
|
|
2012
2013
|
printInfo('Waiting for NocoBase to become ready...');
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
lastLoggedStatus
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2014
|
+
const dockerLogFollower = options?.verbose && options.containerName ? startDockerLogFollower(options.containerName) : undefined;
|
|
2015
|
+
try {
|
|
2016
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
2017
|
+
const result = await Install.requestAppHealthCheck({
|
|
2018
|
+
healthCheckUrl,
|
|
2019
|
+
fetchImpl,
|
|
2020
|
+
requestTimeoutMs,
|
|
2021
|
+
});
|
|
2022
|
+
if (result.ok) {
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
lastMessage = result.message;
|
|
2026
|
+
const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
|
|
2027
|
+
const statusLine = `Waiting for NocoBase to become ready... (${elapsedSeconds}s elapsed, last status: ${Install.formatHealthCheckMessage(lastMessage)})`;
|
|
2028
|
+
if (statusLine !== lastLoggedStatus) {
|
|
2029
|
+
printInfo(statusLine);
|
|
2030
|
+
lastLoggedStatus = statusLine;
|
|
2031
|
+
}
|
|
2032
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
2033
|
+
if (remainingMs <= 0) {
|
|
2034
|
+
break;
|
|
2035
|
+
}
|
|
2036
|
+
await Install.sleep(Math.min(intervalMs, remainingMs));
|
|
2032
2037
|
}
|
|
2033
|
-
|
|
2038
|
+
const logHint = options?.containerName
|
|
2039
|
+
? ` You can inspect startup logs with: docker logs ${options.containerName}`
|
|
2040
|
+
: '';
|
|
2041
|
+
throw new Error(`The application did not become ready in time. Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${Install.formatHealthCheckMessage(lastMessage)}.${logHint}`);
|
|
2042
|
+
}
|
|
2043
|
+
finally {
|
|
2044
|
+
await dockerLogFollower?.stop();
|
|
2034
2045
|
}
|
|
2035
|
-
const logHint = options?.containerName
|
|
2036
|
-
? ` You can inspect startup logs with: docker logs ${options.containerName}`
|
|
2037
|
-
: '';
|
|
2038
|
-
throw new Error(`The application did not become ready in time. Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${Install.formatHealthCheckMessage(lastMessage)}.${logHint}`);
|
|
2039
2046
|
}
|
|
2040
2047
|
async saveInstalledEnv(params) {
|
|
2041
2048
|
await upsertEnv(params.envName, Install.buildSavedEnvConfig(params), { scope: resolveDefaultConfigScope() });
|
|
@@ -2392,6 +2399,7 @@ export default class Install extends Command {
|
|
|
2392
2399
|
envAddResults,
|
|
2393
2400
|
}), {
|
|
2394
2401
|
containerName: dockerAppPlan?.containerName,
|
|
2402
|
+
verbose: parsed.verbose,
|
|
2395
2403
|
});
|
|
2396
2404
|
printInfo(`NocoBase is ready at http://127.0.0.1:${dockerAppPlan?.appPort ?? localAppPlan?.appPort}`);
|
|
2397
2405
|
}
|
|
@@ -8,14 +8,29 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { confirm } from "../../lib/inquirer.js";
|
|
11
|
+
import { updateNocoBaseSkills } from '../../lib/skills-manager.js';
|
|
11
12
|
import { setVerboseMode } from '../../lib/ui.js';
|
|
12
13
|
import { formatSelfUpdateUnavailableMessage, formatUnsupportedSelfUpdateMessage, inspectSelfStatus, updateSelf, } from '../../lib/self-manager.js';
|
|
14
|
+
function formatSkillsUpdateMessage(result, verbose) {
|
|
15
|
+
if (result.action === 'noop') {
|
|
16
|
+
if (result.reason === 'not-installed') {
|
|
17
|
+
return verbose
|
|
18
|
+
? 'NocoBase AI coding skills are not installed globally. Run `nb skills install` first.'
|
|
19
|
+
: 'Skipped skills update because NocoBase AI coding skills are not installed.';
|
|
20
|
+
}
|
|
21
|
+
return verbose
|
|
22
|
+
? 'NocoBase AI coding skills are already up to date globally.'
|
|
23
|
+
: 'NocoBase AI coding skills are up to date.';
|
|
24
|
+
}
|
|
25
|
+
return verbose ? 'Updated the global NocoBase AI coding skills.' : 'Updated NocoBase AI coding skills globally.';
|
|
26
|
+
}
|
|
13
27
|
export default class SelfUpdate extends Command {
|
|
14
28
|
static summary = 'Update the globally installed NocoBase CLI';
|
|
15
29
|
static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm install.';
|
|
16
30
|
static examples = [
|
|
17
31
|
'<%= config.bin %> <%= command.id %>',
|
|
18
32
|
'<%= config.bin %> <%= command.id %> --yes',
|
|
33
|
+
'<%= config.bin %> <%= command.id %> --skills',
|
|
19
34
|
'<%= config.bin %> <%= command.id %> --channel alpha --json',
|
|
20
35
|
];
|
|
21
36
|
static flags = {
|
|
@@ -33,6 +48,10 @@ export default class SelfUpdate extends Command {
|
|
|
33
48
|
description: 'Output the result as JSON',
|
|
34
49
|
default: false,
|
|
35
50
|
}),
|
|
51
|
+
skills: Flags.boolean({
|
|
52
|
+
description: 'Also update the globally installed NocoBase AI coding skills',
|
|
53
|
+
default: false,
|
|
54
|
+
}),
|
|
36
55
|
verbose: Flags.boolean({
|
|
37
56
|
description: 'Show detailed update output',
|
|
38
57
|
default: false,
|
|
@@ -50,11 +69,14 @@ export default class SelfUpdate extends Command {
|
|
|
50
69
|
if (!status.latestVersion && status.registryError) {
|
|
51
70
|
this.error(formatSelfUpdateUnavailableMessage(status));
|
|
52
71
|
}
|
|
72
|
+
let shouldUpdateSkills = flags.skills;
|
|
53
73
|
if (!flags.yes && status.updateAvailable) {
|
|
54
74
|
let confirmed = false;
|
|
55
75
|
try {
|
|
56
76
|
confirmed = await confirm({
|
|
57
|
-
message:
|
|
77
|
+
message: flags.skills
|
|
78
|
+
? `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion} and refresh the globally installed NocoBase AI coding skills?`
|
|
79
|
+
: `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
|
|
58
80
|
default: false,
|
|
59
81
|
});
|
|
60
82
|
}
|
|
@@ -69,6 +91,24 @@ export default class SelfUpdate extends Command {
|
|
|
69
91
|
channel: flags.channel,
|
|
70
92
|
verbose: flags.verbose,
|
|
71
93
|
});
|
|
94
|
+
if (flags.skills && !flags.yes && !status.updateAvailable) {
|
|
95
|
+
let confirmed = false;
|
|
96
|
+
try {
|
|
97
|
+
confirmed = await confirm({
|
|
98
|
+
message: 'Update the globally installed NocoBase AI coding skills?',
|
|
99
|
+
default: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
shouldUpdateSkills = confirmed;
|
|
106
|
+
}
|
|
107
|
+
const skillsResult = shouldUpdateSkills
|
|
108
|
+
? await updateNocoBaseSkills({
|
|
109
|
+
verbose: flags.verbose,
|
|
110
|
+
})
|
|
111
|
+
: undefined;
|
|
72
112
|
if (flags.json) {
|
|
73
113
|
this.log(JSON.stringify({
|
|
74
114
|
ok: true,
|
|
@@ -79,6 +119,17 @@ export default class SelfUpdate extends Command {
|
|
|
79
119
|
channel: result.status.channel,
|
|
80
120
|
fromVersion: result.status.currentVersion,
|
|
81
121
|
toVersion: result.targetVersion,
|
|
122
|
+
skills: skillsResult
|
|
123
|
+
? {
|
|
124
|
+
action: skillsResult.action,
|
|
125
|
+
reason: skillsResult.action === 'noop' ? skillsResult.reason : undefined,
|
|
126
|
+
globalRoot: skillsResult.status.globalRoot,
|
|
127
|
+
workspaceRoot: skillsResult.status.workspaceRoot,
|
|
128
|
+
installedSkillNames: skillsResult.status.installedSkillNames,
|
|
129
|
+
installedVersion: skillsResult.status.installedVersion,
|
|
130
|
+
installedRef: skillsResult.status.installedRef,
|
|
131
|
+
}
|
|
132
|
+
: undefined,
|
|
82
133
|
}, null, 2));
|
|
83
134
|
return;
|
|
84
135
|
}
|
|
@@ -86,10 +137,16 @@ export default class SelfUpdate extends Command {
|
|
|
86
137
|
this.log(flags.verbose
|
|
87
138
|
? `NocoBase CLI is already up to date at ${result.status.currentVersion}.`
|
|
88
139
|
: `NocoBase CLI is up to date: ${result.status.currentVersion}.`);
|
|
140
|
+
if (skillsResult) {
|
|
141
|
+
this.log(formatSkillsUpdateMessage(skillsResult, flags.verbose));
|
|
142
|
+
}
|
|
89
143
|
return;
|
|
90
144
|
}
|
|
91
145
|
this.log(flags.verbose
|
|
92
146
|
? `Updated NocoBase CLI from ${result.status.currentVersion} using ${result.packageSpec}${result.targetVersion ? ` (latest ${result.status.channel} resolves to ${result.targetVersion})` : ''}.`
|
|
93
147
|
: `Updated NocoBase CLI: ${result.status.currentVersion} -> ${result.targetVersion}.`);
|
|
148
|
+
if (skillsResult) {
|
|
149
|
+
this.log(formatSkillsUpdateMessage(skillsResult, flags.verbose));
|
|
150
|
+
}
|
|
94
151
|
}
|
|
95
152
|
}
|
|
@@ -16,20 +16,20 @@ function formatUnsupportedRuntimeMessage(kind, envName) {
|
|
|
16
16
|
return [
|
|
17
17
|
`Can't run dev mode for "${envName}".`,
|
|
18
18
|
'This env is managed by Docker, but `nb source dev` requires a local npm or Git source directory.',
|
|
19
|
-
`Use \`nb app logs --env ${envName}\` to inspect the Docker app, or create a source-based env with \`nb init --env ${envName} --source git\`.`,
|
|
19
|
+
`Use \`nb app logs --env ${envName}\` to inspect the Docker app, or create a source-based env with \`nb init --ui --env ${envName} --source git\`.`,
|
|
20
20
|
].join('\n');
|
|
21
21
|
}
|
|
22
22
|
if (kind === 'ssh') {
|
|
23
23
|
return [
|
|
24
24
|
`Can't run dev mode for "${envName}" yet.`,
|
|
25
25
|
'SSH env support is reserved but not implemented yet.',
|
|
26
|
-
`Create a source-based env with \`nb init --env ${envName} --source git\` if you want local development mode right now.`,
|
|
26
|
+
`Create a source-based env with \`nb init --ui --env ${envName} --source git\` if you want local development mode right now.`,
|
|
27
27
|
].join('\n');
|
|
28
28
|
}
|
|
29
29
|
return [
|
|
30
30
|
`Can't run dev mode for "${envName}".`,
|
|
31
31
|
'This env only has an API connection, but `nb source dev` requires a local npm or Git source directory.',
|
|
32
|
-
`Create a source-based env with \`nb init --env ${envName} --source git\` if you want local development mode.`,
|
|
32
|
+
`Create a source-based env with \`nb init --ui --env ${envName} --source git\` if you want local development mode.`,
|
|
33
33
|
].join('\n');
|
|
34
34
|
}
|
|
35
35
|
function appUrlForPort(port) {
|
|
@@ -129,8 +129,8 @@ export default class SourceDev extends Command {
|
|
|
129
129
|
this.error(formatUnsupportedRuntimeMessage(runtime.kind, runtime.envName));
|
|
130
130
|
}
|
|
131
131
|
announceTargetEnv(runtime.envName);
|
|
132
|
-
const devPort = flags.port
|
|
133
|
-
|
|
132
|
+
const devPort = flags.port ||
|
|
133
|
+
(runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
134
134
|
? String(runtime.env.appPort).trim()
|
|
135
135
|
: undefined);
|
|
136
136
|
const appUrl = appUrlForPort(devPort);
|
package/dist/lib/app-health.js
CHANGED
|
@@ -6,6 +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 { startDockerLogFollower } from './docker-log-stream.js';
|
|
9
10
|
import { printInfo } from './ui.js';
|
|
10
11
|
const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
|
|
11
12
|
const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
|
|
@@ -99,34 +100,40 @@ export async function waitForAppReady(params) {
|
|
|
99
100
|
let lastMessage = 'No response yet';
|
|
100
101
|
let nextProgressLogAt = startedAt + progressLogIntervalMs;
|
|
101
102
|
printInfo(`Waiting for NocoBase to become ready for "${params.envName}"...`);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const now = Date.now();
|
|
113
|
-
if (now >= nextProgressLogAt) {
|
|
114
|
-
const elapsedSeconds = Math.max(1, Math.floor((now - startedAt) / 1000));
|
|
115
|
-
printInfo(`Still waiting for "${params.envName}"... (${elapsedSeconds}s elapsed)`);
|
|
116
|
-
while (nextProgressLogAt <= now) {
|
|
117
|
-
nextProgressLogAt += progressLogIntervalMs;
|
|
103
|
+
const dockerLogFollower = params.verbose && params.containerName ? startDockerLogFollower(params.containerName) : undefined;
|
|
104
|
+
try {
|
|
105
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
106
|
+
const result = await requestAppHealthCheck({
|
|
107
|
+
healthCheckUrl,
|
|
108
|
+
fetchImpl: params.fetchImpl,
|
|
109
|
+
requestTimeoutMs: params.requestTimeoutMs,
|
|
110
|
+
});
|
|
111
|
+
if (result.ok) {
|
|
112
|
+
return;
|
|
118
113
|
}
|
|
114
|
+
lastMessage = result.message;
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
if (now >= nextProgressLogAt) {
|
|
117
|
+
const elapsedSeconds = Math.max(1, Math.floor((now - startedAt) / 1000));
|
|
118
|
+
printInfo(`Still waiting for "${params.envName}"... (${elapsedSeconds}s elapsed)`);
|
|
119
|
+
while (nextProgressLogAt <= now) {
|
|
120
|
+
nextProgressLogAt += progressLogIntervalMs;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
124
|
+
if (remainingMs <= 0) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
await sleep(Math.min(intervalMs, remainingMs));
|
|
119
128
|
}
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
const hints = [
|
|
130
|
+
params.logHint,
|
|
131
|
+
params.containerName ? `docker logs ${params.containerName}` : undefined,
|
|
132
|
+
].filter(Boolean);
|
|
133
|
+
const hintText = hints.length > 0 ? ` ${hints.join(' ')}` : '';
|
|
134
|
+
throw new AppHealthCheckError(`NocoBase did not become ready in time for "${params.envName}". Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${lastMessage}.${hintText}`);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await dockerLogFollower?.stop();
|
|
125
138
|
}
|
|
126
|
-
const hints = [
|
|
127
|
-
params.logHint,
|
|
128
|
-
params.containerName ? `docker logs ${params.containerName}` : undefined,
|
|
129
|
-
].filter(Boolean);
|
|
130
|
-
const hintText = hints.length > 0 ? ` ${hints.join(' ')}` : '';
|
|
131
|
-
throw new AppHealthCheckError(`NocoBase did not become ready in time for "${params.envName}". Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${lastMessage}.${hintText}`);
|
|
132
139
|
}
|
|
@@ -82,7 +82,7 @@ function formatSavedDockerSettingsIncomplete(envName, missing) {
|
|
|
82
82
|
return [
|
|
83
83
|
`Can't start NocoBase for "${envName}" yet.`,
|
|
84
84
|
`The saved Docker settings for this env are incomplete. Missing: ${missing.join(', ')}.`,
|
|
85
|
-
|
|
85
|
+
`Re-run \`nb init --ui --env ${envName}\` to refresh this env config, then try again.`,
|
|
86
86
|
].join('\n');
|
|
87
87
|
}
|
|
88
88
|
function formatDockerAppRecreateFailure(envName, message) {
|