@nocobase/cli 2.1.0-alpha.21 → 2.1.0-alpha.22
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/download.js +33 -22
- package/dist/commands/env/add.js +39 -12
- package/dist/commands/init.js +72 -45
- package/dist/commands/install.js +166 -51
- package/dist/commands/prompts-stages.js +6 -0
- package/dist/commands/prompts-test.js +6 -0
- package/dist/lib/api-client.js +49 -5
- package/dist/lib/cli-locale.js +115 -0
- package/dist/lib/env-auth.js +2 -2
- package/dist/lib/prompt-catalog.js +76 -54
- package/dist/lib/prompt-validators.js +9 -8
- package/dist/lib/prompt-web-ui.js +98 -64
- package/dist/lib/runtime-generator.js +12 -1
- package/dist/locale/en-US.json +282 -0
- package/dist/locale/zh-CN.json +282 -0
- package/package.json +4 -3
|
@@ -0,0 +1,115 @@
|
|
|
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 { readFileSync } from 'node:fs';
|
|
10
|
+
export const SUPPORTED_CLI_LOCALES = ['en-US', 'zh-CN'];
|
|
11
|
+
export const CLI_LOCALE_FLAG_OPTIONS = [...SUPPORTED_CLI_LOCALES];
|
|
12
|
+
export const CLI_LOCALE_FLAG_DESCRIPTION = 'Language for CLI prompts and the local setup UI.';
|
|
13
|
+
const DEFAULT_CLI_LOCALE = 'en-US';
|
|
14
|
+
const localeCache = {};
|
|
15
|
+
function normalizeCliLocale(value) {
|
|
16
|
+
const raw = String(value ?? '').trim();
|
|
17
|
+
if (!raw) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const normalized = raw.replace(/\..*$/, '').replace(/_/g, '-').toLowerCase();
|
|
21
|
+
if (normalized === 'zh' || normalized.startsWith('zh-')) {
|
|
22
|
+
return 'zh-CN';
|
|
23
|
+
}
|
|
24
|
+
if (normalized === 'en' || normalized.startsWith('en-')) {
|
|
25
|
+
return 'en-US';
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function loadLocaleMessages(locale) {
|
|
30
|
+
if (localeCache[locale]) {
|
|
31
|
+
return localeCache[locale];
|
|
32
|
+
}
|
|
33
|
+
const fileUrl = new URL(`../locale/${locale}.json`, import.meta.url);
|
|
34
|
+
const parsed = JSON.parse(readFileSync(fileUrl, 'utf8'));
|
|
35
|
+
localeCache[locale] = parsed;
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
function getPathValue(input, path) {
|
|
39
|
+
let current = input;
|
|
40
|
+
for (const part of path.split('.')) {
|
|
41
|
+
if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, part)) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
current = current[part];
|
|
45
|
+
}
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
function interpolateTemplate(template, values) {
|
|
49
|
+
return template.replace(/{{\s*([\w.]+)\s*}}/g, (_match, key) => {
|
|
50
|
+
const value = getPathValue(values, key);
|
|
51
|
+
return value === undefined || value === null ? '' : String(value);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export function detectCliLocale() {
|
|
55
|
+
const candidates = [
|
|
56
|
+
process.env.NB_LOCALE,
|
|
57
|
+
process.env.LC_ALL,
|
|
58
|
+
process.env.LC_MESSAGES,
|
|
59
|
+
process.env.LANG,
|
|
60
|
+
Intl.DateTimeFormat().resolvedOptions().locale,
|
|
61
|
+
];
|
|
62
|
+
for (const candidate of candidates) {
|
|
63
|
+
const locale = normalizeCliLocale(candidate);
|
|
64
|
+
if (locale) {
|
|
65
|
+
return locale;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return DEFAULT_CLI_LOCALE;
|
|
69
|
+
}
|
|
70
|
+
export function resolveCliLocale(preferred) {
|
|
71
|
+
return normalizeCliLocale(preferred) ?? detectCliLocale();
|
|
72
|
+
}
|
|
73
|
+
export function applyCliLocale(preferred) {
|
|
74
|
+
const locale = resolveCliLocale(preferred);
|
|
75
|
+
process.env.NB_LOCALE = locale;
|
|
76
|
+
return locale;
|
|
77
|
+
}
|
|
78
|
+
export function createCliTranslate(preferred) {
|
|
79
|
+
const locale = resolveCliLocale(preferred);
|
|
80
|
+
return (key, values, fallback) => {
|
|
81
|
+
const messages = loadLocaleMessages(locale);
|
|
82
|
+
const template = getPathValue(messages, key);
|
|
83
|
+
if (typeof template !== 'string') {
|
|
84
|
+
return interpolateTemplate(fallback ?? key, values);
|
|
85
|
+
}
|
|
86
|
+
return interpolateTemplate(template, values);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function translateCli(key, values, options) {
|
|
90
|
+
return createCliTranslate(options?.locale)(key, values, options?.fallback);
|
|
91
|
+
}
|
|
92
|
+
export function localeText(key, values, fallback) {
|
|
93
|
+
return {
|
|
94
|
+
key,
|
|
95
|
+
...(values ? { values } : {}),
|
|
96
|
+
...(fallback ? { fallback } : {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function isLocalizedTextDef(value) {
|
|
100
|
+
return Boolean(value
|
|
101
|
+
&& typeof value === 'object'
|
|
102
|
+
&& typeof value.key === 'string');
|
|
103
|
+
}
|
|
104
|
+
export function resolveLocalizedText(text, options) {
|
|
105
|
+
if (text === undefined) {
|
|
106
|
+
return options?.fallback ?? '';
|
|
107
|
+
}
|
|
108
|
+
if (typeof text === 'string') {
|
|
109
|
+
return text;
|
|
110
|
+
}
|
|
111
|
+
return translateCli(text.key, text.values, {
|
|
112
|
+
locale: options?.locale,
|
|
113
|
+
fallback: text.fallback ?? options?.fallback,
|
|
114
|
+
});
|
|
115
|
+
}
|
package/dist/lib/env-auth.js
CHANGED
|
@@ -483,7 +483,7 @@ export function buildOauthCompletionHtml() {
|
|
|
483
483
|
statusMark: '✓',
|
|
484
484
|
heading: 'Authentication complete',
|
|
485
485
|
description: 'Your sign-in finished successfully. You can return to the terminal and continue there.',
|
|
486
|
-
tip: 'This page will
|
|
486
|
+
tip: 'This page will close automatically in 10 seconds.',
|
|
487
487
|
footer: 'You can close this page after returning to the terminal.',
|
|
488
488
|
extraScriptHtml: ` <script>
|
|
489
489
|
setTimeout(function () {
|
|
@@ -494,7 +494,7 @@ export function buildOauthCompletionHtml() {
|
|
|
494
494
|
el.textContent = 'If this tab stays open, you can close it manually.';
|
|
495
495
|
}
|
|
496
496
|
}, 400);
|
|
497
|
-
},
|
|
497
|
+
}, 10000);
|
|
498
498
|
</script>`,
|
|
499
499
|
});
|
|
500
500
|
}
|
|
@@ -8,16 +8,24 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as p from '@clack/prompts';
|
|
10
10
|
import { exit, stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
11
|
+
import { createCliTranslate, resolveCliLocale, resolveLocalizedText, } from "./cli-locale.js";
|
|
11
12
|
export function selectOptionValues(options) {
|
|
12
13
|
return options.map((o) => (typeof o === 'string' ? o : o.value));
|
|
13
14
|
}
|
|
14
|
-
function
|
|
15
|
+
function resolvePromptText(text, locale, fallback = '') {
|
|
16
|
+
return resolveLocalizedText(text, { locale, fallback });
|
|
17
|
+
}
|
|
18
|
+
function clackSelectOptions(options, locale) {
|
|
15
19
|
return options.map((o) => typeof o === 'string'
|
|
16
20
|
? { value: o, label: o }
|
|
17
|
-
: {
|
|
21
|
+
: {
|
|
22
|
+
value: o.value,
|
|
23
|
+
label: resolvePromptText(o.label, locale, o.value),
|
|
24
|
+
...(o.hint !== undefined ? { hint: resolvePromptText(o.hint, locale) } : {}),
|
|
25
|
+
});
|
|
18
26
|
}
|
|
19
|
-
function defaultOnCancel() {
|
|
20
|
-
p.cancel('
|
|
27
|
+
function defaultOnCancel(locale) {
|
|
28
|
+
p.cancel(createCliTranslate(locale)('promptCatalog.common.cancelled'));
|
|
21
29
|
exit(0);
|
|
22
30
|
}
|
|
23
31
|
function defaultOnMissingNonInteractive(message) {
|
|
@@ -121,7 +129,8 @@ export function isPromptBlockSkipped(def, values) {
|
|
|
121
129
|
* If **`preset`** defines **`key`**, validate and set **`out[key]`**, return **`true`** (caller should `continue`).
|
|
122
130
|
* No-op for non-input block types.
|
|
123
131
|
*/
|
|
124
|
-
function tryApplyPreset(key, def, preset, out, hooks) {
|
|
132
|
+
function tryApplyPreset(key, def, preset, out, hooks, locale) {
|
|
133
|
+
const t = createCliTranslate(locale);
|
|
125
134
|
if (!hasIvKey(preset, key)) {
|
|
126
135
|
return false;
|
|
127
136
|
}
|
|
@@ -134,7 +143,7 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
134
143
|
case 'text': {
|
|
135
144
|
const s = String(raw ?? '');
|
|
136
145
|
if (def.required && isBlankText(s)) {
|
|
137
|
-
hooks.onMissingNonInteractive(
|
|
146
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
|
|
138
147
|
}
|
|
139
148
|
out[key] = s;
|
|
140
149
|
return true;
|
|
@@ -147,7 +156,7 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
147
156
|
const valueList = selectOptionValues(def.options);
|
|
148
157
|
const s = String(raw ?? '');
|
|
149
158
|
if (!valueList.includes(s)) {
|
|
150
|
-
hooks.onMissingNonInteractive(
|
|
159
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.invalidSelect', { key, value: s, options: valueList.join(', ') }));
|
|
151
160
|
}
|
|
152
161
|
out[key] = s;
|
|
153
162
|
return true;
|
|
@@ -155,7 +164,7 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
155
164
|
case 'password': {
|
|
156
165
|
const s = String(raw ?? '');
|
|
157
166
|
if (def.required && isBlankText(s)) {
|
|
158
|
-
hooks.onMissingNonInteractive(
|
|
167
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
|
|
159
168
|
}
|
|
160
169
|
out[key] = s;
|
|
161
170
|
return true;
|
|
@@ -168,13 +177,13 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
168
177
|
const s = String(raw ?? '').trim();
|
|
169
178
|
if (s === '') {
|
|
170
179
|
if (def.required) {
|
|
171
|
-
hooks.onMissingNonInteractive(
|
|
180
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
|
|
172
181
|
}
|
|
173
182
|
out[key] = def.initialValue ?? 0;
|
|
174
183
|
return true;
|
|
175
184
|
}
|
|
176
185
|
if (!/^-?\d+$/.test(s)) {
|
|
177
|
-
hooks.onMissingNonInteractive(
|
|
186
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.invalidInteger', { key }));
|
|
178
187
|
}
|
|
179
188
|
out[key] = Number.parseInt(s, 10);
|
|
180
189
|
return true;
|
|
@@ -200,12 +209,14 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
200
209
|
* Input blocks may set **`validate(value, values)`** (sync or async): return a string to fail; used after required/type checks, and by the local web form on submit. When **`validate`** is set, interactive TTY steps re-ask on failure (`log.error` + retry) except for simple fields without a custom `validate` (fast path).
|
|
201
210
|
*/
|
|
202
211
|
export async function runPromptCatalog(catalog, options = {}) {
|
|
212
|
+
const locale = resolveCliLocale(options.locale);
|
|
213
|
+
const t = createCliTranslate(locale);
|
|
203
214
|
const promptIv = options.initialValues ?? {};
|
|
204
215
|
const yesIv = options.yesInitialValues ?? {};
|
|
205
216
|
const resolveIv = options.yes ? { ...promptIv, ...yesIv } : promptIv;
|
|
206
217
|
const useYesInitial = Boolean(options.yes);
|
|
207
218
|
const hooks = {
|
|
208
|
-
onCancel: options.hooks?.onCancel ?? defaultOnCancel,
|
|
219
|
+
onCancel: options.hooks?.onCancel ?? (() => defaultOnCancel(locale)),
|
|
209
220
|
onMissingNonInteractive: options.hooks?.onMissingNonInteractive ?? defaultOnMissingNonInteractive,
|
|
210
221
|
};
|
|
211
222
|
const interactive = Boolean(stdinStream.isTTY && stdoutStream.isTTY && !options.yes);
|
|
@@ -213,7 +224,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
213
224
|
const out = {};
|
|
214
225
|
for (const [key, def] of Object.entries(catalog)) {
|
|
215
226
|
// Apply `values` presets before `hidden` / `when` so CLI/env fixes still win without UI.
|
|
216
|
-
if (tryApplyPreset(key, def, preset, out, hooks)) {
|
|
227
|
+
if (tryApplyPreset(key, def, preset, out, hooks, locale)) {
|
|
217
228
|
const errV = await runPromptFieldValidate(def, out[key], out);
|
|
218
229
|
if (errV) {
|
|
219
230
|
hooks.onMissingNonInteractive(errV);
|
|
@@ -224,11 +235,11 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
224
235
|
continue;
|
|
225
236
|
}
|
|
226
237
|
if (def.type === 'intro') {
|
|
227
|
-
p.intro(def.title);
|
|
238
|
+
p.intro(resolvePromptText(def.title, locale));
|
|
228
239
|
continue;
|
|
229
240
|
}
|
|
230
241
|
if (def.type === 'outro') {
|
|
231
|
-
p.outro(def.message);
|
|
242
|
+
p.outro(resolvePromptText(def.message, locale));
|
|
232
243
|
continue;
|
|
233
244
|
}
|
|
234
245
|
if (def.type === 'run') {
|
|
@@ -236,10 +247,14 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
236
247
|
continue;
|
|
237
248
|
}
|
|
238
249
|
if (def.type === 'text') {
|
|
250
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
251
|
+
const placeholder = def.placeholder !== undefined
|
|
252
|
+
? resolvePromptText(def.placeholder, locale)
|
|
253
|
+
: undefined;
|
|
239
254
|
if (!interactive) {
|
|
240
255
|
const merged = mergedText(key, def, resolveIv, useYesInitial, out);
|
|
241
256
|
if (def.required && isBlankText(merged)) {
|
|
242
|
-
hooks.onMissingNonInteractive(
|
|
257
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.textRequired', { key }));
|
|
243
258
|
}
|
|
244
259
|
out[key] = merged;
|
|
245
260
|
const errT = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
@@ -253,16 +268,16 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
253
268
|
let last = merged;
|
|
254
269
|
for (;;) {
|
|
255
270
|
const raw = await p.text({
|
|
256
|
-
message
|
|
271
|
+
message,
|
|
257
272
|
initialValue: last,
|
|
258
|
-
...(
|
|
273
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
259
274
|
});
|
|
260
275
|
if (p.isCancel(raw)) {
|
|
261
276
|
hooks.onCancel();
|
|
262
277
|
}
|
|
263
278
|
const s = typeof raw === 'string' ? raw : last;
|
|
264
279
|
if (def.required && isBlankText(s)) {
|
|
265
|
-
p.log.error('
|
|
280
|
+
p.log.error(t('promptCatalog.common.required'));
|
|
266
281
|
last = s;
|
|
267
282
|
continue;
|
|
268
283
|
}
|
|
@@ -278,10 +293,10 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
278
293
|
continue;
|
|
279
294
|
}
|
|
280
295
|
const raw = await p.text({
|
|
281
|
-
message
|
|
296
|
+
message,
|
|
282
297
|
initialValue: merged,
|
|
283
|
-
...(
|
|
284
|
-
validate: def.required ? (value) => (isBlankText(value) ? '
|
|
298
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
299
|
+
validate: def.required ? (value) => (isBlankText(value) ? t('promptCatalog.common.required') : undefined) : undefined,
|
|
285
300
|
});
|
|
286
301
|
if (p.isCancel(raw)) {
|
|
287
302
|
hooks.onCancel();
|
|
@@ -290,6 +305,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
290
305
|
continue;
|
|
291
306
|
}
|
|
292
307
|
if (def.type === 'boolean') {
|
|
308
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
293
309
|
if (!interactive) {
|
|
294
310
|
const b = mergedBoolean(key, def, resolveIv, useYesInitial);
|
|
295
311
|
out[key] = b;
|
|
@@ -303,7 +319,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
303
319
|
if (def.validate) {
|
|
304
320
|
for (;;) {
|
|
305
321
|
const raw = await p.confirm({
|
|
306
|
-
message
|
|
322
|
+
message,
|
|
307
323
|
initialValue: merged,
|
|
308
324
|
});
|
|
309
325
|
if (p.isCancel(raw)) {
|
|
@@ -321,7 +337,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
321
337
|
continue;
|
|
322
338
|
}
|
|
323
339
|
const raw = await p.confirm({
|
|
324
|
-
message
|
|
340
|
+
message,
|
|
325
341
|
initialValue: merged,
|
|
326
342
|
});
|
|
327
343
|
if (p.isCancel(raw)) {
|
|
@@ -331,9 +347,10 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
331
347
|
continue;
|
|
332
348
|
}
|
|
333
349
|
if (def.type === 'select') {
|
|
350
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
334
351
|
const valueList = selectOptionValues(def.options);
|
|
335
352
|
if (def.required && def.options.length === 0) {
|
|
336
|
-
hooks.onMissingNonInteractive(
|
|
353
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.selectRequiredNoOptions', { key }));
|
|
337
354
|
}
|
|
338
355
|
if (!interactive) {
|
|
339
356
|
const merged = mergedSelect(key, def, resolveIv, useYesInitial);
|
|
@@ -344,8 +361,8 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
344
361
|
? String(promptIv[key])
|
|
345
362
|
: undefined;
|
|
346
363
|
hooks.onMissingNonInteractive(bad !== undefined
|
|
347
|
-
?
|
|
348
|
-
:
|
|
364
|
+
? t('promptCatalog.nonInteractive.selectInvalidValue', { key, value: bad, options: valueList.join(', ') })
|
|
365
|
+
: t('promptCatalog.nonInteractive.selectMissingDefault', { key }));
|
|
349
366
|
}
|
|
350
367
|
out[key] = merged;
|
|
351
368
|
const errS = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
@@ -360,15 +377,15 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
360
377
|
valueList[0];
|
|
361
378
|
if (uiInitial === undefined || !valueList.includes(uiInitial)) {
|
|
362
379
|
const hint = def.required
|
|
363
|
-
?
|
|
364
|
-
:
|
|
380
|
+
? t('promptCatalog.nonInteractive.selectRequiredInteractive', { key })
|
|
381
|
+
: t('promptCatalog.nonInteractive.selectMissingInteractiveDefault', { key });
|
|
365
382
|
hooks.onMissingNonInteractive(hint);
|
|
366
383
|
}
|
|
367
384
|
if (def.validate) {
|
|
368
385
|
for (;;) {
|
|
369
386
|
const raw = await p.select({
|
|
370
|
-
message
|
|
371
|
-
options: clackSelectOptions(def.options),
|
|
387
|
+
message,
|
|
388
|
+
options: clackSelectOptions(def.options, locale),
|
|
372
389
|
initialValue: uiInitial,
|
|
373
390
|
});
|
|
374
391
|
if (p.isCancel(raw)) {
|
|
@@ -386,8 +403,8 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
386
403
|
continue;
|
|
387
404
|
}
|
|
388
405
|
const raw = await p.select({
|
|
389
|
-
message
|
|
390
|
-
options: clackSelectOptions(def.options),
|
|
406
|
+
message,
|
|
407
|
+
options: clackSelectOptions(def.options, locale),
|
|
391
408
|
initialValue: uiInitial,
|
|
392
409
|
});
|
|
393
410
|
if (p.isCancel(raw)) {
|
|
@@ -397,11 +414,12 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
397
414
|
continue;
|
|
398
415
|
}
|
|
399
416
|
if (def.type === 'password') {
|
|
417
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
400
418
|
if (!interactive) {
|
|
401
419
|
const merged = mergedPassword(key, def, resolveIv, useYesInitial);
|
|
402
420
|
if (merged === undefined) {
|
|
403
421
|
if (def.required) {
|
|
404
|
-
hooks.onMissingNonInteractive(
|
|
422
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.passwordRequired', { key }));
|
|
405
423
|
}
|
|
406
424
|
out[key] = '';
|
|
407
425
|
const errPE = await runPromptFieldValidate(def, '', { ...out, [key]: '' });
|
|
@@ -411,7 +429,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
411
429
|
continue;
|
|
412
430
|
}
|
|
413
431
|
if (def.required && isBlankText(merged)) {
|
|
414
|
-
hooks.onMissingNonInteractive(
|
|
432
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.passwordRequiredNonEmpty', { key }));
|
|
415
433
|
}
|
|
416
434
|
out[key] = merged;
|
|
417
435
|
const errP = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
@@ -423,15 +441,15 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
423
441
|
if (def.validate) {
|
|
424
442
|
for (;;) {
|
|
425
443
|
const raw = await p.password({
|
|
426
|
-
message
|
|
427
|
-
validate: def.required ? (value) => (isBlankText(value) ? '
|
|
444
|
+
message,
|
|
445
|
+
validate: def.required ? (value) => (isBlankText(value) ? t('promptCatalog.common.required') : undefined) : undefined,
|
|
428
446
|
});
|
|
429
447
|
if (p.isCancel(raw)) {
|
|
430
448
|
hooks.onCancel();
|
|
431
449
|
}
|
|
432
450
|
const s = typeof raw === 'string' ? raw : '';
|
|
433
451
|
if (def.required && isBlankText(s)) {
|
|
434
|
-
p.log.error('
|
|
452
|
+
p.log.error(t('promptCatalog.common.required'));
|
|
435
453
|
continue;
|
|
436
454
|
}
|
|
437
455
|
const errP = await runPromptFieldValidate(def, s, { ...out, [key]: s });
|
|
@@ -445,8 +463,8 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
445
463
|
continue;
|
|
446
464
|
}
|
|
447
465
|
const raw = await p.password({
|
|
448
|
-
message
|
|
449
|
-
validate: def.required ? (value) => (isBlankText(value) ? '
|
|
466
|
+
message,
|
|
467
|
+
validate: def.required ? (value) => (isBlankText(value) ? t('promptCatalog.common.required') : undefined) : undefined,
|
|
450
468
|
});
|
|
451
469
|
if (p.isCancel(raw)) {
|
|
452
470
|
hooks.onCancel();
|
|
@@ -455,11 +473,15 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
455
473
|
continue;
|
|
456
474
|
}
|
|
457
475
|
if (def.type === 'integer') {
|
|
476
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
477
|
+
const placeholder = def.placeholder !== undefined
|
|
478
|
+
? resolvePromptText(def.placeholder, locale)
|
|
479
|
+
: undefined;
|
|
458
480
|
if (!interactive) {
|
|
459
481
|
const merged = mergedInteger(key, def, resolveIv, useYesInitial);
|
|
460
482
|
if (merged === undefined) {
|
|
461
483
|
if (def.required) {
|
|
462
|
-
hooks.onMissingNonInteractive(
|
|
484
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.integerRequired', { key }));
|
|
463
485
|
}
|
|
464
486
|
const z = def.initialValue ?? 0;
|
|
465
487
|
out[key] = z;
|
|
@@ -482,16 +504,16 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
482
504
|
let last = lineDefault;
|
|
483
505
|
for (;;) {
|
|
484
506
|
const raw = await p.text({
|
|
485
|
-
message
|
|
507
|
+
message,
|
|
486
508
|
initialValue: last,
|
|
487
|
-
...(
|
|
509
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
488
510
|
validate: (value) => {
|
|
489
|
-
const
|
|
490
|
-
if (
|
|
491
|
-
return def.required ? '
|
|
511
|
+
const trimmed = value.trim();
|
|
512
|
+
if (trimmed === '') {
|
|
513
|
+
return def.required ? t('promptCatalog.common.required') : undefined;
|
|
492
514
|
}
|
|
493
|
-
if (!/^-?\d+$/.test(
|
|
494
|
-
return '
|
|
515
|
+
if (!/^-?\d+$/.test(trimmed)) {
|
|
516
|
+
return t('promptCatalog.common.mustBeInteger');
|
|
495
517
|
}
|
|
496
518
|
return undefined;
|
|
497
519
|
},
|
|
@@ -523,16 +545,16 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
523
545
|
continue;
|
|
524
546
|
}
|
|
525
547
|
const raw = await p.text({
|
|
526
|
-
message
|
|
548
|
+
message,
|
|
527
549
|
initialValue: lineDefault,
|
|
528
|
-
...(
|
|
550
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
529
551
|
validate: (value) => {
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
return def.required ? '
|
|
552
|
+
const trimmed = value.trim();
|
|
553
|
+
if (trimmed === '') {
|
|
554
|
+
return def.required ? t('promptCatalog.common.required') : undefined;
|
|
533
555
|
}
|
|
534
|
-
if (!/^-?\d+$/.test(
|
|
535
|
-
return '
|
|
556
|
+
if (!/^-?\d+$/.test(trimmed)) {
|
|
557
|
+
return t('promptCatalog.common.mustBeInteger');
|
|
536
558
|
}
|
|
537
559
|
return undefined;
|
|
538
560
|
},
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { spawn } from 'node:child_process';
|
|
10
10
|
import net from 'node:net';
|
|
11
|
+
import { translateCli } from "./cli-locale.js";
|
|
11
12
|
const API_BASE_URL_EXAMPLE = 'http://localhost:13000/api';
|
|
12
13
|
const ENV_KEY_PATTERN = /^[A-Za-z0-9]+$/;
|
|
13
14
|
const TCP_PORT_EXAMPLE = '13000';
|
|
@@ -21,10 +22,10 @@ export function validateApiBaseUrl(value) {
|
|
|
21
22
|
url = new URL(raw);
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
24
|
-
return
|
|
25
|
+
return translateCli('validators.apiBaseUrl.invalid', { example: API_BASE_URL_EXAMPLE });
|
|
25
26
|
}
|
|
26
27
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
27
|
-
return
|
|
28
|
+
return translateCli('validators.apiBaseUrl.invalidProtocol', { example: API_BASE_URL_EXAMPLE });
|
|
28
29
|
}
|
|
29
30
|
return undefined;
|
|
30
31
|
}
|
|
@@ -34,7 +35,7 @@ export function validateEnvKey(value) {
|
|
|
34
35
|
return undefined;
|
|
35
36
|
}
|
|
36
37
|
if (!ENV_KEY_PATTERN.test(raw)) {
|
|
37
|
-
return '
|
|
38
|
+
return translateCli('validators.envKey.invalid');
|
|
38
39
|
}
|
|
39
40
|
return undefined;
|
|
40
41
|
}
|
|
@@ -59,7 +60,7 @@ export function validateTcpPort(value) {
|
|
|
59
60
|
}
|
|
60
61
|
const port = parseTcpPort(raw);
|
|
61
62
|
if (port === undefined) {
|
|
62
|
-
return
|
|
63
|
+
return translateCli('validators.tcpPort.invalid', { example: TCP_PORT_EXAMPLE });
|
|
63
64
|
}
|
|
64
65
|
return undefined;
|
|
65
66
|
}
|
|
@@ -138,7 +139,7 @@ async function allocateAvailableTcpPort() {
|
|
|
138
139
|
: undefined;
|
|
139
140
|
if (!port) {
|
|
140
141
|
server.close(() => {
|
|
141
|
-
reject(new Error('
|
|
142
|
+
reject(new Error(translateCli('validators.tcpPort.allocateFailed')));
|
|
142
143
|
});
|
|
143
144
|
return;
|
|
144
145
|
}
|
|
@@ -160,7 +161,7 @@ export async function findAvailableTcpPort() {
|
|
|
160
161
|
return candidate;
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
|
-
throw new Error('
|
|
164
|
+
throw new Error(translateCli('validators.tcpPort.allocateNotDockerPublished'));
|
|
164
165
|
}
|
|
165
166
|
export async function validateAvailableTcpPort(value) {
|
|
166
167
|
const raw = String(value ?? '').trim();
|
|
@@ -174,11 +175,11 @@ export async function validateAvailableTcpPort(value) {
|
|
|
174
175
|
const port = parseTcpPort(raw);
|
|
175
176
|
const available = await canListenOnTcpPort(port);
|
|
176
177
|
if (!available) {
|
|
177
|
-
return
|
|
178
|
+
return translateCli('validators.tcpPort.alreadyInUse', { port });
|
|
178
179
|
}
|
|
179
180
|
const dockerPorts = await getDockerPublishedTcpPorts();
|
|
180
181
|
if (dockerPorts.has(port)) {
|
|
181
|
-
return
|
|
182
|
+
return translateCli('validators.tcpPort.alreadyInUseByDocker', { port });
|
|
182
183
|
}
|
|
183
184
|
return undefined;
|
|
184
185
|
}
|