@nocobase/cli 2.1.0-alpha.40 → 2.1.0-alpha.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -8
- package/README.zh-CN.md +20 -8
- package/bin/run.js +9 -37
- package/dist/commands/app/destroy.js +225 -0
- package/dist/commands/app/down.js +24 -254
- package/dist/commands/app/restart.js +12 -5
- package/dist/commands/app/shared.js +122 -0
- package/dist/commands/app/start.js +41 -59
- package/dist/commands/app/stop.js +72 -26
- package/dist/commands/app/upgrade.js +312 -429
- package/dist/commands/config/delete.js +8 -1
- package/dist/commands/config/get.js +5 -0
- package/dist/commands/config/set.js +10 -1
- package/dist/commands/db/ps.js +5 -18
- package/dist/commands/db/start.js +23 -8
- package/dist/commands/env/add.js +66 -6
- package/dist/commands/env/auth.js +86 -27
- package/dist/commands/env/info.js +52 -8
- package/dist/commands/env/list.js +6 -7
- package/dist/commands/env/shared.js +41 -3
- package/dist/commands/env/status.js +10 -7
- package/dist/commands/init.js +214 -136
- package/dist/commands/install.js +364 -294
- package/dist/commands/license/plugins/shared.js +9 -3
- package/dist/commands/license/plugins/sync.js +54 -25
- package/dist/commands/self/update.js +58 -1
- package/dist/commands/source/dev.js +5 -5
- package/dist/commands/source/download.js +29 -25
- package/dist/generated/command-registry.js +3 -2
- package/dist/lib/api-client.js +6 -0
- package/dist/lib/api-command-compat.js +641 -0
- package/dist/lib/app-health.js +31 -18
- package/dist/lib/app-managed-resources.js +1 -1
- package/dist/lib/app-runtime.js +2 -2
- package/dist/lib/auth-store.js +56 -19
- package/dist/lib/backup.js +3 -3
- package/dist/lib/bootstrap.js +14 -8
- package/dist/lib/cli-config.js +137 -4
- package/dist/lib/cli-entry-error.js +44 -0
- package/dist/lib/cli-locale.js +19 -7
- package/dist/lib/db-connection-check.js +61 -0
- package/dist/lib/docker-log-stream.js +45 -0
- package/dist/lib/env-auth.js +98 -11
- package/dist/lib/env-config.js +3 -2
- package/dist/lib/env-guard.js +1 -1
- package/dist/lib/generated-command.js +17 -0
- package/dist/lib/prompt-validators.js +23 -5
- package/dist/lib/prompt-web-ui.js +143 -19
- package/dist/lib/run-npm.js +133 -23
- package/dist/lib/skills-manager.js +80 -4
- package/dist/lib/source-publish.js +4 -1
- package/dist/lib/startup-update.js +74 -50
- package/dist/lib/ui.js +4 -1
- package/dist/locale/en-US.json +92 -5
- package/dist/locale/zh-CN.json +92 -5
- package/package.json +34 -2
package/dist/lib/app-health.js
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
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 {
|
|
9
|
+
import { startDockerLogFollower } from './docker-log-stream.js';
|
|
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;
|
|
12
13
|
const APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS = 5_000;
|
|
14
|
+
const APP_HEALTH_CHECK_PROGRESS_LOG_INTERVAL_MS = 10_000;
|
|
13
15
|
function trimValue(value) {
|
|
14
16
|
return String(value ?? '').trim();
|
|
15
17
|
}
|
|
@@ -92,35 +94,46 @@ export async function waitForAppReady(params) {
|
|
|
92
94
|
}
|
|
93
95
|
const healthCheckUrl = buildHealthCheckUrl(apiBaseUrl);
|
|
94
96
|
const startedAt = Date.now();
|
|
97
|
+
const timeoutMs = params.timeoutMs ?? APP_HEALTH_CHECK_TIMEOUT_MS;
|
|
98
|
+
const intervalMs = params.intervalMs ?? APP_HEALTH_CHECK_INTERVAL_MS;
|
|
99
|
+
const progressLogIntervalMs = Math.max(1, params.progressLogIntervalMs ?? APP_HEALTH_CHECK_PROGRESS_LOG_INTERVAL_MS);
|
|
95
100
|
let lastMessage = 'No response yet';
|
|
96
|
-
let
|
|
97
|
-
|
|
101
|
+
let nextProgressLogAt = startedAt + progressLogIntervalMs;
|
|
102
|
+
printInfo(`Waiting for NocoBase to become ready for "${params.envName}"...`);
|
|
103
|
+
const dockerLogFollower = params.verbose && params.containerName ? startDockerLogFollower(params.containerName) : undefined;
|
|
98
104
|
try {
|
|
99
|
-
while (Date.now() - startedAt <
|
|
105
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
100
106
|
const result = await requestAppHealthCheck({
|
|
101
107
|
healthCheckUrl,
|
|
102
108
|
fetchImpl: params.fetchImpl,
|
|
109
|
+
requestTimeoutMs: params.requestTimeoutMs,
|
|
103
110
|
});
|
|
104
111
|
if (result.ok) {
|
|
105
|
-
stopTask();
|
|
106
|
-
spinnerActive = false;
|
|
107
112
|
return;
|
|
108
113
|
}
|
|
109
114
|
lastMessage = result.message;
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
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));
|
|
113
128
|
}
|
|
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}`);
|
|
114
135
|
}
|
|
115
136
|
finally {
|
|
116
|
-
|
|
117
|
-
stopTask();
|
|
118
|
-
}
|
|
137
|
+
await dockerLogFollower?.stop();
|
|
119
138
|
}
|
|
120
|
-
const hints = [
|
|
121
|
-
params.logHint,
|
|
122
|
-
params.containerName ? `docker logs ${params.containerName}` : undefined,
|
|
123
|
-
].filter(Boolean);
|
|
124
|
-
const hintText = hints.length > 0 ? ` ${hints.join(' ')}` : '';
|
|
125
|
-
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}`);
|
|
126
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) {
|
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
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { promises as fs } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath
|
|
11
|
+
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
|
|
12
|
+
import { normalizeCliLocale } from './cli-locale.js';
|
|
12
13
|
import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
|
|
13
14
|
function normalizeStoredEnvKind(value) {
|
|
14
15
|
const kind = String(value ?? '').trim();
|
|
@@ -24,13 +25,27 @@ function normalizeOptionalString(value) {
|
|
|
24
25
|
const normalized = String(value ?? '').trim();
|
|
25
26
|
return normalized || undefined;
|
|
26
27
|
}
|
|
28
|
+
function normalizeOptionalCliLocale(value) {
|
|
29
|
+
const normalized = normalizeOptionalString(value);
|
|
30
|
+
if (!normalized) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return normalizeCliLocale(normalized);
|
|
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
|
+
}
|
|
27
42
|
export function readEnvApiBaseUrl(config) {
|
|
28
43
|
if (!config) {
|
|
29
44
|
return undefined;
|
|
30
45
|
}
|
|
31
|
-
return (normalizeOptionalString(config.apiBaseUrl)
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
return (normalizeOptionalString(config.apiBaseUrl) ??
|
|
47
|
+
normalizeOptionalString(config.baseUrl) ??
|
|
48
|
+
normalizeOptionalString(config.apibaseUrl));
|
|
34
49
|
}
|
|
35
50
|
export function resolveEnvKind(config) {
|
|
36
51
|
if (!config) {
|
|
@@ -70,9 +85,13 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
70
85
|
}
|
|
71
86
|
function normalizeAuthConfig(config) {
|
|
72
87
|
const settings = config.settings ?? {};
|
|
88
|
+
const locale = normalizeOptionalCliLocale(settings.locale);
|
|
89
|
+
const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
|
|
73
90
|
return {
|
|
74
91
|
name: config.name || config.dockerResourcePrefix,
|
|
75
92
|
settings: {
|
|
93
|
+
...(locale ? { locale } : {}),
|
|
94
|
+
...(updatePolicy ? { update: { policy: updatePolicy } } : {}),
|
|
76
95
|
...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
|
|
77
96
|
...(settings.docker?.network || settings.docker?.containerPrefix
|
|
78
97
|
? {
|
|
@@ -84,8 +103,19 @@ function normalizeAuthConfig(config) {
|
|
|
84
103
|
},
|
|
85
104
|
}
|
|
86
105
|
: {}),
|
|
106
|
+
...(settings.bin?.docker || settings.bin?.git || settings.bin?.yarn
|
|
107
|
+
? {
|
|
108
|
+
bin: {
|
|
109
|
+
...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
|
|
110
|
+
...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
|
|
111
|
+
...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
: {}),
|
|
87
115
|
},
|
|
88
|
-
lastEnv: config.lastEnv ||
|
|
116
|
+
lastEnv: config.lastEnv ||
|
|
117
|
+
config.currentEnv ||
|
|
118
|
+
'default',
|
|
89
119
|
envs: Object.fromEntries(Object.entries(config.envs || {}).map(([envName, entry]) => [envName, normalizeEnvConfigEntry(entry) ?? {}])),
|
|
90
120
|
};
|
|
91
121
|
}
|
|
@@ -222,10 +252,11 @@ export class Env {
|
|
|
222
252
|
export async function getEnv(envName, options = {}) {
|
|
223
253
|
const { config: snapshot, ...loadOptions } = options;
|
|
224
254
|
const config = snapshot ?? (await loadAuthConfig(loadOptions));
|
|
225
|
-
const resolved = envName?.trim() ||
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
255
|
+
const resolved = envName?.trim() ||
|
|
256
|
+
(await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
|
|
257
|
+
scope: loadOptions.scope,
|
|
258
|
+
lastEnv: config.lastEnv,
|
|
259
|
+
}));
|
|
229
260
|
const envConfig = config.envs[resolved];
|
|
230
261
|
if (!envConfig) {
|
|
231
262
|
return undefined;
|
|
@@ -260,37 +291,40 @@ async function writeEnv(envName, updater, options = {}) {
|
|
|
260
291
|
await saveAuthConfig(config, options);
|
|
261
292
|
}
|
|
262
293
|
function normalizeConfiguredAuthType(value) {
|
|
263
|
-
return value === 'token' || value === 'oauth' ? value : undefined;
|
|
294
|
+
return value === 'basic' || value === 'token' || value === 'oauth' ? value : undefined;
|
|
264
295
|
}
|
|
265
296
|
export function resolveConfiguredAuthType(config) {
|
|
266
297
|
return normalizeConfiguredAuthType(config?.authType) ?? normalizeConfiguredAuthType(config?.auth?.type);
|
|
267
298
|
}
|
|
268
299
|
export async function upsertEnv(envName, config, options = {}) {
|
|
269
300
|
await writeEnv(envName, (previous) => {
|
|
270
|
-
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, ...rest } = config;
|
|
301
|
+
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
|
|
271
302
|
const nextApiBaseUrl = readEnvApiBaseUrl(config);
|
|
272
303
|
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
273
304
|
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
274
305
|
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
275
306
|
const requestedAuthType = normalizeConfiguredAuthType(authType);
|
|
276
307
|
const nextAuthType = requestedAuthType ?? (accessToken ? 'token' : previousAuthType);
|
|
308
|
+
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) ?? previous?.authUsername : undefined;
|
|
277
309
|
const nextAuth = accessToken
|
|
278
310
|
? {
|
|
279
311
|
type: 'token',
|
|
280
312
|
accessToken,
|
|
281
313
|
}
|
|
282
|
-
: nextAuthType === '
|
|
283
|
-
?
|
|
284
|
-
:
|
|
314
|
+
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
315
|
+
? previous.auth
|
|
316
|
+
: undefined;
|
|
285
317
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
286
318
|
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
319
|
+
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
287
320
|
return {
|
|
288
321
|
...previous,
|
|
289
322
|
apiBaseUrl: nextApiBaseUrl,
|
|
290
323
|
authType: nextAuthType,
|
|
324
|
+
authUsername: nextAuthUsername,
|
|
291
325
|
auth: nextAuth,
|
|
292
326
|
...rest,
|
|
293
|
-
runtime: baseUrlChanged || authChanged || authTypeChanged ? undefined : previous?.runtime,
|
|
327
|
+
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
294
328
|
};
|
|
295
329
|
}, options);
|
|
296
330
|
}
|
|
@@ -302,22 +336,25 @@ export async function updateEnvConnection(envName, updates, options = {}) {
|
|
|
302
336
|
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
303
337
|
const requestedAuthType = normalizeConfiguredAuthType(updates.authType);
|
|
304
338
|
const nextAuthType = requestedAuthType ?? (updates.accessToken ? 'token' : previousAuthType);
|
|
339
|
+
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(updates.authUsername) ?? previous?.authUsername : undefined;
|
|
305
340
|
const nextAuth = updates.accessToken
|
|
306
341
|
? {
|
|
307
342
|
type: 'token',
|
|
308
343
|
accessToken: updates.accessToken,
|
|
309
344
|
}
|
|
310
|
-
: nextAuthType === '
|
|
311
|
-
?
|
|
312
|
-
:
|
|
345
|
+
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
346
|
+
? previous.auth
|
|
347
|
+
: undefined;
|
|
313
348
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
314
349
|
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
350
|
+
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
315
351
|
return {
|
|
316
352
|
...previous,
|
|
317
353
|
...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
|
|
318
354
|
authType: nextAuthType,
|
|
355
|
+
authUsername: nextAuthUsername,
|
|
319
356
|
auth: nextAuth,
|
|
320
|
-
runtime: baseUrlChanged || authChanged || authTypeChanged ? undefined : previous?.runtime,
|
|
357
|
+
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
321
358
|
};
|
|
322
359
|
}, options);
|
|
323
360
|
}
|
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
|
@@ -8,13 +8,24 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { loadExactAuthConfig, saveAuthConfig } from './auth-store.js';
|
|
10
10
|
import { resolveDefaultConfigScope } from './cli-home.js';
|
|
11
|
+
import { CLI_LOCALE_FLAG_OPTIONS, normalizeCliLocale, resolveCliLocale } from './cli-locale.js';
|
|
11
12
|
export const DEFAULT_LICENSE_PKG_URL = 'https://pkg.nocobase.com/';
|
|
12
13
|
export const DEFAULT_DOCKER_NETWORK = 'nocobase';
|
|
13
14
|
export const DEFAULT_DOCKER_CONTAINER_PREFIX = 'nb';
|
|
15
|
+
export const DEFAULT_DOCKER_BIN = 'docker';
|
|
16
|
+
export const DEFAULT_GIT_BIN = 'git';
|
|
17
|
+
export const DEFAULT_YARN_BIN = 'yarn';
|
|
18
|
+
export const CLI_UPDATE_POLICY_OPTIONS = ['prompt', 'auto', 'off'];
|
|
19
|
+
export const DEFAULT_UPDATE_POLICY = 'prompt';
|
|
14
20
|
export const SUPPORTED_CLI_CONFIG_KEYS = [
|
|
21
|
+
'locale',
|
|
22
|
+
'update.policy',
|
|
15
23
|
'license.pkg-url',
|
|
16
24
|
'docker.network',
|
|
17
25
|
'docker.container-prefix',
|
|
26
|
+
'bin.docker',
|
|
27
|
+
'bin.git',
|
|
28
|
+
'bin.yarn',
|
|
18
29
|
];
|
|
19
30
|
function trimValue(value) {
|
|
20
31
|
const text = String(value ?? '').trim();
|
|
@@ -34,13 +45,30 @@ export function assertSupportedCliConfigKey(value) {
|
|
|
34
45
|
}
|
|
35
46
|
return value;
|
|
36
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
|
+
}
|
|
37
55
|
function cloneSettings(config) {
|
|
38
56
|
return {
|
|
57
|
+
...(config.settings?.locale ? { locale: trimValue(config.settings.locale) } : {}),
|
|
58
|
+
update: config.settings?.update ? { ...config.settings.update } : undefined,
|
|
39
59
|
license: config.settings?.license ? { ...config.settings.license } : undefined,
|
|
40
60
|
docker: config.settings?.docker ? { ...config.settings.docker } : undefined,
|
|
61
|
+
bin: config.settings?.bin ? { ...config.settings.bin } : undefined,
|
|
41
62
|
};
|
|
42
63
|
}
|
|
43
64
|
function pruneSettings(config) {
|
|
65
|
+
if (config.settings && !trimValue(config.settings.locale)) {
|
|
66
|
+
delete config.settings.locale;
|
|
67
|
+
}
|
|
68
|
+
const update = config.settings?.update;
|
|
69
|
+
if (update && !normalizeCliUpdatePolicy(update.policy)) {
|
|
70
|
+
delete config.settings?.update;
|
|
71
|
+
}
|
|
44
72
|
const license = config.settings?.license;
|
|
45
73
|
if (license && !trimValue(license.pkgUrl)) {
|
|
46
74
|
delete config.settings?.license;
|
|
@@ -49,34 +77,61 @@ function pruneSettings(config) {
|
|
|
49
77
|
if (docker && !trimValue(docker.network) && !trimValue(docker.containerPrefix)) {
|
|
50
78
|
delete config.settings?.docker;
|
|
51
79
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
const bin = config.settings?.bin;
|
|
81
|
+
if (bin && !trimValue(bin.docker) && !trimValue(bin.git) && !trimValue(bin.yarn)) {
|
|
82
|
+
delete config.settings?.bin;
|
|
83
|
+
}
|
|
84
|
+
if (config.settings &&
|
|
85
|
+
!config.settings.locale &&
|
|
86
|
+
!config.settings.update &&
|
|
87
|
+
!config.settings.license &&
|
|
88
|
+
!config.settings.docker &&
|
|
89
|
+
!config.settings.bin) {
|
|
55
90
|
delete config.settings;
|
|
56
91
|
}
|
|
57
92
|
}
|
|
58
93
|
export function getExplicitCliConfigValue(config, key) {
|
|
59
94
|
switch (key) {
|
|
95
|
+
case 'locale':
|
|
96
|
+
return trimValue(config.settings?.locale);
|
|
97
|
+
case 'update.policy':
|
|
98
|
+
return normalizeCliUpdatePolicy(config.settings?.update?.policy);
|
|
60
99
|
case 'license.pkg-url':
|
|
61
100
|
return trimValue(config.settings?.license?.pkgUrl);
|
|
62
101
|
case 'docker.network':
|
|
63
102
|
return trimValue(config.settings?.docker?.network);
|
|
64
103
|
case 'docker.container-prefix':
|
|
65
104
|
return trimValue(config.settings?.docker?.containerPrefix);
|
|
105
|
+
case 'bin.docker':
|
|
106
|
+
return trimValue(config.settings?.bin?.docker);
|
|
107
|
+
case 'bin.git':
|
|
108
|
+
return trimValue(config.settings?.bin?.git);
|
|
109
|
+
case 'bin.yarn':
|
|
110
|
+
return trimValue(config.settings?.bin?.yarn);
|
|
66
111
|
}
|
|
67
112
|
}
|
|
68
113
|
export function getEffectiveCliConfigValue(config, key) {
|
|
69
114
|
const explicit = getExplicitCliConfigValue(config, key);
|
|
70
|
-
if (explicit) {
|
|
115
|
+
if (explicit && key !== 'locale') {
|
|
71
116
|
return explicit;
|
|
72
117
|
}
|
|
73
118
|
switch (key) {
|
|
119
|
+
case 'locale':
|
|
120
|
+
return resolveCliLocale(undefined, { configuredLocale: trimValue(config.settings?.locale) });
|
|
121
|
+
case 'update.policy':
|
|
122
|
+
return explicit ?? DEFAULT_UPDATE_POLICY;
|
|
74
123
|
case 'license.pkg-url':
|
|
75
124
|
return DEFAULT_LICENSE_PKG_URL;
|
|
76
125
|
case 'docker.network':
|
|
77
126
|
return trimValue(config.name) || DEFAULT_DOCKER_NETWORK;
|
|
78
127
|
case 'docker.container-prefix':
|
|
79
128
|
return trimValue(config.name) || DEFAULT_DOCKER_CONTAINER_PREFIX;
|
|
129
|
+
case 'bin.docker':
|
|
130
|
+
return DEFAULT_DOCKER_BIN;
|
|
131
|
+
case 'bin.git':
|
|
132
|
+
return DEFAULT_GIT_BIN;
|
|
133
|
+
case 'bin.yarn':
|
|
134
|
+
return DEFAULT_YARN_BIN;
|
|
80
135
|
}
|
|
81
136
|
}
|
|
82
137
|
export function normalizeCliConfigValue(key, value) {
|
|
@@ -87,6 +142,20 @@ export function normalizeCliConfigValue(key, value) {
|
|
|
87
142
|
if (key === 'license.pkg-url') {
|
|
88
143
|
return normalized.replace(/\/+$/, '') + '/';
|
|
89
144
|
}
|
|
145
|
+
if (key === 'locale') {
|
|
146
|
+
const locale = normalizeCliLocale(normalized);
|
|
147
|
+
if (!locale) {
|
|
148
|
+
throw new Error(`Config key "${key}" must be one of: ${CLI_LOCALE_FLAG_OPTIONS.join(', ')}`);
|
|
149
|
+
}
|
|
150
|
+
return locale;
|
|
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
|
+
}
|
|
90
159
|
return normalized;
|
|
91
160
|
}
|
|
92
161
|
export async function loadCliConfig(options = {}) {
|
|
@@ -113,6 +182,15 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
113
182
|
const normalized = normalizeCliConfigValue(key, value);
|
|
114
183
|
config.settings = cloneSettings(config);
|
|
115
184
|
switch (key) {
|
|
185
|
+
case 'locale':
|
|
186
|
+
config.settings.locale = normalized;
|
|
187
|
+
break;
|
|
188
|
+
case 'update.policy':
|
|
189
|
+
config.settings.update = {
|
|
190
|
+
...(config.settings.update ?? {}),
|
|
191
|
+
policy: normalized,
|
|
192
|
+
};
|
|
193
|
+
break;
|
|
116
194
|
case 'license.pkg-url':
|
|
117
195
|
config.settings.license = {
|
|
118
196
|
...(config.settings.license ?? {}),
|
|
@@ -131,6 +209,24 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
131
209
|
containerPrefix: normalized,
|
|
132
210
|
};
|
|
133
211
|
break;
|
|
212
|
+
case 'bin.docker':
|
|
213
|
+
config.settings.bin = {
|
|
214
|
+
...(config.settings.bin ?? {}),
|
|
215
|
+
docker: normalized,
|
|
216
|
+
};
|
|
217
|
+
break;
|
|
218
|
+
case 'bin.git':
|
|
219
|
+
config.settings.bin = {
|
|
220
|
+
...(config.settings.bin ?? {}),
|
|
221
|
+
git: normalized,
|
|
222
|
+
};
|
|
223
|
+
break;
|
|
224
|
+
case 'bin.yarn':
|
|
225
|
+
config.settings.bin = {
|
|
226
|
+
...(config.settings.bin ?? {}),
|
|
227
|
+
yarn: normalized,
|
|
228
|
+
};
|
|
229
|
+
break;
|
|
134
230
|
}
|
|
135
231
|
pruneSettings(config);
|
|
136
232
|
await saveAuthConfig(config, scope);
|
|
@@ -145,6 +241,14 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
145
241
|
}
|
|
146
242
|
config.settings = cloneSettings(config);
|
|
147
243
|
switch (key) {
|
|
244
|
+
case 'locale':
|
|
245
|
+
delete config.settings.locale;
|
|
246
|
+
break;
|
|
247
|
+
case 'update.policy':
|
|
248
|
+
if (config.settings.update) {
|
|
249
|
+
delete config.settings.update.policy;
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
148
252
|
case 'license.pkg-url':
|
|
149
253
|
if (config.settings.license) {
|
|
150
254
|
delete config.settings.license.pkgUrl;
|
|
@@ -160,6 +264,21 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
160
264
|
delete config.settings.docker.containerPrefix;
|
|
161
265
|
}
|
|
162
266
|
break;
|
|
267
|
+
case 'bin.docker':
|
|
268
|
+
if (config.settings.bin) {
|
|
269
|
+
delete config.settings.bin.docker;
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
case 'bin.git':
|
|
273
|
+
if (config.settings.bin) {
|
|
274
|
+
delete config.settings.bin.git;
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
case 'bin.yarn':
|
|
278
|
+
if (config.settings.bin) {
|
|
279
|
+
delete config.settings.bin.yarn;
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
163
282
|
}
|
|
164
283
|
pruneSettings(config);
|
|
165
284
|
await saveAuthConfig(config, scope);
|
|
@@ -174,3 +293,17 @@ export async function resolveDockerContainerPrefix(options = {}) {
|
|
|
174
293
|
export async function resolveLicensePkgUrlFromConfig(options = {}) {
|
|
175
294
|
return await getCliConfigValue('license.pkg-url', options);
|
|
176
295
|
}
|
|
296
|
+
const CONFIGURABLE_COMMAND_KEYS = {
|
|
297
|
+
docker: 'bin.docker',
|
|
298
|
+
git: 'bin.git',
|
|
299
|
+
yarn: 'bin.yarn',
|
|
300
|
+
};
|
|
301
|
+
export function isConfigurableCommandName(value) {
|
|
302
|
+
return Object.prototype.hasOwnProperty.call(CONFIGURABLE_COMMAND_KEYS, value);
|
|
303
|
+
}
|
|
304
|
+
export async function resolveConfiguredCommandName(commandName, options = {}) {
|
|
305
|
+
if (!isConfigurableCommandName(commandName)) {
|
|
306
|
+
return commandName;
|
|
307
|
+
}
|
|
308
|
+
return await getCliConfigValue(CONFIGURABLE_COMMAND_KEYS[commandName], options);
|
|
309
|
+
}
|
|
@@ -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
|
+
}
|