@nocobase/cli 2.3.0-beta.6 → 2.4.0-alpha.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/dist/commands/api/swagger/get.js +96 -0
- package/dist/commands/api/swagger/index.js +20 -0
- package/dist/commands/api/swagger/list.js +92 -0
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/add.js +6 -0
- package/dist/commands/env/update.js +13 -0
- package/dist/commands/init.js +51 -5
- package/dist/commands/install.js +60 -3
- package/dist/commands/portal/config.js +99 -0
- package/dist/commands/portal/create.js +96 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +126 -0
- package/dist/commands/portal/dev.js +73 -0
- package/dist/commands/portal/index.js +20 -0
- package/dist/commands/portal/info.js +82 -0
- package/dist/commands/portal/list.js +98 -0
- package/dist/commands/portal/pull.js +113 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/source/dev.js +1 -1
- package/dist/lib/api-client.js +35 -9
- package/dist/lib/app-client-entry-mode.js +28 -0
- package/dist/lib/app-managed-resources.js +2 -0
- package/dist/lib/auth-store.js +55 -2
- package/dist/lib/bootstrap.js +3 -1
- package/dist/lib/cli-config.js +20 -1
- package/dist/lib/env-auth.js +2 -36
- package/dist/lib/env-command-config.js +1 -0
- package/dist/lib/env-config.js +11 -0
- package/dist/lib/env-portal-config.js +30 -0
- package/dist/lib/env-proxy.js +154 -7
- package/dist/lib/managed-env-file.js +119 -9
- package/dist/lib/managed-init-env.js +4 -1
- package/dist/lib/portal-build-html.js +27 -0
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-config.js +119 -0
- package/dist/lib/portal-configure.js +110 -0
- package/dist/lib/portal-create.js +515 -0
- package/dist/lib/portal-deploy.js +266 -0
- package/dist/lib/portal-destroy.js +114 -0
- package/dist/lib/portal-dev.js +78 -0
- package/dist/lib/portal-env-files.js +54 -0
- package/dist/lib/portal-info.js +28 -0
- package/dist/lib/portal-list.js +205 -0
- package/dist/lib/portal-path-safety.js +76 -0
- package/dist/lib/portal-source.js +692 -0
- package/dist/lib/prompt-catalog-core.js +2 -2
- package/dist/lib/prompt-catalog-terminal.js +4 -5
- package/dist/lib/prompt-web-ui.js +12 -6
- package/dist/lib/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/dist/lib/run-npm.js +85 -20
- package/dist/lib/swagger-command.js +52 -0
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +245 -1
- package/dist/locale/zh-CN.json +245 -1
- package/package.json +5 -2
|
@@ -60,7 +60,7 @@ export function mergedBoolean(key, def, iv, useYesInitial) {
|
|
|
60
60
|
}
|
|
61
61
|
return def.initialValue ?? true;
|
|
62
62
|
}
|
|
63
|
-
export function mergedSelect(key, def, iv, useYesInitial) {
|
|
63
|
+
export function mergedSelect(key, def, iv, useYesInitial, valuesSoFar = {}) {
|
|
64
64
|
const enabledValueList = enabledSelectOptionValues(def.options);
|
|
65
65
|
if (hasIvKey(iv, key)) {
|
|
66
66
|
const s = String(iv[key]);
|
|
@@ -72,7 +72,7 @@ export function mergedSelect(key, def, iv, useYesInitial) {
|
|
|
72
72
|
if (useYesInitial && def.yesInitialValue !== undefined && enabledValueList.includes(def.yesInitialValue)) {
|
|
73
73
|
return def.yesInitialValue;
|
|
74
74
|
}
|
|
75
|
-
const d = def.initialValue;
|
|
75
|
+
const d = typeof def.initialValue === 'function' ? def.initialValue(valuesSoFar) : def.initialValue;
|
|
76
76
|
if (d !== undefined && enabledValueList.includes(d)) {
|
|
77
77
|
return d;
|
|
78
78
|
}
|
|
@@ -221,11 +221,12 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
221
221
|
if (def.type === 'select') {
|
|
222
222
|
const message = resolvePromptText(def.message, locale, key);
|
|
223
223
|
const valueList = selectOptionValues(def.options);
|
|
224
|
+
const valuesSoFar = { ...computationSeed, ...out };
|
|
224
225
|
if (def.required && def.options.length === 0) {
|
|
225
226
|
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.selectRequiredNoOptions', { key }));
|
|
226
227
|
}
|
|
227
228
|
if (!interactive) {
|
|
228
|
-
const merged = mergedSelect(key, def, resolveIv, useYesInitial);
|
|
229
|
+
const merged = mergedSelect(key, def, resolveIv, useYesInitial, valuesSoFar);
|
|
229
230
|
if (merged === undefined || !valueList.includes(merged)) {
|
|
230
231
|
const bad = hasIvKey(resolveIv, key) && !valueList.includes(String(resolveIv[key]))
|
|
231
232
|
? String(resolveIv[key])
|
|
@@ -243,10 +244,8 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
243
244
|
}
|
|
244
245
|
continue;
|
|
245
246
|
}
|
|
246
|
-
const merged = mergedSelect(key, def, promptIv, false);
|
|
247
|
-
const uiInitial = merged ??
|
|
248
|
-
(def.initialValue && valueList.includes(def.initialValue) ? def.initialValue : undefined) ??
|
|
249
|
-
valueList[0];
|
|
247
|
+
const merged = mergedSelect(key, def, promptIv, false, valuesSoFar);
|
|
248
|
+
const uiInitial = merged ?? valueList[0];
|
|
250
249
|
if (uiInitial === undefined || !valueList.includes(uiInitial)) {
|
|
251
250
|
const hint = def.required
|
|
252
251
|
? t('promptCatalog.nonInteractive.selectRequiredInteractive', { key })
|
|
@@ -34,6 +34,13 @@ function resolveTextDefault(def, out) {
|
|
|
34
34
|
}
|
|
35
35
|
return String(iv ?? '');
|
|
36
36
|
}
|
|
37
|
+
function resolveSelectDefault(def, out) {
|
|
38
|
+
const iv = def.initialValue;
|
|
39
|
+
if (typeof iv === 'function') {
|
|
40
|
+
return iv(out);
|
|
41
|
+
}
|
|
42
|
+
return iv;
|
|
43
|
+
}
|
|
37
44
|
function resolvePasswordDefault(def, out) {
|
|
38
45
|
const iv = def.initialValue;
|
|
39
46
|
if (typeof iv === 'function') {
|
|
@@ -98,7 +105,7 @@ function defaultValueForInput(key, def, out) {
|
|
|
98
105
|
const first = def.options
|
|
99
106
|
.find((o) => typeof o === 'string' || o.disabled !== true);
|
|
100
107
|
const firstValue = typeof first === 'string' ? first : first?.value;
|
|
101
|
-
const i = def
|
|
108
|
+
const i = resolveSelectDefault(def, out);
|
|
102
109
|
const enabledValues = def.options
|
|
103
110
|
.filter((o) => typeof o === 'string' || o.disabled !== true)
|
|
104
111
|
.map((o) => (typeof o === 'string' ? o : o.value));
|
|
@@ -765,11 +772,10 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
765
772
|
}
|
|
766
773
|
};
|
|
767
774
|
const servePage = (port) => {
|
|
768
|
-
const base = `http://${publicHost}:${port}`;
|
|
769
775
|
const formInner = buildPwcFormHtml(catalog, formDefaults, initialShow, pwcStepDefs, 0, pwcNSteps, locale, uiText);
|
|
770
776
|
const wizardClientJson = JSON.stringify({ n: pwcNSteps, stepDefs: pwcStepDefs });
|
|
771
|
-
const pwcValStepUrl = pwcNSteps > 1 ? JSON.stringify(
|
|
772
|
-
const pwcValFieldUrl = JSON.stringify(
|
|
777
|
+
const pwcValStepUrl = pwcNSteps > 1 ? JSON.stringify(resolveValidateStepPath) : 'null';
|
|
778
|
+
const pwcValFieldUrl = JSON.stringify(resolveValidateFieldPath);
|
|
773
779
|
const uiTextJson = JSON.stringify(uiText);
|
|
774
780
|
const pwcShellClass = options.stages && options.stages.length > 0
|
|
775
781
|
? 'pwc-shell pwc-shell--stages'
|
|
@@ -1493,8 +1499,8 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
1493
1499
|
</div>
|
|
1494
1500
|
<script>
|
|
1495
1501
|
(function () {
|
|
1496
|
-
var sub = ${JSON.stringify(
|
|
1497
|
-
var ref = ${JSON.stringify(
|
|
1502
|
+
var sub = ${JSON.stringify(submitPath)};
|
|
1503
|
+
var ref = ${JSON.stringify(reflowPath)};
|
|
1498
1504
|
var pwcValStep = ${pwcValStepUrl};
|
|
1499
1505
|
var pwcValField = ${pwcValFieldUrl};
|
|
1500
1506
|
var pwcStepMeta = ${JSON.stringify(PWC_FORM_META_STEP)};
|
package/dist/lib/proxy-caddy.js
CHANGED
|
@@ -79,6 +79,7 @@ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeCon
|
|
|
79
79
|
writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
|
|
80
80
|
writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
|
|
81
81
|
writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
|
|
82
|
+
writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
|
|
82
83
|
writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
|
|
83
84
|
]);
|
|
84
85
|
return {
|
|
@@ -100,6 +101,7 @@ export async function writeManualCaddyProxyBundle(input, appEntryOptions, runtim
|
|
|
100
101
|
writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
|
|
101
102
|
writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
|
|
102
103
|
writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
|
|
104
|
+
writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
|
|
103
105
|
writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
|
|
104
106
|
]);
|
|
105
107
|
return {
|
package/dist/lib/proxy-nginx.js
CHANGED
|
@@ -107,6 +107,7 @@ async function writeResolvedNginxProxyBundle(bundle, appEntryOptions, options) {
|
|
|
107
107
|
writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
|
|
108
108
|
writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
|
|
109
109
|
writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
|
|
110
|
+
writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
|
|
110
111
|
writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
|
|
111
112
|
syncEnvProxyNginxSnippets(),
|
|
112
113
|
]);
|
package/dist/lib/run-npm.js
CHANGED
|
@@ -48,6 +48,10 @@ const MISSING_COMMAND_SPECS = {
|
|
|
48
48
|
displayName: 'pnpm',
|
|
49
49
|
configKey: 'bin.pnpm',
|
|
50
50
|
},
|
|
51
|
+
npm: {
|
|
52
|
+
displayName: 'npm',
|
|
53
|
+
configKey: 'bin.npm',
|
|
54
|
+
},
|
|
51
55
|
};
|
|
52
56
|
const DOCKER_DAEMON_UNAVAILABLE_PATTERNS = [
|
|
53
57
|
/cannot connect to the docker daemon/i,
|
|
@@ -61,19 +65,92 @@ async function resolveCommandName(name) {
|
|
|
61
65
|
function shouldTeeInheritedOutput(options) {
|
|
62
66
|
return options?.stdio === 'inherit' && Boolean(String(process.env.NB_CLI_ACTIVE_LOG_FILE ?? '').trim());
|
|
63
67
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return undefined;
|
|
68
|
+
function buildProcessEnv(options) {
|
|
69
|
+
if (options?.envMode === 'replace') {
|
|
70
|
+
return options.env ?? {};
|
|
68
71
|
}
|
|
72
|
+
return {
|
|
73
|
+
...process.env,
|
|
74
|
+
...options?.env,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function createMissingCommandError(name, label, error) {
|
|
69
78
|
if (!Object.prototype.hasOwnProperty.call(MISSING_COMMAND_SPECS, name)) {
|
|
70
79
|
return undefined;
|
|
71
80
|
}
|
|
72
81
|
const spec = MISSING_COMMAND_SPECS[name];
|
|
82
|
+
if (!isMissingCommandError(name, spec.displayName, error)) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
73
85
|
return new Error(translateCli('commands.shared.missingCommand', { action: label, displayName: spec.displayName, configKey: spec.configKey }, {
|
|
74
86
|
fallback: `Couldn't run \`${label}\` because the ${spec.displayName} executable could not be found. Install ${spec.displayName} or update \`nb config set ${spec.configKey} <path>\` and try again.`,
|
|
75
87
|
}));
|
|
76
88
|
}
|
|
89
|
+
function isMissingCommandError(name, displayName, error) {
|
|
90
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : undefined;
|
|
91
|
+
if (code === 'ENOENT') {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
const lowerMessage = message.toLowerCase();
|
|
96
|
+
return (lowerMessage.includes(`spawn ${name.toLowerCase()} enoent`) ||
|
|
97
|
+
lowerMessage.includes(`${name.toLowerCase()} executable could not be found`) ||
|
|
98
|
+
lowerMessage.includes(`${displayName.toLowerCase()} executable could not be found`));
|
|
99
|
+
}
|
|
100
|
+
export async function runPnpmCommand(runCommand, args, options) {
|
|
101
|
+
try {
|
|
102
|
+
await runCommand('pnpm', args, options);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
throw createMissingCommandError('pnpm', options.errorName ?? `pnpm ${args.join(' ')}`.trim(), error) ?? error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function createInstallArgsWithoutTrustLockfile(args) {
|
|
109
|
+
if (!args.includes('install') || !args.includes('--trust-lockfile')) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
return args.filter((arg) => arg !== '--trust-lockfile');
|
|
113
|
+
}
|
|
114
|
+
function isFriendlyMissingPnpmError(error) {
|
|
115
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
116
|
+
return message.includes('because the pnpm executable could not be found');
|
|
117
|
+
}
|
|
118
|
+
function formatPnpmLabel(args) {
|
|
119
|
+
return `pnpm ${args.join(' ')}`.trim();
|
|
120
|
+
}
|
|
121
|
+
export async function resolvePnpmInstallCommand(cwd) {
|
|
122
|
+
const hasLockfile = await fsp
|
|
123
|
+
.stat(path.join(cwd, 'pnpm-lock.yaml'))
|
|
124
|
+
.then((stats) => stats.isFile())
|
|
125
|
+
.catch(() => false);
|
|
126
|
+
if (hasLockfile) {
|
|
127
|
+
const args = ['install', '--frozen-lockfile', '--trust-lockfile'];
|
|
128
|
+
return {
|
|
129
|
+
args,
|
|
130
|
+
errorName: formatPnpmLabel(args),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const args = ['install'];
|
|
134
|
+
return {
|
|
135
|
+
args,
|
|
136
|
+
errorName: formatPnpmLabel(args),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export async function runPnpmInstallCommand(runCommand, args, options) {
|
|
140
|
+
try {
|
|
141
|
+
await runPnpmCommand(runCommand, args, options);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const fallbackArgs = createInstallArgsWithoutTrustLockfile(args);
|
|
145
|
+
if (!fallbackArgs || isFriendlyMissingPnpmError(error)) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
await runPnpmCommand(runCommand, fallbackArgs, {
|
|
149
|
+
...options,
|
|
150
|
+
errorName: formatPnpmLabel(fallbackArgs),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
77
154
|
function isDockerDaemonUnavailableError(error) {
|
|
78
155
|
const message = error instanceof Error ? error.message : String(error);
|
|
79
156
|
return DOCKER_DAEMON_UNAVAILABLE_PATTERNS.some((pattern) => pattern.test(message));
|
|
@@ -146,10 +223,7 @@ export async function run(name, args, options) {
|
|
|
146
223
|
const child = spawn(command, [...args], {
|
|
147
224
|
stdio,
|
|
148
225
|
cwd,
|
|
149
|
-
env:
|
|
150
|
-
...process.env,
|
|
151
|
-
...options?.env,
|
|
152
|
-
},
|
|
226
|
+
env: buildProcessEnv(options),
|
|
153
227
|
windowsHide: process.platform === 'win32',
|
|
154
228
|
});
|
|
155
229
|
if (options?.stdio === 'pipe' || shouldTeeInheritedOutput(options)) {
|
|
@@ -268,10 +342,7 @@ export async function commandSucceeds(name, args, options) {
|
|
|
268
342
|
return await new Promise((resolve, reject) => {
|
|
269
343
|
const child = spawn(command, [...args], {
|
|
270
344
|
cwd,
|
|
271
|
-
env:
|
|
272
|
-
...process.env,
|
|
273
|
-
...options?.env,
|
|
274
|
-
},
|
|
345
|
+
env: buildProcessEnv(options),
|
|
275
346
|
stdio: 'ignore',
|
|
276
347
|
windowsHide: process.platform === 'win32',
|
|
277
348
|
});
|
|
@@ -302,10 +373,7 @@ export async function commandOutput(name, args, options) {
|
|
|
302
373
|
return await new Promise((resolve, reject) => {
|
|
303
374
|
const child = spawn(command, [...args], {
|
|
304
375
|
cwd,
|
|
305
|
-
env:
|
|
306
|
-
...process.env,
|
|
307
|
-
...options?.env,
|
|
308
|
-
},
|
|
376
|
+
env: buildProcessEnv(options),
|
|
309
377
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
310
378
|
windowsHide: process.platform === 'win32',
|
|
311
379
|
});
|
|
@@ -364,10 +432,7 @@ export async function commandOutputViaFile(name, args, options) {
|
|
|
364
432
|
const result = await new Promise((resolve, reject) => {
|
|
365
433
|
const child = spawn(command, [...args], {
|
|
366
434
|
cwd,
|
|
367
|
-
env:
|
|
368
|
-
...process.env,
|
|
369
|
-
...options?.env,
|
|
370
|
-
},
|
|
435
|
+
env: buildProcessEnv(options),
|
|
371
436
|
stdio: ['ignore', stdoutHandle.fd, stderrHandle.fd],
|
|
372
437
|
windowsHide: process.platform === 'win32',
|
|
373
438
|
});
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { Flags } from '@oclif/core';
|
|
10
|
+
import { executeRawApiRequest } from './api-client.js';
|
|
11
|
+
import { ensureCrossEnvConfirmed } from './env-guard.js';
|
|
12
|
+
export const swaggerRequestFlags = {
|
|
13
|
+
env: Flags.string({
|
|
14
|
+
char: 'e',
|
|
15
|
+
description: 'CLI env name; omitted uses the current env',
|
|
16
|
+
}),
|
|
17
|
+
yes: Flags.boolean({
|
|
18
|
+
char: 'y',
|
|
19
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
20
|
+
default: false,
|
|
21
|
+
}),
|
|
22
|
+
'api-base-url': Flags.string({
|
|
23
|
+
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
24
|
+
}),
|
|
25
|
+
role: Flags.string({
|
|
26
|
+
description: 'Role override, sent as X-Role',
|
|
27
|
+
}),
|
|
28
|
+
token: Flags.string({
|
|
29
|
+
char: 't',
|
|
30
|
+
description: 'API key or access token override',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
export async function executeSwaggerRequest(command, flags, path, query) {
|
|
34
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
35
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
36
|
+
command,
|
|
37
|
+
requestedEnv,
|
|
38
|
+
yes: flags.yes,
|
|
39
|
+
});
|
|
40
|
+
if (!confirmed) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return executeRawApiRequest({
|
|
44
|
+
envName: requestedEnv,
|
|
45
|
+
baseUrl: flags['api-base-url'],
|
|
46
|
+
token: flags.token,
|
|
47
|
+
role: flags.role,
|
|
48
|
+
method: 'GET',
|
|
49
|
+
path,
|
|
50
|
+
query,
|
|
51
|
+
});
|
|
52
|
+
}
|
package/dist/lib/ui.js
CHANGED
|
@@ -15,8 +15,35 @@ let verboseMode = false;
|
|
|
15
15
|
let lastStaticTaskMessage;
|
|
16
16
|
let lastStaticTaskAt = 0;
|
|
17
17
|
const STATIC_TASK_UPDATE_THROTTLE_MS = 3_000;
|
|
18
|
+
function isCombiningCodePoint(codePoint) {
|
|
19
|
+
return ((codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
20
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
21
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
22
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
23
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f));
|
|
24
|
+
}
|
|
25
|
+
function isFullWidthCodePoint(codePoint) {
|
|
26
|
+
return (codePoint >= 0x1100 &&
|
|
27
|
+
(codePoint <= 0x115f ||
|
|
28
|
+
codePoint === 0x2329 ||
|
|
29
|
+
codePoint === 0x232a ||
|
|
30
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
31
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
32
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
33
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
34
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
35
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
36
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
37
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd)));
|
|
38
|
+
}
|
|
18
39
|
function stringWidth(value) {
|
|
19
|
-
return Array.from(value).
|
|
40
|
+
return Array.from(value).reduce((width, character) => {
|
|
41
|
+
const codePoint = character.codePointAt(0);
|
|
42
|
+
if (!codePoint || codePoint === 0 || codePoint < 32 || isCombiningCodePoint(codePoint)) {
|
|
43
|
+
return width;
|
|
44
|
+
}
|
|
45
|
+
return width + (isFullWidthCodePoint(codePoint) ? 2 : 1);
|
|
46
|
+
}, 0);
|
|
20
47
|
}
|
|
21
48
|
function pad(value, width) {
|
|
22
49
|
const padding = Math.max(0, width - stringWidth(value));
|
package/dist/locale/en-US.json
CHANGED
|
@@ -89,6 +89,9 @@
|
|
|
89
89
|
"lowerCaseTableNamesRequiresUnderscored": "MySQL lower_case_table_names=1 requires DB_UNDERSCORED=true."
|
|
90
90
|
}
|
|
91
91
|
},
|
|
92
|
+
"apiClient": {
|
|
93
|
+
"authRequiredHint": "Authentication failed or the saved session has expired. Run `{{command}}` to sign in again."
|
|
94
|
+
},
|
|
92
95
|
"commands": {
|
|
93
96
|
"envAdd": {
|
|
94
97
|
"prompts": {
|
|
@@ -168,6 +171,204 @@
|
|
|
168
171
|
"refusal": "Refusing to run against env \"{{requestedEnv}}\" because the current env is \"{{currentEnv}}\" and interactive confirmation is unavailable in the current agent session.\n\nFor safety, the agent will not switch envs automatically and will not add --yes on your behalf.\n\nTo continue:\n- run `nb env use {{requestedEnv}}` yourself and then re-run the command, or\n- re-run the same command with `--env {{requestedEnv}} --yes` to confirm this one-off cross-env operation."
|
|
169
172
|
}
|
|
170
173
|
},
|
|
174
|
+
"portalCreate": {
|
|
175
|
+
"errors": {
|
|
176
|
+
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
|
177
|
+
"noEnvConfigured": "No NocoBase env is configured yet. Run `nb init --ui` to create one first.",
|
|
178
|
+
"templateInvalidDirectory": "Portal template \"{{source}}\" is invalid: expected a directory.",
|
|
179
|
+
"localTemplateMissing": "Portal template directory does not exist: {{source}}",
|
|
180
|
+
"templateUnresolved": "Portal template \"{{source}}\" could not be resolved. {{details}}",
|
|
181
|
+
"templateDownloadFailed": "Failed to download portal template \"{{source}}\" with npm pack. {{details}}",
|
|
182
|
+
"templateExtractFailed": "Failed to extract portal template \"{{source}}\": {{details}}",
|
|
183
|
+
"templateMissingPackageJson": "Portal template \"{{source}}\" is invalid: package.json is missing.",
|
|
184
|
+
"npmPackNoTarball": "npm pack did not produce a local tarball for {{source}}.",
|
|
185
|
+
"npmPackMultipleTarballs": "npm pack produced multiple tarballs for {{source}}.",
|
|
186
|
+
"invalidPortalName": "Invalid portal name \"{{value}}\". Use lowercase letters, numbers, underscores, or hyphens, and start with a lowercase letter or number.",
|
|
187
|
+
"invalidPortalAppName": "Invalid portal app name \"{{value}}\" from apiBaseUrl. Use letters, numbers, underscores, or hyphens, and start with a letter or number.",
|
|
188
|
+
"missingApiBaseUrl": "Cannot create a portal because the selected env has no apiBaseUrl.",
|
|
189
|
+
"outsideParent": "Refusing to modify a portal outside {{parentDir}}: {{portalDir}}",
|
|
190
|
+
"sshUnsupported": "Cannot create a portal for ssh envs in the first version.",
|
|
191
|
+
"workspaceExists": "Portal already exists: {{portalDir}}\nPass --force to delete it and create a new portal.",
|
|
192
|
+
"workspaceNotReplaceable": "Refusing to replace a non-portal directory: {{portalDir}}\nThe target directory must be empty or contain package.json with a nocobase field."
|
|
193
|
+
},
|
|
194
|
+
"messages": {
|
|
195
|
+
"skipInstall": "Skipped pnpm install because package.json was not found in {{portalDir}}.",
|
|
196
|
+
"created": "Portal \"{{portal}}\" created at {{portalDir}}",
|
|
197
|
+
"app": "App: {{app}}",
|
|
198
|
+
"base": "Base: {{base}}",
|
|
199
|
+
"sourceStorage": "Source storage: {{sourceStorage}}",
|
|
200
|
+
"installFailed": "Dependency installation did not finish successfully. Run `pnpm install` manually in {{portalDir}}."
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"portalConfig": {
|
|
204
|
+
"errors": {
|
|
205
|
+
"invalidSourceStorage": "Invalid source storage \"{{value}}\". Use \"nocobase\" or \"git\".",
|
|
206
|
+
"invalidGitPath": "--git-path must be a relative path inside the Git repository.",
|
|
207
|
+
"gitOptionsForNocobaseStorage": "--git-repo, --git-branch, and --git-path can only be used with --source-storage git.",
|
|
208
|
+
"gitRepoRequired": "--git-repo is required when --source-storage is git.",
|
|
209
|
+
"gitRepoInvalid": "--git-repo must be a full Git remote URL.",
|
|
210
|
+
"updateFailed": "Portal config update failed with status {{status}}\n{{details}}"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"portalConfigure": {
|
|
214
|
+
"errors": {
|
|
215
|
+
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
|
216
|
+
"noEnvConfigured": "No NocoBase env is configured yet. Run `nb init --ui` to create one first.",
|
|
217
|
+
"noChanges": "No portal configuration changes were provided. Pass --path, --source-storage, or a --git-* flag.",
|
|
218
|
+
"notFound": "Portal \"{{portal}}\" was not found. Run `nb portal list` to see available portals."
|
|
219
|
+
},
|
|
220
|
+
"messages": {
|
|
221
|
+
"updated": "Portal \"{{portal}}\" configuration updated.",
|
|
222
|
+
"pathUpdated": "Development path: {{portalDir}}",
|
|
223
|
+
"remoteSynced": "Remote portal record: synced",
|
|
224
|
+
"remoteSkipped": "Remote portal record: not found; local config only"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"portalDeploy": {
|
|
228
|
+
"errors": {
|
|
229
|
+
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
|
230
|
+
"noEnvConfigured": "No NocoBase env is configured yet. Run `nb init --ui` to create one first.",
|
|
231
|
+
"workspaceMissing": "Portal does not exist: {{portalDir}}\nRun `nb portal create {{portal}}` first.",
|
|
232
|
+
"packageJsonMissing": "Portal is invalid: package.json is missing in {{portalDir}}.",
|
|
233
|
+
"distMissing": "Portal build did not produce {{distDir}}/index.html.",
|
|
234
|
+
"unsupportedEnvKind": "Cannot deploy a portal for {{kind}} envs in the first version.",
|
|
235
|
+
"uploadFailed": "Portal dist upload failed with status {{status}}\n{{details}}",
|
|
236
|
+
"recordSyncFailed": "Portal record sync failed with status {{status}}\n{{details}}"
|
|
237
|
+
},
|
|
238
|
+
"messages": {
|
|
239
|
+
"deployed": "Portal \"{{portal}}\" deployed.",
|
|
240
|
+
"mode": "Mode: {{mode}}",
|
|
241
|
+
"app": "App: {{app}}",
|
|
242
|
+
"base": "Base: {{base}}",
|
|
243
|
+
"record": "Record: synced",
|
|
244
|
+
"dist": "Dist: {{dist}}",
|
|
245
|
+
"localDist": "Local dist: {{dist}}",
|
|
246
|
+
"serverDist": "Server dist: {{dist}}"
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
"portalList": {
|
|
250
|
+
"errors": {
|
|
251
|
+
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
|
252
|
+
"noEnvConfigured": "No NocoBase env is configured yet. Run `nb init --ui` to create one first.",
|
|
253
|
+
"unsupportedEnvKind": "Cannot list portals for {{kind}} envs in the first version.",
|
|
254
|
+
"listFailed": "Portal list failed with status {{status}}\n{{details}}"
|
|
255
|
+
},
|
|
256
|
+
"messages": {
|
|
257
|
+
"empty": "No portal records found."
|
|
258
|
+
},
|
|
259
|
+
"table": {
|
|
260
|
+
"name": "Name",
|
|
261
|
+
"url": "URL",
|
|
262
|
+
"portalType": "Portal type",
|
|
263
|
+
"path": "Development path",
|
|
264
|
+
"enabled": "Enabled",
|
|
265
|
+
"default": "Default"
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"portalPull": {
|
|
269
|
+
"errors": {
|
|
270
|
+
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
|
271
|
+
"noEnvConfigured": "No NocoBase env is configured yet. Run `nb init --ui` to create one first.",
|
|
272
|
+
"gitRepoRequiredForTemporaryPull": "--git-branch and --git-path require --git-repo for a temporary Git pull. To update the portal configuration, use `nb portal config`."
|
|
273
|
+
},
|
|
274
|
+
"messages": {
|
|
275
|
+
"noop": "No pull is needed.",
|
|
276
|
+
"pulled": "Pulled portal source \"{{portal}}\" into {{portalDir}}",
|
|
277
|
+
"installFailed": "Dependency installation did not finish successfully. Run `pnpm install` manually in {{portalDir}}."
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
"portalSource": {
|
|
281
|
+
"errors": {
|
|
282
|
+
"workspaceNotReplaceable": "Refusing to replace a non-portal directory: {{portalDir}}\nThe target directory must be empty or contain package.json with a nocobase field."
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
"swagger": {
|
|
286
|
+
"errors": {
|
|
287
|
+
"pluginDisabled": "The API documentation plugin is not enabled. Enable it before requesting Swagger documents.",
|
|
288
|
+
"requestFailed": "Swagger request failed with status {{status}}\n{{details}}",
|
|
289
|
+
"invalidDestinations": "swagger:getUrls returned an invalid destination list.",
|
|
290
|
+
"invalidDocument": "swagger:get returned an invalid OpenAPI document."
|
|
291
|
+
},
|
|
292
|
+
"messages": {
|
|
293
|
+
"empty": "No Swagger document namespaces are available.",
|
|
294
|
+
"saved": "Saved Swagger document to {{output}}."
|
|
295
|
+
},
|
|
296
|
+
"table": {
|
|
297
|
+
"name": "Name",
|
|
298
|
+
"namespace": "Namespace",
|
|
299
|
+
"url": "URL",
|
|
300
|
+
"field": "Field",
|
|
301
|
+
"value": "Value"
|
|
302
|
+
},
|
|
303
|
+
"fields": {
|
|
304
|
+
"namespace": "Namespace",
|
|
305
|
+
"title": "Title",
|
|
306
|
+
"version": "Version",
|
|
307
|
+
"openapi": "OpenAPI",
|
|
308
|
+
"paths": "Paths"
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
"portalInfo": {
|
|
312
|
+
"errors": {
|
|
313
|
+
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
|
314
|
+
"noEnvConfigured": "No NocoBase env is configured yet. Run `nb init --ui` to create one first.",
|
|
315
|
+
"notFound": "Portal \"{{portal}}\" was not found."
|
|
316
|
+
},
|
|
317
|
+
"fields": {
|
|
318
|
+
"name": "Name",
|
|
319
|
+
"url": "URL",
|
|
320
|
+
"portalType": "Portal type",
|
|
321
|
+
"developmentPath": "Development path",
|
|
322
|
+
"deploymentPath": "Deployment path",
|
|
323
|
+
"enabled": "Enabled"
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
"portalDestroy": {
|
|
327
|
+
"errors": {
|
|
328
|
+
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
|
329
|
+
"noEnvConfigured": "No NocoBase env is configured yet. Run `nb init --ui` to create one first.",
|
|
330
|
+
"confirmationRequired": "Refusing to destroy a portal in non-interactive mode without --yes.",
|
|
331
|
+
"outsideParent": "Refusing to delete a portal outside {{parentDir}}: {{portalDir}}",
|
|
332
|
+
"workspaceMissing": "Portal deployment path does not exist: {{portalDir}}\nPass --force to ignore missing deployment files.",
|
|
333
|
+
"unsupportedEnvKind": "Cannot destroy a portal for {{kind}} envs in the first version.",
|
|
334
|
+
"unsafeDevelopmentPath": "Refusing to delete an unsafe portal development path: {{portalDir}}",
|
|
335
|
+
"recordDestroyFailed": "Portal record destroy failed with status {{status}}\n{{details}}"
|
|
336
|
+
},
|
|
337
|
+
"prompts": {
|
|
338
|
+
"confirm": "Destroy portal \"{{portal}}\" and delete its deployment directory?"
|
|
339
|
+
},
|
|
340
|
+
"statuses": {
|
|
341
|
+
"deleted": "deleted",
|
|
342
|
+
"missing": "missing",
|
|
343
|
+
"retained": "retained"
|
|
344
|
+
},
|
|
345
|
+
"messages": {
|
|
346
|
+
"destroyed": "Portal \"{{portal}}\" destroyed.",
|
|
347
|
+
"mode": "Mode: {{mode}}",
|
|
348
|
+
"app": "App: {{app}}",
|
|
349
|
+
"base": "Base: {{base}}",
|
|
350
|
+
"record": "Record: {{status}}",
|
|
351
|
+
"deploymentPath": "Deployment path: {{status}} ({{dir}})",
|
|
352
|
+
"developmentPath": "Development path: {{status}} ({{dir}})",
|
|
353
|
+
"developmentPathMissing": "Development path: missing"
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
"portalDev": {
|
|
357
|
+
"errors": {
|
|
358
|
+
"envNotConfigured": "Env \"{{envName}}\" is not configured. Run `nb env add {{envName}} --api-base-url <url>` first.",
|
|
359
|
+
"noEnvConfigured": "No NocoBase env is configured yet. Run `nb init --ui` to create one first.",
|
|
360
|
+
"workspaceMissing": "Portal does not exist: {{portalDir}}\nRun `nb portal create {{portal}}` first.",
|
|
361
|
+
"packageJsonMissing": "Portal is invalid: package.json is missing in {{portalDir}}.",
|
|
362
|
+
"sshUnsupported": "Cannot start a portal in dev mode for ssh envs in the first version."
|
|
363
|
+
},
|
|
364
|
+
"messages": {
|
|
365
|
+
"starting": "Starting portal \"{{portal}}\"...",
|
|
366
|
+
"mode": "Mode: {{mode}}",
|
|
367
|
+
"app": "App: {{app}}",
|
|
368
|
+
"base": "Base: {{base}}",
|
|
369
|
+
"dir": "Dir: {{dir}}"
|
|
370
|
+
}
|
|
371
|
+
},
|
|
171
372
|
"license": {
|
|
172
373
|
"activate": {
|
|
173
374
|
"interactive": {
|
|
@@ -267,7 +468,7 @@
|
|
|
267
468
|
"betaLabel": "beta",
|
|
268
469
|
"betaHint": "Preview release. Good for trying upcoming features before general release.",
|
|
269
470
|
"alphaLabel": "alpha",
|
|
270
|
-
"alphaHint": "
|
|
471
|
+
"alphaHint": "Choose this version to try the new features in 3.0.",
|
|
271
472
|
"otherLabel": "Other",
|
|
272
473
|
"otherHint": "Enter another package version, Docker tag, or Git ref manually, such as a branch name."
|
|
273
474
|
},
|
|
@@ -341,6 +542,21 @@
|
|
|
341
542
|
"message": "App subpath (for example, /nocobase/)",
|
|
342
543
|
"placeholder": "/ or /nocobase/"
|
|
343
544
|
},
|
|
545
|
+
"portalType": {
|
|
546
|
+
"message": "Portal type",
|
|
547
|
+
"noCodeLabel": "No-code portal",
|
|
548
|
+
"noCodeHint": "Create with visual configuration. AI can help adjust the configuration. Path: /v/<name>",
|
|
549
|
+
"aiLabel": "AI portal",
|
|
550
|
+
"aiHint": "Create with AI Agent and code. Users can request changes in natural language. Path: /x/<name>"
|
|
551
|
+
},
|
|
552
|
+
"portalName": {
|
|
553
|
+
"message": "Portal name",
|
|
554
|
+
"placeholder": "admin"
|
|
555
|
+
},
|
|
556
|
+
"portalTemplate": {
|
|
557
|
+
"message": "Starter template (copied to ./storage/portals/)",
|
|
558
|
+
"placeholder": "git@github.com:nocobase/admin-starter.git"
|
|
559
|
+
},
|
|
344
560
|
"storagePath": {
|
|
345
561
|
"message": "Uploads and local files directory",
|
|
346
562
|
"placeholder": "./<env>/storage/"
|
|
@@ -440,6 +656,30 @@
|
|
|
440
656
|
},
|
|
441
657
|
"skipDownload": {
|
|
442
658
|
"message": "Skip downloading NocoBase and reuse existing local app files or Docker images"
|
|
659
|
+
},
|
|
660
|
+
"appClientEntryMode": {
|
|
661
|
+
"message": "App client entry mode",
|
|
662
|
+
"modernOnlyLabel": "Modern UI only",
|
|
663
|
+
"modernOnlyHint": "Only the modern UI is available; the legacy UI cannot be accessed.",
|
|
664
|
+
"modernDefaultLabel": "Modern UI by default",
|
|
665
|
+
"modernDefaultHint": "Open the modern UI by default while keeping the legacy UI available.",
|
|
666
|
+
"legacyDefaultLabel": "Legacy UI by default",
|
|
667
|
+
"legacyDefaultHint": "Open the legacy UI by default while keeping the modern UI available."
|
|
668
|
+
},
|
|
669
|
+
"portalType": {
|
|
670
|
+
"message": "Portal type",
|
|
671
|
+
"noCodeLabel": "No-code portal",
|
|
672
|
+
"noCodeHint": "Create with visual configuration. AI can help adjust the configuration. Path: /v/<name>",
|
|
673
|
+
"aiLabel": "AI portal",
|
|
674
|
+
"aiHint": "Create with AI Agent and code. Users can request changes in natural language. Path: /x/<name>"
|
|
675
|
+
},
|
|
676
|
+
"portalName": {
|
|
677
|
+
"message": "Portal name",
|
|
678
|
+
"placeholder": "admin"
|
|
679
|
+
},
|
|
680
|
+
"portalTemplate": {
|
|
681
|
+
"message": "Starter template (copied to ./storage/portals/)",
|
|
682
|
+
"placeholder": "git@github.com:nocobase/admin-starter.git"
|
|
443
683
|
}
|
|
444
684
|
},
|
|
445
685
|
"webUi": {
|
|
@@ -462,6 +702,10 @@
|
|
|
462
702
|
"title": "App source and version",
|
|
463
703
|
"description": "Choose how to get the app and which source and version to use."
|
|
464
704
|
},
|
|
705
|
+
"portalType": {
|
|
706
|
+
"title": "Configure portal",
|
|
707
|
+
"description": "Set the default portal name and type."
|
|
708
|
+
},
|
|
465
709
|
"configureDatabase": {
|
|
466
710
|
"title": "Configure the database",
|
|
467
711
|
"description": "Use built-in or custom."
|