@nocobase/cli 2.1.0-beta.42 → 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/dist/lib/app-runtime.js
CHANGED
|
@@ -110,10 +110,10 @@ export function formatMissingManagedAppEnvMessage(envName) {
|
|
|
110
110
|
if (requested) {
|
|
111
111
|
return [
|
|
112
112
|
`Env "${requested}" is not configured in this workspace.`,
|
|
113
|
-
`If you want to create a new NocoBase AI environment, run \`nb init --env ${requested}\` first.`,
|
|
113
|
+
`If you want to create a new NocoBase AI environment, run \`nb init --ui --env ${requested}\` first.`,
|
|
114
114
|
].join('\n');
|
|
115
115
|
}
|
|
116
|
-
return 'No NocoBase env is configured yet. Run `nb init` to create one first.';
|
|
116
|
+
return 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.';
|
|
117
117
|
}
|
|
118
118
|
export async function runLocalNocoBaseCommand(runtime, args, options) {
|
|
119
119
|
const envVars = await buildRuntimeEnvVars(runtime);
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -32,6 +32,13 @@ function normalizeOptionalCliLocale(value) {
|
|
|
32
32
|
}
|
|
33
33
|
return normalizeCliLocale(normalized);
|
|
34
34
|
}
|
|
35
|
+
function normalizeOptionalCliUpdatePolicy(value) {
|
|
36
|
+
const normalized = normalizeOptionalString(value);
|
|
37
|
+
if (normalized === 'prompt' || normalized === 'auto' || normalized === 'off') {
|
|
38
|
+
return normalized;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
35
42
|
export function readEnvApiBaseUrl(config) {
|
|
36
43
|
if (!config) {
|
|
37
44
|
return undefined;
|
|
@@ -79,10 +86,12 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
79
86
|
function normalizeAuthConfig(config) {
|
|
80
87
|
const settings = config.settings ?? {};
|
|
81
88
|
const locale = normalizeOptionalCliLocale(settings.locale);
|
|
89
|
+
const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
|
|
82
90
|
return {
|
|
83
91
|
name: config.name || config.dockerResourcePrefix,
|
|
84
92
|
settings: {
|
|
85
93
|
...(locale ? { locale } : {}),
|
|
94
|
+
...(updatePolicy ? { update: { policy: updatePolicy } } : {}),
|
|
86
95
|
...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
|
|
87
96
|
...(settings.docker?.network || settings.docker?.containerPrefix
|
|
88
97
|
? {
|
package/dist/lib/backup.js
CHANGED
|
@@ -62,14 +62,14 @@ export async function resolveBackupTargetEnv(requestedEnv) {
|
|
|
62
62
|
const { envs } = await listEnvs({ scope });
|
|
63
63
|
const configuredEnvNames = Object.keys(envs);
|
|
64
64
|
if (!configuredEnvNames.length) {
|
|
65
|
-
throw new Error('No env is configured. Run `nb
|
|
65
|
+
throw new Error('No env is configured. Run `nb init --ui` first.');
|
|
66
66
|
}
|
|
67
67
|
if (requestedEnv?.trim()) {
|
|
68
|
-
throw new Error(`Env "${envName}" is not configured. Run \`nb env
|
|
68
|
+
throw new Error(`Env "${envName}" is not configured. Run \`nb init --ui --env ${envName}\` first.`);
|
|
69
69
|
}
|
|
70
70
|
throw new Error([
|
|
71
71
|
`Current env "${envName}" is not configured.`,
|
|
72
|
-
|
|
72
|
+
`Switch to an existing env with \`nb env use <name>\`, or run \`nb init --ui --env ${envName}\` to create or connect it.`,
|
|
73
73
|
].join('\n'));
|
|
74
74
|
}
|
|
75
75
|
export async function ensureBackupRuntimeCommands(params) {
|
package/dist/lib/bootstrap.js
CHANGED
|
@@ -254,25 +254,31 @@ export function formatSwaggerSchemaError(response, context) {
|
|
|
254
254
|
})
|
|
255
255
|
.join('\n');
|
|
256
256
|
const envLabel = context.envName ? ` for env "${context.envName}"` : '';
|
|
257
|
+
const tokenHint = context.envName
|
|
258
|
+
? `Update the API key with \`nb env update ${context.envName} --token <api-key>\`, log in with \`nb env auth ${context.envName}\`, or rerun the command with \`--access-token <api-key>\`.`
|
|
259
|
+
: 'Update the API key with `nb env update <name> --token <api-key>`, log in with `nb env auth <name>`, or rerun the command with `--access-token <api-key>`.';
|
|
257
260
|
const commandHint = context.commandToken
|
|
258
|
-
? `If \`${context.commandToken}\` is a runtime command,
|
|
259
|
-
: 'Run `nb --help` to inspect built-in commands, then
|
|
261
|
+
? `If \`${context.commandToken}\` is a runtime command, retry it after updating the token. If it is a typo, run \`nb --help\` to inspect available commands.`
|
|
262
|
+
: 'Run `nb --help` to inspect built-in commands, then retry the original command after updating the token.';
|
|
260
263
|
return [
|
|
261
264
|
`Authentication failed while loading the command runtime from \`swagger:get\`${envLabel}.`,
|
|
262
265
|
`Base URL: ${context.baseUrl}`,
|
|
263
266
|
details,
|
|
264
|
-
|
|
267
|
+
tokenHint,
|
|
265
268
|
commandHint,
|
|
266
269
|
].join('\n');
|
|
267
270
|
}
|
|
268
271
|
if (isNetworkFetchFailure(response)) {
|
|
269
272
|
const rawMessage = response.data?.error?.message || 'fetch failed';
|
|
273
|
+
const updateConnectionHint = context.envName
|
|
274
|
+
? `If you recently changed the server address, update env "${context.envName}" with \`nb env update ${context.envName} --api-base-url <url>\` and retry the original command.`
|
|
275
|
+
: 'If you recently changed the server address, update the saved env connection with `nb env update <name> --api-base-url <url>` and retry the original command.';
|
|
270
276
|
return [
|
|
271
277
|
'Failed to reach the NocoBase server while loading the command runtime from `swagger:get`.',
|
|
272
278
|
`Base URL: ${context.baseUrl}`,
|
|
273
279
|
`Network error: ${rawMessage}`,
|
|
274
280
|
'Check that the NocoBase app is running, the base URL is correct, and the server is reachable from this machine.',
|
|
275
|
-
|
|
281
|
+
updateConnectionHint,
|
|
276
282
|
'Use `nb env list` to inspect the current env configuration.',
|
|
277
283
|
].join('\n');
|
|
278
284
|
}
|
|
@@ -282,7 +288,7 @@ export function formatMissingRuntimeEnvError(commandToken) {
|
|
|
282
288
|
if (!commandToken) {
|
|
283
289
|
return [
|
|
284
290
|
'No env is configured for runtime commands.',
|
|
285
|
-
'Run `nb
|
|
291
|
+
'Run `nb init --ui` first.',
|
|
286
292
|
'If you configure multiple environments later, switch with `nb env use <name>`.',
|
|
287
293
|
].join('\n');
|
|
288
294
|
}
|
|
@@ -290,7 +296,7 @@ export function formatMissingRuntimeEnvError(commandToken) {
|
|
|
290
296
|
`Unable to resolve runtime command \`${commandToken}\`.`,
|
|
291
297
|
'No env is configured, so the CLI cannot load runtime commands from `swagger:get`.',
|
|
292
298
|
'If this is a built-in command or a typo, run `nb --help` to inspect available commands.',
|
|
293
|
-
'If this should be an application runtime command, run `nb
|
|
299
|
+
'If this should be an application runtime command, run `nb init --ui` to create or connect a NocoBase app first.',
|
|
294
300
|
].join('\n');
|
|
295
301
|
}
|
|
296
302
|
export async function ensureRuntimeFromArgv(argv, options) {
|
|
@@ -366,8 +372,8 @@ export async function updateEnvRuntime(options) {
|
|
|
366
372
|
throw new Error([
|
|
367
373
|
env
|
|
368
374
|
? `Env "${envName}" is missing a base URL.`
|
|
369
|
-
: `Env "${envName}" is not configured. Run \`nb env
|
|
370
|
-
env ?
|
|
375
|
+
: `Env "${envName}" is not configured. Run \`nb init --ui --env ${envName}\` first.`,
|
|
376
|
+
env ? `Update env "${envName}" with \`nb env update ${envName} --api-base-url <url>\` first.` : '',
|
|
371
377
|
]
|
|
372
378
|
.filter(Boolean)
|
|
373
379
|
.join('\n'));
|
package/dist/lib/cli-config.js
CHANGED
|
@@ -15,8 +15,11 @@ export const DEFAULT_DOCKER_CONTAINER_PREFIX = 'nb';
|
|
|
15
15
|
export const DEFAULT_DOCKER_BIN = 'docker';
|
|
16
16
|
export const DEFAULT_GIT_BIN = 'git';
|
|
17
17
|
export const DEFAULT_YARN_BIN = 'yarn';
|
|
18
|
+
export const CLI_UPDATE_POLICY_OPTIONS = ['prompt', 'auto', 'off'];
|
|
19
|
+
export const DEFAULT_UPDATE_POLICY = 'prompt';
|
|
18
20
|
export const SUPPORTED_CLI_CONFIG_KEYS = [
|
|
19
21
|
'locale',
|
|
22
|
+
'update.policy',
|
|
20
23
|
'license.pkg-url',
|
|
21
24
|
'docker.network',
|
|
22
25
|
'docker.container-prefix',
|
|
@@ -42,9 +45,17 @@ export function assertSupportedCliConfigKey(value) {
|
|
|
42
45
|
}
|
|
43
46
|
return value;
|
|
44
47
|
}
|
|
48
|
+
export function normalizeCliUpdatePolicy(value) {
|
|
49
|
+
const normalized = trimValue(value);
|
|
50
|
+
if (!normalized) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return CLI_UPDATE_POLICY_OPTIONS.includes(normalized) ? normalized : undefined;
|
|
54
|
+
}
|
|
45
55
|
function cloneSettings(config) {
|
|
46
56
|
return {
|
|
47
57
|
...(config.settings?.locale ? { locale: trimValue(config.settings.locale) } : {}),
|
|
58
|
+
update: config.settings?.update ? { ...config.settings.update } : undefined,
|
|
48
59
|
license: config.settings?.license ? { ...config.settings.license } : undefined,
|
|
49
60
|
docker: config.settings?.docker ? { ...config.settings.docker } : undefined,
|
|
50
61
|
bin: config.settings?.bin ? { ...config.settings.bin } : undefined,
|
|
@@ -54,6 +65,10 @@ function pruneSettings(config) {
|
|
|
54
65
|
if (config.settings && !trimValue(config.settings.locale)) {
|
|
55
66
|
delete config.settings.locale;
|
|
56
67
|
}
|
|
68
|
+
const update = config.settings?.update;
|
|
69
|
+
if (update && !normalizeCliUpdatePolicy(update.policy)) {
|
|
70
|
+
delete config.settings?.update;
|
|
71
|
+
}
|
|
57
72
|
const license = config.settings?.license;
|
|
58
73
|
if (license && !trimValue(license.pkgUrl)) {
|
|
59
74
|
delete config.settings?.license;
|
|
@@ -68,6 +83,7 @@ function pruneSettings(config) {
|
|
|
68
83
|
}
|
|
69
84
|
if (config.settings &&
|
|
70
85
|
!config.settings.locale &&
|
|
86
|
+
!config.settings.update &&
|
|
71
87
|
!config.settings.license &&
|
|
72
88
|
!config.settings.docker &&
|
|
73
89
|
!config.settings.bin) {
|
|
@@ -78,6 +94,8 @@ export function getExplicitCliConfigValue(config, key) {
|
|
|
78
94
|
switch (key) {
|
|
79
95
|
case 'locale':
|
|
80
96
|
return trimValue(config.settings?.locale);
|
|
97
|
+
case 'update.policy':
|
|
98
|
+
return normalizeCliUpdatePolicy(config.settings?.update?.policy);
|
|
81
99
|
case 'license.pkg-url':
|
|
82
100
|
return trimValue(config.settings?.license?.pkgUrl);
|
|
83
101
|
case 'docker.network':
|
|
@@ -100,6 +118,8 @@ export function getEffectiveCliConfigValue(config, key) {
|
|
|
100
118
|
switch (key) {
|
|
101
119
|
case 'locale':
|
|
102
120
|
return resolveCliLocale(undefined, { configuredLocale: trimValue(config.settings?.locale) });
|
|
121
|
+
case 'update.policy':
|
|
122
|
+
return explicit ?? DEFAULT_UPDATE_POLICY;
|
|
103
123
|
case 'license.pkg-url':
|
|
104
124
|
return DEFAULT_LICENSE_PKG_URL;
|
|
105
125
|
case 'docker.network':
|
|
@@ -129,6 +149,13 @@ export function normalizeCliConfigValue(key, value) {
|
|
|
129
149
|
}
|
|
130
150
|
return locale;
|
|
131
151
|
}
|
|
152
|
+
if (key === 'update.policy') {
|
|
153
|
+
const policy = normalizeCliUpdatePolicy(normalized);
|
|
154
|
+
if (!policy) {
|
|
155
|
+
throw new Error(`Config key "${key}" must be one of: ${CLI_UPDATE_POLICY_OPTIONS.join(', ')}`);
|
|
156
|
+
}
|
|
157
|
+
return policy;
|
|
158
|
+
}
|
|
132
159
|
return normalized;
|
|
133
160
|
}
|
|
134
161
|
export async function loadCliConfig(options = {}) {
|
|
@@ -158,6 +185,12 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
158
185
|
case 'locale':
|
|
159
186
|
config.settings.locale = normalized;
|
|
160
187
|
break;
|
|
188
|
+
case 'update.policy':
|
|
189
|
+
config.settings.update = {
|
|
190
|
+
...(config.settings.update ?? {}),
|
|
191
|
+
policy: normalized,
|
|
192
|
+
};
|
|
193
|
+
break;
|
|
161
194
|
case 'license.pkg-url':
|
|
162
195
|
config.settings.license = {
|
|
163
196
|
...(config.settings.license ?? {}),
|
|
@@ -211,6 +244,11 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
211
244
|
case 'locale':
|
|
212
245
|
delete config.settings.locale;
|
|
213
246
|
break;
|
|
247
|
+
case 'update.policy':
|
|
248
|
+
if (config.settings.update) {
|
|
249
|
+
delete config.settings.update.policy;
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
214
252
|
case 'license.pkg-url':
|
|
215
253
|
if (config.settings.license) {
|
|
216
254
|
delete config.settings.license.pkgUrl;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
export function getCommandPathTokens(argv) {
|
|
10
|
+
const tokens = [];
|
|
11
|
+
for (const token of argv) {
|
|
12
|
+
if (!token) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (token.startsWith('-')) {
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
tokens.push(token);
|
|
19
|
+
}
|
|
20
|
+
return tokens;
|
|
21
|
+
}
|
|
22
|
+
export function formatCliEntryError(error, argv) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
const missingCommandMatch = message.match(/^Command (.+) not found\.$/);
|
|
25
|
+
if (!missingCommandMatch) {
|
|
26
|
+
return message;
|
|
27
|
+
}
|
|
28
|
+
const commandPathTokens = getCommandPathTokens(argv);
|
|
29
|
+
const attemptedCommand = commandPathTokens.join(' ') || missingCommandMatch[1];
|
|
30
|
+
const isApiCommand = commandPathTokens[0] === 'api';
|
|
31
|
+
if (isApiCommand) {
|
|
32
|
+
const helpCommandTokens = commandPathTokens.length > 2 ? commandPathTokens.slice(0, -1) : ['api'];
|
|
33
|
+
const helpCommand = `nb ${helpCommandTokens.join(' ')} --help`;
|
|
34
|
+
return [
|
|
35
|
+
`Unknown command: \`${attemptedCommand}\`.`,
|
|
36
|
+
`If this is a built-in command or a typo, run \`${helpCommand}\` to inspect the commands available under that API group.`,
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
39
|
+
return [
|
|
40
|
+
`Unknown command: \`${attemptedCommand}\`.`,
|
|
41
|
+
'If this is a built-in command or a typo, run `nb --help` to inspect available commands.',
|
|
42
|
+
`If \`${attemptedCommand}\` should be a runtime command from your NocoBase app, check whether the connected app exposes it, then retry the command.`,
|
|
43
|
+
].join('\n');
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
const DEFAULT_DOCKER_LOG_TAIL = 50;
|
|
11
|
+
export function startDockerLogFollower(containerName, options) {
|
|
12
|
+
const tail = Math.max(0, options?.tail ?? DEFAULT_DOCKER_LOG_TAIL);
|
|
13
|
+
const child = spawn('docker', ['logs', '--tail', String(tail), '--follow', containerName], {
|
|
14
|
+
stdio: 'inherit',
|
|
15
|
+
});
|
|
16
|
+
let settled = false;
|
|
17
|
+
let resolveClosed;
|
|
18
|
+
const closed = new Promise((resolve) => {
|
|
19
|
+
resolveClosed = resolve;
|
|
20
|
+
});
|
|
21
|
+
const settle = () => {
|
|
22
|
+
if (!settled) {
|
|
23
|
+
settled = true;
|
|
24
|
+
resolveClosed();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
child.once('error', settle);
|
|
28
|
+
child.once('close', settle);
|
|
29
|
+
return {
|
|
30
|
+
stop: async () => {
|
|
31
|
+
if (settled) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
if (!child.kill()) {
|
|
36
|
+
settle();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
settle();
|
|
41
|
+
}
|
|
42
|
+
await closed;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
package/dist/lib/env-auth.js
CHANGED
|
@@ -629,19 +629,19 @@ async function createLoopbackServer(state) {
|
|
|
629
629
|
});
|
|
630
630
|
let resolveWaiter;
|
|
631
631
|
let rejectWaiter;
|
|
632
|
-
const waitForCode = () => new Promise((
|
|
632
|
+
const waitForCode = () => new Promise((resolve, reject) => {
|
|
633
633
|
resolveWaiter = (code) => {
|
|
634
634
|
void close();
|
|
635
|
-
|
|
635
|
+
resolve(code);
|
|
636
636
|
};
|
|
637
637
|
rejectWaiter = (error) => {
|
|
638
638
|
void close();
|
|
639
|
-
|
|
639
|
+
reject(error);
|
|
640
640
|
};
|
|
641
641
|
});
|
|
642
642
|
const close = async () => {
|
|
643
|
-
await new Promise((
|
|
644
|
-
server.close(() =>
|
|
643
|
+
await new Promise((resolve) => {
|
|
644
|
+
server.close(() => resolve());
|
|
645
645
|
});
|
|
646
646
|
};
|
|
647
647
|
server.on('error', (error) => {
|
|
@@ -772,7 +772,7 @@ export async function resolveAccessToken(options) {
|
|
|
772
772
|
}
|
|
773
773
|
const baseUrl = options.baseUrl ?? env.baseUrl;
|
|
774
774
|
if (!baseUrl) {
|
|
775
|
-
throw new Error(`Env "${envName}" is missing a base URL.
|
|
775
|
+
throw new Error(`Env "${envName}" is missing a base URL. Update it with \`nb env update ${envName} --api-base-url <url>\` first.`);
|
|
776
776
|
}
|
|
777
777
|
printVerbose(`Refreshing OAuth session for env "${envName}"`);
|
|
778
778
|
return refreshOauthAccessToken({
|
|
@@ -793,7 +793,12 @@ export async function resolveServerRequestTarget(options) {
|
|
|
793
793
|
scope: options.scope,
|
|
794
794
|
});
|
|
795
795
|
if (!baseUrl) {
|
|
796
|
-
throw new Error(
|
|
796
|
+
throw new Error([
|
|
797
|
+
env ? `Env "${envName}" is missing a base URL.` : `Env "${envName}" is not configured.`,
|
|
798
|
+
env
|
|
799
|
+
? `Use --api-base-url or update env "${envName}" with \`nb env update ${envName} --api-base-url <url>\`.`
|
|
800
|
+
: `Use --api-base-url or run \`nb init --ui --env ${envName}\` first.`,
|
|
801
|
+
].join('\n'));
|
|
797
802
|
}
|
|
798
803
|
return { baseUrl, token };
|
|
799
804
|
}
|
|
@@ -807,8 +812,8 @@ export async function authenticateEnvWithBasic(options) {
|
|
|
807
812
|
? `Environment "${envName}" does not have an API base URL yet.`
|
|
808
813
|
: `Environment "${envName}" has not been set up yet.`,
|
|
809
814
|
env
|
|
810
|
-
? `Run \`nb env
|
|
811
|
-
: `Run \`nb env
|
|
815
|
+
? `Run \`nb env update ${envName} --api-base-url <url>\` to finish setting it up.`
|
|
816
|
+
: `Run \`nb init --ui --env ${envName}\` first.`,
|
|
812
817
|
]
|
|
813
818
|
.filter(Boolean)
|
|
814
819
|
.join('\n'));
|
|
@@ -864,8 +869,8 @@ export async function authenticateEnvWithOauth(options) {
|
|
|
864
869
|
? `Environment "${envName}" does not have an API base URL yet.`
|
|
865
870
|
: `Environment "${envName}" has not been set up yet.`,
|
|
866
871
|
env
|
|
867
|
-
? `Run \`nb env
|
|
868
|
-
: `Run \`nb env
|
|
872
|
+
? `Run \`nb env update ${envName} --api-base-url <url>\` to finish setting it up.`
|
|
873
|
+
: `Run \`nb init --ui --env ${envName}\` first.`,
|
|
869
874
|
]
|
|
870
875
|
.filter(Boolean)
|
|
871
876
|
.join('\n'));
|
|
@@ -912,10 +917,13 @@ export async function authenticateEnvWithOauth(options) {
|
|
|
912
917
|
const code = await new Promise((resolve, reject) => {
|
|
913
918
|
const timeout = setTimeout(() => reject(new Error(`OAuth sign-in timed out after 5 minutes. Run \`nb env auth ${envName}\` to try again.`)), OAUTH_LOGIN_TIMEOUT_MS);
|
|
914
919
|
timeout.unref?.();
|
|
915
|
-
callback
|
|
920
|
+
callback
|
|
921
|
+
.waitForCode()
|
|
922
|
+
.then((value) => {
|
|
916
923
|
clearTimeout(timeout);
|
|
917
924
|
resolve(value);
|
|
918
|
-
}
|
|
925
|
+
})
|
|
926
|
+
.catch((error) => {
|
|
919
927
|
clearTimeout(timeout);
|
|
920
928
|
reject(error);
|
|
921
929
|
});
|
|
@@ -292,6 +292,9 @@ export async function publishSourceSnapshot(params) {
|
|
|
292
292
|
if (publishError) {
|
|
293
293
|
throw publishError;
|
|
294
294
|
}
|
|
295
|
+
if (!result) {
|
|
296
|
+
throw new Error('Source snapshot publishing finished without a result.');
|
|
297
|
+
}
|
|
295
298
|
return result;
|
|
296
299
|
}
|
|
297
300
|
export function buildSuggestedInitCommand(result) {
|
|
@@ -299,7 +302,7 @@ export function buildSuggestedInitCommand(result) {
|
|
|
299
302
|
const normalizedRegistry = result.npmRegistry || `http://${host}:${port || DEFAULT_SOURCE_REGISTRY_PORT}`;
|
|
300
303
|
const suggestedEnv = ['snapshot', sanitizeEnvSegment(result.gitSha)].filter(Boolean).join('');
|
|
301
304
|
return [
|
|
302
|
-
`nb init --env ${suggestedEnv} --yes --source npm`,
|
|
305
|
+
`nb init --ui --env ${suggestedEnv} --yes --source npm`,
|
|
303
306
|
`--version ${result.version}`,
|
|
304
307
|
`--npm-registry=${normalizedRegistry}`,
|
|
305
308
|
].join(' ');
|
|
@@ -10,6 +10,7 @@ import fs from 'node:fs/promises';
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { confirm } from "./inquirer.js";
|
|
13
|
+
import { DEFAULT_UPDATE_POLICY, getExplicitCliConfigValue, loadCliConfig, } from './cli-config.js';
|
|
13
14
|
import { inspectSelfInstall, inspectSelfStatus, } from './self-manager.js';
|
|
14
15
|
import { inspectSkillsStatus } from './skills-manager.js';
|
|
15
16
|
import { resolveCliHomeDir } from './cli-home.js';
|
|
@@ -83,39 +84,53 @@ async function writeCurrentInstallEntry(updater) {
|
|
|
83
84
|
const state = await readState();
|
|
84
85
|
const installBinPath = getCurrentInstallBinPath();
|
|
85
86
|
const nextEntry = updater(getCurrentInstallEntry(state), state);
|
|
87
|
+
const entries = {
|
|
88
|
+
...(state.entries ?? {}),
|
|
89
|
+
};
|
|
90
|
+
if (nextEntry?.policy || nextEntry?.lastCheckedDate) {
|
|
91
|
+
entries[installBinPath] = nextEntry;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
delete entries[installBinPath];
|
|
95
|
+
}
|
|
86
96
|
await writeState({
|
|
87
97
|
...state,
|
|
88
|
-
entries:
|
|
89
|
-
...(state.entries ?? {}),
|
|
90
|
-
[installBinPath]: nextEntry,
|
|
91
|
-
},
|
|
98
|
+
entries: Object.keys(entries).length ? entries : undefined,
|
|
92
99
|
});
|
|
93
100
|
}
|
|
94
101
|
async function markChecked(now = new Date()) {
|
|
95
|
-
await writeCurrentInstallEntry((current
|
|
102
|
+
await writeCurrentInstallEntry((current) => {
|
|
96
103
|
return {
|
|
97
|
-
|
|
104
|
+
...current,
|
|
98
105
|
lastCheckedDate: todayStamp(now),
|
|
99
106
|
};
|
|
100
107
|
});
|
|
101
108
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
109
|
+
function resolveLegacyStartupUpdatePolicy(state) {
|
|
110
|
+
const policy = getCurrentInstallEntry(state)?.policy;
|
|
111
|
+
if (policy === 'disabled') {
|
|
112
|
+
return 'off';
|
|
113
|
+
}
|
|
114
|
+
if (policy === 'daily') {
|
|
115
|
+
return 'prompt';
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
110
118
|
}
|
|
111
|
-
async function
|
|
112
|
-
await
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
async function resolveStartupUpdatePolicy(state) {
|
|
120
|
+
const config = await loadCliConfig({ scope: 'global' });
|
|
121
|
+
const explicit = getExplicitCliConfigValue(config, 'update.policy');
|
|
122
|
+
return explicit ?? resolveLegacyStartupUpdatePolicy(state) ?? DEFAULT_UPDATE_POLICY;
|
|
123
|
+
}
|
|
124
|
+
export async function clearLegacyStartupUpdatePolicyForCurrentInstall() {
|
|
125
|
+
const state = await readState();
|
|
126
|
+
const current = getCurrentInstallEntry(state);
|
|
127
|
+
if (!current?.policy) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
await writeCurrentInstallEntry(() => ({
|
|
131
|
+
lastCheckedDate: current.lastCheckedDate,
|
|
132
|
+
}));
|
|
133
|
+
return true;
|
|
119
134
|
}
|
|
120
135
|
export async function shouldRunStartupUpdateCheck(argv, now = new Date()) {
|
|
121
136
|
if (process.env[NB_SKIP_STARTUP_UPDATE_ENV] === '1') {
|
|
@@ -125,19 +140,14 @@ export async function shouldRunStartupUpdateCheck(argv, now = new Date()) {
|
|
|
125
140
|
return false;
|
|
126
141
|
}
|
|
127
142
|
const state = await readState();
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
143
|
+
const policy = await resolveStartupUpdatePolicy(state);
|
|
144
|
+
if (policy === 'off') {
|
|
130
145
|
return false;
|
|
131
146
|
}
|
|
132
|
-
if (currentEntry?.policy === 'daily') {
|
|
133
|
-
return readCurrentInstallLastCheckedDate(state) !== todayStamp(now);
|
|
134
|
-
}
|
|
135
147
|
const selfInstall = await inspectSelfInstall();
|
|
136
148
|
if (!shouldEnableStartupUpdateForInstallMethod(selfInstall.installMethod)) {
|
|
137
|
-
await disableStartupUpdateForCurrentInstall();
|
|
138
149
|
return false;
|
|
139
150
|
}
|
|
140
|
-
await enableDailyStartupUpdateForCurrentInstall();
|
|
141
151
|
return readCurrentInstallLastCheckedDate(state) !== todayStamp(now);
|
|
142
152
|
}
|
|
143
153
|
export function shouldEnableStartupUpdateForInstallMethod(installMethod) {
|
|
@@ -189,14 +199,13 @@ function buildPromptMessage(selfStatus, skillsStatus) {
|
|
|
189
199
|
return lines.join('\n');
|
|
190
200
|
}
|
|
191
201
|
function buildUpdateCommands(selfStatus, skillsStatus) {
|
|
192
|
-
const commands = [];
|
|
193
202
|
if (selfStatus.updateAvailable && selfStatus.updatable) {
|
|
194
|
-
|
|
203
|
+
return [skillsStatus.updateAvailable === true ? 'nb self update --yes --skills' : 'nb self update --yes'];
|
|
195
204
|
}
|
|
196
205
|
if (skillsStatus.updateAvailable === true) {
|
|
197
|
-
|
|
206
|
+
return ['nb skills update --yes'];
|
|
198
207
|
}
|
|
199
|
-
return
|
|
208
|
+
return [];
|
|
200
209
|
}
|
|
201
210
|
function buildNonInteractiveWarning(selfStatus, skillsStatus) {
|
|
202
211
|
const commands = buildUpdateCommands(selfStatus, skillsStatus);
|
|
@@ -233,26 +242,36 @@ function buildDeclinedWarning(selfStatus, skillsStatus) {
|
|
|
233
242
|
'You may run into compatibility issues until you update.',
|
|
234
243
|
].join(' ');
|
|
235
244
|
}
|
|
236
|
-
async function runStartupUpdates() {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
}
|
|
245
|
+
async function runStartupUpdates(selfStatus, skillsStatus) {
|
|
246
|
+
if (selfStatus.updateAvailable && selfStatus.updatable) {
|
|
247
|
+
const args = ['self', 'update', '--yes'];
|
|
248
|
+
if (skillsStatus.updateAvailable === true) {
|
|
249
|
+
args.push('--skills');
|
|
250
|
+
}
|
|
251
|
+
await run('nb', args, {
|
|
252
|
+
stdio: 'inherit',
|
|
253
|
+
env: {
|
|
254
|
+
[NB_SKIP_STARTUP_UPDATE_ENV]: '1',
|
|
255
|
+
},
|
|
256
|
+
errorName: 'nb self update',
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (skillsStatus.updateAvailable === true) {
|
|
261
|
+
await run('nb', ['skills', 'update', '--yes'], {
|
|
262
|
+
stdio: 'inherit',
|
|
263
|
+
env: {
|
|
264
|
+
[NB_SKIP_STARTUP_UPDATE_ENV]: '1',
|
|
265
|
+
},
|
|
266
|
+
errorName: 'nb skills update',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
251
269
|
}
|
|
252
|
-
export async function
|
|
270
|
+
export async function maybeRunStartupUpdate(argv) {
|
|
253
271
|
if (!(await shouldRunStartupUpdateCheck(argv))) {
|
|
254
272
|
return { kind: 'skipped' };
|
|
255
273
|
}
|
|
274
|
+
const policy = await resolveStartupUpdatePolicy(await readState());
|
|
256
275
|
const selfStatus = await inspectSelfStatus();
|
|
257
276
|
const skillsStatus = await inspectSkillsStatus();
|
|
258
277
|
if (!hasPendingUpdates(selfStatus, skillsStatus)) {
|
|
@@ -264,6 +283,11 @@ export async function maybeRunStartupUpdatePrompt(argv) {
|
|
|
264
283
|
await markChecked();
|
|
265
284
|
return { kind: 'warned' };
|
|
266
285
|
}
|
|
286
|
+
if (policy === 'auto') {
|
|
287
|
+
await runStartupUpdates(selfStatus, skillsStatus);
|
|
288
|
+
await markChecked();
|
|
289
|
+
return { kind: 'updated' };
|
|
290
|
+
}
|
|
267
291
|
let answer = false;
|
|
268
292
|
try {
|
|
269
293
|
answer = await confirm({
|
|
@@ -279,7 +303,7 @@ export async function maybeRunStartupUpdatePrompt(argv) {
|
|
|
279
303
|
await markChecked();
|
|
280
304
|
return { kind: 'declined' };
|
|
281
305
|
}
|
|
282
|
-
await runStartupUpdates();
|
|
306
|
+
await runStartupUpdates(selfStatus, skillsStatus);
|
|
283
307
|
await markChecked();
|
|
284
308
|
return { kind: 'updated' };
|
|
285
309
|
}
|
package/dist/locale/en-US.json
CHANGED
|
@@ -370,6 +370,12 @@
|
|
|
370
370
|
"description": "Set up the first admin."
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
|
+
},
|
|
374
|
+
"env": {
|
|
375
|
+
"messages": {
|
|
376
|
+
"noEnvsConfigured": "No envs configured.",
|
|
377
|
+
"noEnvsConfiguredHelp": "Run `nb init --ui` to create one first."
|
|
378
|
+
}
|
|
373
379
|
}
|
|
374
380
|
},
|
|
375
381
|
"apiCommandCompat": {
|
package/dist/locale/zh-CN.json
CHANGED