@nocobase/cli 2.1.0-beta.42 → 2.1.0-beta.42-test.2
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/init.js +18 -0
- package/dist/commands/install.js +53 -29
- 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/init.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import pc from 'picocolors';
|
|
11
|
+
import crypto from 'node:crypto';
|
|
11
12
|
import { existsSync } from 'node:fs';
|
|
12
13
|
import path from 'node:path';
|
|
13
14
|
import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
@@ -84,6 +85,16 @@ function explicitApiBaseUrlFlag(flags) {
|
|
|
84
85
|
function explicitDbHostFlag(flags) {
|
|
85
86
|
return String(flags['db-host'] ?? '').trim();
|
|
86
87
|
}
|
|
88
|
+
function optionalInitString(value) {
|
|
89
|
+
const text = String(value ?? '').trim();
|
|
90
|
+
return text || undefined;
|
|
91
|
+
}
|
|
92
|
+
function resolveManagedAppKey(value) {
|
|
93
|
+
return optionalInitString(value) ?? crypto.randomBytes(32).toString('hex');
|
|
94
|
+
}
|
|
95
|
+
function resolveManagedTimeZone(value) {
|
|
96
|
+
return optionalInitString(value) ?? (Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC');
|
|
97
|
+
}
|
|
87
98
|
function shouldAllowExistingInitEnv() {
|
|
88
99
|
return argvHasToken(process.argv.slice(2), ['--force', '-f']);
|
|
89
100
|
}
|
|
@@ -803,6 +814,7 @@ Prompt modes:
|
|
|
803
814
|
}
|
|
804
815
|
async persistManagedEnvConfig(results, flags = {}) {
|
|
805
816
|
const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
|
|
817
|
+
const existingEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
|
|
806
818
|
const appPort = String(results.appPort ?? '').trim();
|
|
807
819
|
const source = String(results.source ?? '').trim();
|
|
808
820
|
const version = resolveInitDownloadVersion(results);
|
|
@@ -826,11 +838,15 @@ Prompt modes:
|
|
|
826
838
|
const authUsername = authType === 'basic' ? String(results.username ?? results.rootUsername ?? '').trim() : '';
|
|
827
839
|
const accessToken = String(results.accessToken ?? '');
|
|
828
840
|
const skipDownload = results.skipDownload === true;
|
|
841
|
+
const appKey = resolveManagedAppKey(results.appKey ?? existingEnv?.config.appKey);
|
|
842
|
+
const timeZone = resolveManagedTimeZone(results.timeZone ?? existingEnv?.config.timezone);
|
|
829
843
|
const builtinDb = explicitDbHostFlag(flags)
|
|
830
844
|
? false
|
|
831
845
|
: results.builtinDb === undefined
|
|
832
846
|
? undefined
|
|
833
847
|
: Boolean(results.builtinDb);
|
|
848
|
+
results.appKey = appKey;
|
|
849
|
+
results.timeZone = timeZone;
|
|
834
850
|
await upsertEnv(envName, {
|
|
835
851
|
...(source === 'docker'
|
|
836
852
|
? { kind: 'docker' }
|
|
@@ -852,6 +868,8 @@ Prompt modes:
|
|
|
852
868
|
...(appRootPath ? { appRootPath } : {}),
|
|
853
869
|
...(storagePath ? { storagePath } : {}),
|
|
854
870
|
...(appPort ? { appPort } : {}),
|
|
871
|
+
...(appKey ? { appKey } : {}),
|
|
872
|
+
...(timeZone ? { timezone: timeZone } : {}),
|
|
855
873
|
...(!skipDownload && results.devDependencies !== undefined
|
|
856
874
|
? { devDependencies: Boolean(results.devDependencies) }
|
|
857
875
|
: {}),
|
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';
|
|
@@ -842,6 +843,18 @@ export default class Install extends Command {
|
|
|
842
843
|
const text = String(value).trim();
|
|
843
844
|
return text || undefined;
|
|
844
845
|
}
|
|
846
|
+
static resolveManagedAppKey(value) {
|
|
847
|
+
return Install.toOptionalPromptString(value) ?? crypto.randomBytes(32).toString('hex');
|
|
848
|
+
}
|
|
849
|
+
static resolveManagedTimeZone(value) {
|
|
850
|
+
return Install.toOptionalPromptString(value) ?? (Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC');
|
|
851
|
+
}
|
|
852
|
+
async ensureManagedAppRuntimeConfig(params) {
|
|
853
|
+
const savedEnv = await getEnv(params.envName, { scope: resolveDefaultConfigScope() });
|
|
854
|
+
const savedConfig = savedEnv?.config;
|
|
855
|
+
params.appResults.appKey = Install.resolveManagedAppKey(params.appResults.appKey ?? savedConfig?.appKey);
|
|
856
|
+
params.appResults.timeZone = Install.resolveManagedTimeZone(params.appResults.timeZone ?? savedConfig?.timezone);
|
|
857
|
+
}
|
|
845
858
|
static async validateAppPort(value, values) {
|
|
846
859
|
const formatError = validateTcpPort(value);
|
|
847
860
|
if (formatError) {
|
|
@@ -1112,7 +1125,7 @@ export default class Install extends Command {
|
|
|
1112
1125
|
throw new Error([
|
|
1113
1126
|
`Cannot continue setup for "${env.name}" in non-interactive resume mode yet.`,
|
|
1114
1127
|
`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.`,
|
|
1128
|
+
`Run \`nb init --ui --env ${env.name} --resume\` without \`--yes\`, or pass those flags again.`,
|
|
1116
1129
|
].join('\n'));
|
|
1117
1130
|
}
|
|
1118
1131
|
}
|
|
@@ -1664,8 +1677,8 @@ export default class Install extends Command {
|
|
|
1664
1677
|
const dbSchema = optionalEnvString(params.dbResults.dbSchema);
|
|
1665
1678
|
const dbTablePrefix = optionalEnvString(params.dbResults.dbTablePrefix);
|
|
1666
1679
|
const dbUnderscored = optionalEnvBoolean(params.dbResults.dbUnderscored);
|
|
1667
|
-
const appKey =
|
|
1668
|
-
const timeZone =
|
|
1680
|
+
const appKey = Install.resolveManagedAppKey(params.appResults.appKey);
|
|
1681
|
+
const timeZone = Install.resolveManagedTimeZone(params.appResults.timeZone);
|
|
1669
1682
|
const containerName = Install.buildDockerAppContainerName(params.envName, params.dockerContainerPrefix ?? params.workspaceName);
|
|
1670
1683
|
const configuredEnvFile = String(params.appResults.envFile ?? '').trim();
|
|
1671
1684
|
const envFile = await resolveDockerEnvFileArg(params.envName, configuredEnvFile ? { envFile: configuredEnvFile } : undefined);
|
|
@@ -1888,8 +1901,8 @@ export default class Install extends Command {
|
|
|
1888
1901
|
const storagePath = resolveConfiguredEnvPath(configuredStoragePath) ??
|
|
1889
1902
|
resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
|
|
1890
1903
|
const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
|
|
1891
|
-
const appKey =
|
|
1892
|
-
const timeZone =
|
|
1904
|
+
const appKey = Install.resolveManagedAppKey(params.appResults.appKey);
|
|
1905
|
+
const timeZone = Install.resolveManagedTimeZone(params.appResults.timeZone);
|
|
1893
1906
|
const env = {
|
|
1894
1907
|
STORAGE_PATH: storagePath,
|
|
1895
1908
|
APP_PORT: String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT,
|
|
@@ -2010,32 +2023,38 @@ export default class Install extends Command {
|
|
|
2010
2023
|
let lastMessage = 'No response yet';
|
|
2011
2024
|
let lastLoggedStatus = '';
|
|
2012
2025
|
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
|
-
|
|
2026
|
+
const dockerLogFollower = options?.verbose && options.containerName ? startDockerLogFollower(options.containerName) : undefined;
|
|
2027
|
+
try {
|
|
2028
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
2029
|
+
const result = await Install.requestAppHealthCheck({
|
|
2030
|
+
healthCheckUrl,
|
|
2031
|
+
fetchImpl,
|
|
2032
|
+
requestTimeoutMs,
|
|
2033
|
+
});
|
|
2034
|
+
if (result.ok) {
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
lastMessage = result.message;
|
|
2038
|
+
const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
|
|
2039
|
+
const statusLine = `Waiting for NocoBase to become ready... (${elapsedSeconds}s elapsed, last status: ${Install.formatHealthCheckMessage(lastMessage)})`;
|
|
2040
|
+
if (statusLine !== lastLoggedStatus) {
|
|
2041
|
+
printInfo(statusLine);
|
|
2042
|
+
lastLoggedStatus = statusLine;
|
|
2043
|
+
}
|
|
2044
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
2045
|
+
if (remainingMs <= 0) {
|
|
2046
|
+
break;
|
|
2047
|
+
}
|
|
2048
|
+
await Install.sleep(Math.min(intervalMs, remainingMs));
|
|
2032
2049
|
}
|
|
2033
|
-
|
|
2050
|
+
const logHint = options?.containerName
|
|
2051
|
+
? ` You can inspect startup logs with: docker logs ${options.containerName}`
|
|
2052
|
+
: '';
|
|
2053
|
+
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}`);
|
|
2054
|
+
}
|
|
2055
|
+
finally {
|
|
2056
|
+
await dockerLogFollower?.stop();
|
|
2034
2057
|
}
|
|
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
2058
|
}
|
|
2040
2059
|
async saveInstalledEnv(params) {
|
|
2041
2060
|
await upsertEnv(params.envName, Install.buildSavedEnvConfig(params), { scope: resolveDefaultConfigScope() });
|
|
@@ -2273,6 +2292,10 @@ export default class Install extends Command {
|
|
|
2273
2292
|
}
|
|
2274
2293
|
const promptResults = await this.collectPromptResults(parsed, flags.yes);
|
|
2275
2294
|
const { envName, appResults, downloadResults, dbResults, rootResults, envAddResults } = promptResults;
|
|
2295
|
+
await this.ensureManagedAppRuntimeConfig({
|
|
2296
|
+
envName,
|
|
2297
|
+
appResults,
|
|
2298
|
+
});
|
|
2276
2299
|
const source = String(downloadResultsValue(downloadResults, 'source') ?? '').trim();
|
|
2277
2300
|
const usesDockerResources = Boolean(dbResults.builtinDb) || source === 'docker';
|
|
2278
2301
|
const dockerNetworkName = usesDockerResources
|
|
@@ -2392,6 +2415,7 @@ export default class Install extends Command {
|
|
|
2392
2415
|
envAddResults,
|
|
2393
2416
|
}), {
|
|
2394
2417
|
containerName: dockerAppPlan?.containerName,
|
|
2418
|
+
verbose: parsed.verbose,
|
|
2395
2419
|
});
|
|
2396
2420
|
printInfo(`NocoBase is ready at http://127.0.0.1:${dockerAppPlan?.appPort ?? localAppPlan?.appPort}`);
|
|
2397
2421
|
}
|
|
@@ -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
|
}
|