@nocobase/cli 2.1.0-alpha.21 → 2.1.0-alpha.23
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 +170 -37
- package/dist/commands/env/add.js +39 -12
- package/dist/commands/init.js +90 -47
- package/dist/commands/install.js +191 -57
- 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 +87 -58
- package/dist/lib/prompt-validators.js +9 -8
- package/dist/lib/prompt-web-ui.js +143 -74
- package/dist/lib/run-npm.js +10 -10
- package/dist/lib/runtime-generator.js +12 -1
- package/dist/locale/en-US.json +333 -0
- package/dist/locale/zh-CN.json +333 -0
- package/package.json +5 -3
package/dist/lib/api-client.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
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
|
+
*/
|
|
1
9
|
import { promises as fs } from 'node:fs';
|
|
2
10
|
import { resolveServerRequestTarget } from './env-auth.js';
|
|
11
|
+
const CLI_REQUEST_SOURCE_HEADER = 'x-request-source';
|
|
12
|
+
const CLI_REQUEST_SOURCE_VALUE = 'cli';
|
|
13
|
+
function stripUtf8Bom(text) {
|
|
14
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
15
|
+
}
|
|
16
|
+
function parseJsonInput(raw, flagName) {
|
|
17
|
+
const content = stripUtf8Bom(raw);
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(content);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error(`Invalid JSON for --${flagName}: ${error?.message ?? 'parse failed'}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
3
25
|
function normalizeBaseUrl(baseUrl) {
|
|
4
26
|
return baseUrl.replace(/\/+$/, '');
|
|
5
27
|
}
|
|
@@ -59,6 +81,28 @@ function listProvidedBodyFlags(flags, parameters) {
|
|
|
59
81
|
.filter((parameter) => hasParameterValue(flags, parameter))
|
|
60
82
|
.map((parameter) => `--${parameter.flagName}`);
|
|
61
83
|
}
|
|
84
|
+
function parseBodyFieldValue(rawValue, parameter) {
|
|
85
|
+
if (rawValue === undefined) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
if (parameter.isArray && !parameter.jsonEncoded) {
|
|
89
|
+
return Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : undefined;
|
|
90
|
+
}
|
|
91
|
+
if (parameter.jsonEncoded || parameter.type === 'object' || parameter.type === 'array') {
|
|
92
|
+
if (typeof rawValue !== 'string') {
|
|
93
|
+
return rawValue;
|
|
94
|
+
}
|
|
95
|
+
const parsed = parseJsonInput(rawValue, parameter.flagName);
|
|
96
|
+
if (parameter.type === 'array' && !Array.isArray(parsed)) {
|
|
97
|
+
throw new Error(`--${parameter.flagName} must be a JSON array`);
|
|
98
|
+
}
|
|
99
|
+
if (parameter.type === 'object' && (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object')) {
|
|
100
|
+
throw new Error(`--${parameter.flagName} must be a JSON object`);
|
|
101
|
+
}
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
return parseScalarValue(rawValue, parameter.type);
|
|
105
|
+
}
|
|
62
106
|
export async function parseBody(flags, operation) {
|
|
63
107
|
const inlineBody = flags.body;
|
|
64
108
|
const bodyFile = flags['body-file'];
|
|
@@ -70,10 +114,10 @@ export async function parseBody(flags, operation) {
|
|
|
70
114
|
throw new Error(`Conflicting request body inputs: received ${rawBodyInput} together with body field flags (${providedBodyFlags.join(', ')}). Use either body field flags or --body/--body-file.`);
|
|
71
115
|
}
|
|
72
116
|
if (inlineBody) {
|
|
73
|
-
return
|
|
117
|
+
return parseJsonInput(inlineBody, 'body');
|
|
74
118
|
}
|
|
75
119
|
if (bodyFile) {
|
|
76
|
-
return fs.readFile(bodyFile, 'utf8').then((content) =>
|
|
120
|
+
return fs.readFile(bodyFile, 'utf8').then((content) => parseJsonInput(content, 'body-file'));
|
|
77
121
|
}
|
|
78
122
|
if (!bodyParameters.length) {
|
|
79
123
|
return undefined;
|
|
@@ -81,9 +125,7 @@ export async function parseBody(flags, operation) {
|
|
|
81
125
|
const body = {};
|
|
82
126
|
for (const parameter of bodyParameters) {
|
|
83
127
|
const rawValue = flags[parameter.flagName];
|
|
84
|
-
const value =
|
|
85
|
-
? (Array.isArray(rawValue) ? rawValue : rawValue ? [rawValue] : undefined)
|
|
86
|
-
: parseScalarValue(rawValue, parameter.type);
|
|
128
|
+
const value = parseBodyFieldValue(rawValue, parameter);
|
|
87
129
|
if (parameter.required && (value === undefined || value === '')) {
|
|
88
130
|
throw new Error(`Missing required body field --${parameter.flagName}`);
|
|
89
131
|
}
|
|
@@ -103,6 +145,7 @@ export async function parseBody(flags, operation) {
|
|
|
103
145
|
export async function executeApiRequest(options) {
|
|
104
146
|
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
105
147
|
const headers = new Headers();
|
|
148
|
+
headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
|
|
106
149
|
if (token) {
|
|
107
150
|
headers.set('authorization', `Bearer ${token}`);
|
|
108
151
|
}
|
|
@@ -162,6 +205,7 @@ export async function executeApiRequest(options) {
|
|
|
162
205
|
export async function executeRawApiRequest(options) {
|
|
163
206
|
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
164
207
|
const headers = new Headers();
|
|
208
|
+
headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
|
|
165
209
|
if (token) {
|
|
166
210
|
headers.set('authorization', `Bearer ${token}`);
|
|
167
211
|
}
|
|
@@ -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,30 @@
|
|
|
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
|
+
...(o.disabled !== undefined ? { disabled: o.disabled } : {}),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function enabledSelectOptionValues(options) {
|
|
29
|
+
return options
|
|
30
|
+
.filter((o) => typeof o === 'string' || o.disabled !== true)
|
|
31
|
+
.map((o) => (typeof o === 'string' ? o : o.value));
|
|
18
32
|
}
|
|
19
|
-
function defaultOnCancel() {
|
|
20
|
-
p.cancel('
|
|
33
|
+
function defaultOnCancel(locale) {
|
|
34
|
+
p.cancel(createCliTranslate(locale)('promptCatalog.common.cancelled'));
|
|
21
35
|
exit(0);
|
|
22
36
|
}
|
|
23
37
|
function defaultOnMissingNonInteractive(message) {
|
|
@@ -55,21 +69,22 @@ function mergedBoolean(key, def, iv, useYesInitial) {
|
|
|
55
69
|
}
|
|
56
70
|
function mergedSelect(key, def, iv, useYesInitial) {
|
|
57
71
|
const valueList = selectOptionValues(def.options);
|
|
72
|
+
const enabledValueList = enabledSelectOptionValues(def.options);
|
|
58
73
|
if (hasIvKey(iv, key)) {
|
|
59
74
|
const s = String(iv[key]);
|
|
60
|
-
if (
|
|
75
|
+
if (enabledValueList.includes(s)) {
|
|
61
76
|
return s;
|
|
62
77
|
}
|
|
63
78
|
return undefined;
|
|
64
79
|
}
|
|
65
|
-
if (useYesInitial && def.yesInitialValue !== undefined &&
|
|
80
|
+
if (useYesInitial && def.yesInitialValue !== undefined && enabledValueList.includes(def.yesInitialValue)) {
|
|
66
81
|
return def.yesInitialValue;
|
|
67
82
|
}
|
|
68
83
|
const d = def.initialValue;
|
|
69
|
-
if (d !== undefined &&
|
|
84
|
+
if (d !== undefined && enabledValueList.includes(d)) {
|
|
70
85
|
return d;
|
|
71
86
|
}
|
|
72
|
-
return
|
|
87
|
+
return enabledValueList[0];
|
|
73
88
|
}
|
|
74
89
|
function mergedInteger(key, def, iv, useYesInitial) {
|
|
75
90
|
if (hasIvKey(iv, key)) {
|
|
@@ -121,7 +136,8 @@ export function isPromptBlockSkipped(def, values) {
|
|
|
121
136
|
* If **`preset`** defines **`key`**, validate and set **`out[key]`**, return **`true`** (caller should `continue`).
|
|
122
137
|
* No-op for non-input block types.
|
|
123
138
|
*/
|
|
124
|
-
function tryApplyPreset(key, def, preset, out, hooks) {
|
|
139
|
+
function tryApplyPreset(key, def, preset, out, hooks, locale) {
|
|
140
|
+
const t = createCliTranslate(locale);
|
|
125
141
|
if (!hasIvKey(preset, key)) {
|
|
126
142
|
return false;
|
|
127
143
|
}
|
|
@@ -134,7 +150,7 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
134
150
|
case 'text': {
|
|
135
151
|
const s = String(raw ?? '');
|
|
136
152
|
if (def.required && isBlankText(s)) {
|
|
137
|
-
hooks.onMissingNonInteractive(
|
|
153
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
|
|
138
154
|
}
|
|
139
155
|
out[key] = s;
|
|
140
156
|
return true;
|
|
@@ -147,7 +163,7 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
147
163
|
const valueList = selectOptionValues(def.options);
|
|
148
164
|
const s = String(raw ?? '');
|
|
149
165
|
if (!valueList.includes(s)) {
|
|
150
|
-
hooks.onMissingNonInteractive(
|
|
166
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.invalidSelect', { key, value: s, options: valueList.join(', ') }));
|
|
151
167
|
}
|
|
152
168
|
out[key] = s;
|
|
153
169
|
return true;
|
|
@@ -155,7 +171,7 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
155
171
|
case 'password': {
|
|
156
172
|
const s = String(raw ?? '');
|
|
157
173
|
if (def.required && isBlankText(s)) {
|
|
158
|
-
hooks.onMissingNonInteractive(
|
|
174
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
|
|
159
175
|
}
|
|
160
176
|
out[key] = s;
|
|
161
177
|
return true;
|
|
@@ -168,13 +184,13 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
168
184
|
const s = String(raw ?? '').trim();
|
|
169
185
|
if (s === '') {
|
|
170
186
|
if (def.required) {
|
|
171
|
-
hooks.onMissingNonInteractive(
|
|
187
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.required', { key }));
|
|
172
188
|
}
|
|
173
189
|
out[key] = def.initialValue ?? 0;
|
|
174
190
|
return true;
|
|
175
191
|
}
|
|
176
192
|
if (!/^-?\d+$/.test(s)) {
|
|
177
|
-
hooks.onMissingNonInteractive(
|
|
193
|
+
hooks.onMissingNonInteractive(t('promptCatalog.preset.invalidInteger', { key }));
|
|
178
194
|
}
|
|
179
195
|
out[key] = Number.parseInt(s, 10);
|
|
180
196
|
return true;
|
|
@@ -200,12 +216,14 @@ function tryApplyPreset(key, def, preset, out, hooks) {
|
|
|
200
216
|
* 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
217
|
*/
|
|
202
218
|
export async function runPromptCatalog(catalog, options = {}) {
|
|
219
|
+
const locale = resolveCliLocale(options.locale);
|
|
220
|
+
const t = createCliTranslate(locale);
|
|
203
221
|
const promptIv = options.initialValues ?? {};
|
|
204
222
|
const yesIv = options.yesInitialValues ?? {};
|
|
205
223
|
const resolveIv = options.yes ? { ...promptIv, ...yesIv } : promptIv;
|
|
206
224
|
const useYesInitial = Boolean(options.yes);
|
|
207
225
|
const hooks = {
|
|
208
|
-
onCancel: options.hooks?.onCancel ?? defaultOnCancel,
|
|
226
|
+
onCancel: options.hooks?.onCancel ?? (() => defaultOnCancel(locale)),
|
|
209
227
|
onMissingNonInteractive: options.hooks?.onMissingNonInteractive ?? defaultOnMissingNonInteractive,
|
|
210
228
|
};
|
|
211
229
|
const interactive = Boolean(stdinStream.isTTY && stdoutStream.isTTY && !options.yes);
|
|
@@ -213,7 +231,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
213
231
|
const out = {};
|
|
214
232
|
for (const [key, def] of Object.entries(catalog)) {
|
|
215
233
|
// Apply `values` presets before `hidden` / `when` so CLI/env fixes still win without UI.
|
|
216
|
-
if (tryApplyPreset(key, def, preset, out, hooks)) {
|
|
234
|
+
if (tryApplyPreset(key, def, preset, out, hooks, locale)) {
|
|
217
235
|
const errV = await runPromptFieldValidate(def, out[key], out);
|
|
218
236
|
if (errV) {
|
|
219
237
|
hooks.onMissingNonInteractive(errV);
|
|
@@ -224,11 +242,11 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
224
242
|
continue;
|
|
225
243
|
}
|
|
226
244
|
if (def.type === 'intro') {
|
|
227
|
-
p.intro(def.title);
|
|
245
|
+
p.intro(resolvePromptText(def.title, locale));
|
|
228
246
|
continue;
|
|
229
247
|
}
|
|
230
248
|
if (def.type === 'outro') {
|
|
231
|
-
p.outro(def.message);
|
|
249
|
+
p.outro(resolvePromptText(def.message, locale));
|
|
232
250
|
continue;
|
|
233
251
|
}
|
|
234
252
|
if (def.type === 'run') {
|
|
@@ -236,10 +254,14 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
236
254
|
continue;
|
|
237
255
|
}
|
|
238
256
|
if (def.type === 'text') {
|
|
257
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
258
|
+
const placeholder = def.placeholder !== undefined
|
|
259
|
+
? resolvePromptText(def.placeholder, locale)
|
|
260
|
+
: undefined;
|
|
239
261
|
if (!interactive) {
|
|
240
262
|
const merged = mergedText(key, def, resolveIv, useYesInitial, out);
|
|
241
263
|
if (def.required && isBlankText(merged)) {
|
|
242
|
-
hooks.onMissingNonInteractive(
|
|
264
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.textRequired', { key }));
|
|
243
265
|
}
|
|
244
266
|
out[key] = merged;
|
|
245
267
|
const errT = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
@@ -253,16 +275,16 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
253
275
|
let last = merged;
|
|
254
276
|
for (;;) {
|
|
255
277
|
const raw = await p.text({
|
|
256
|
-
message
|
|
278
|
+
message,
|
|
257
279
|
initialValue: last,
|
|
258
|
-
...(
|
|
280
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
259
281
|
});
|
|
260
282
|
if (p.isCancel(raw)) {
|
|
261
283
|
hooks.onCancel();
|
|
262
284
|
}
|
|
263
285
|
const s = typeof raw === 'string' ? raw : last;
|
|
264
286
|
if (def.required && isBlankText(s)) {
|
|
265
|
-
p.log.error('
|
|
287
|
+
p.log.error(t('promptCatalog.common.required'));
|
|
266
288
|
last = s;
|
|
267
289
|
continue;
|
|
268
290
|
}
|
|
@@ -278,10 +300,10 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
278
300
|
continue;
|
|
279
301
|
}
|
|
280
302
|
const raw = await p.text({
|
|
281
|
-
message
|
|
303
|
+
message,
|
|
282
304
|
initialValue: merged,
|
|
283
|
-
...(
|
|
284
|
-
validate: def.required ? (value) => (isBlankText(value) ? '
|
|
305
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
306
|
+
validate: def.required ? (value) => (isBlankText(value) ? t('promptCatalog.common.required') : undefined) : undefined,
|
|
285
307
|
});
|
|
286
308
|
if (p.isCancel(raw)) {
|
|
287
309
|
hooks.onCancel();
|
|
@@ -290,6 +312,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
290
312
|
continue;
|
|
291
313
|
}
|
|
292
314
|
if (def.type === 'boolean') {
|
|
315
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
293
316
|
if (!interactive) {
|
|
294
317
|
const b = mergedBoolean(key, def, resolveIv, useYesInitial);
|
|
295
318
|
out[key] = b;
|
|
@@ -303,7 +326,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
303
326
|
if (def.validate) {
|
|
304
327
|
for (;;) {
|
|
305
328
|
const raw = await p.confirm({
|
|
306
|
-
message
|
|
329
|
+
message,
|
|
307
330
|
initialValue: merged,
|
|
308
331
|
});
|
|
309
332
|
if (p.isCancel(raw)) {
|
|
@@ -321,7 +344,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
321
344
|
continue;
|
|
322
345
|
}
|
|
323
346
|
const raw = await p.confirm({
|
|
324
|
-
message
|
|
347
|
+
message,
|
|
325
348
|
initialValue: merged,
|
|
326
349
|
});
|
|
327
350
|
if (p.isCancel(raw)) {
|
|
@@ -331,9 +354,10 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
331
354
|
continue;
|
|
332
355
|
}
|
|
333
356
|
if (def.type === 'select') {
|
|
357
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
334
358
|
const valueList = selectOptionValues(def.options);
|
|
335
359
|
if (def.required && def.options.length === 0) {
|
|
336
|
-
hooks.onMissingNonInteractive(
|
|
360
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.selectRequiredNoOptions', { key }));
|
|
337
361
|
}
|
|
338
362
|
if (!interactive) {
|
|
339
363
|
const merged = mergedSelect(key, def, resolveIv, useYesInitial);
|
|
@@ -344,8 +368,8 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
344
368
|
? String(promptIv[key])
|
|
345
369
|
: undefined;
|
|
346
370
|
hooks.onMissingNonInteractive(bad !== undefined
|
|
347
|
-
?
|
|
348
|
-
:
|
|
371
|
+
? t('promptCatalog.nonInteractive.selectInvalidValue', { key, value: bad, options: valueList.join(', ') })
|
|
372
|
+
: t('promptCatalog.nonInteractive.selectMissingDefault', { key }));
|
|
349
373
|
}
|
|
350
374
|
out[key] = merged;
|
|
351
375
|
const errS = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
@@ -360,15 +384,15 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
360
384
|
valueList[0];
|
|
361
385
|
if (uiInitial === undefined || !valueList.includes(uiInitial)) {
|
|
362
386
|
const hint = def.required
|
|
363
|
-
?
|
|
364
|
-
:
|
|
387
|
+
? t('promptCatalog.nonInteractive.selectRequiredInteractive', { key })
|
|
388
|
+
: t('promptCatalog.nonInteractive.selectMissingInteractiveDefault', { key });
|
|
365
389
|
hooks.onMissingNonInteractive(hint);
|
|
366
390
|
}
|
|
367
391
|
if (def.validate) {
|
|
368
392
|
for (;;) {
|
|
369
393
|
const raw = await p.select({
|
|
370
|
-
message
|
|
371
|
-
options: clackSelectOptions(def.options),
|
|
394
|
+
message,
|
|
395
|
+
options: clackSelectOptions(def.options, locale),
|
|
372
396
|
initialValue: uiInitial,
|
|
373
397
|
});
|
|
374
398
|
if (p.isCancel(raw)) {
|
|
@@ -386,8 +410,8 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
386
410
|
continue;
|
|
387
411
|
}
|
|
388
412
|
const raw = await p.select({
|
|
389
|
-
message
|
|
390
|
-
options: clackSelectOptions(def.options),
|
|
413
|
+
message,
|
|
414
|
+
options: clackSelectOptions(def.options, locale),
|
|
391
415
|
initialValue: uiInitial,
|
|
392
416
|
});
|
|
393
417
|
if (p.isCancel(raw)) {
|
|
@@ -397,11 +421,12 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
397
421
|
continue;
|
|
398
422
|
}
|
|
399
423
|
if (def.type === 'password') {
|
|
424
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
400
425
|
if (!interactive) {
|
|
401
426
|
const merged = mergedPassword(key, def, resolveIv, useYesInitial);
|
|
402
427
|
if (merged === undefined) {
|
|
403
428
|
if (def.required) {
|
|
404
|
-
hooks.onMissingNonInteractive(
|
|
429
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.passwordRequired', { key }));
|
|
405
430
|
}
|
|
406
431
|
out[key] = '';
|
|
407
432
|
const errPE = await runPromptFieldValidate(def, '', { ...out, [key]: '' });
|
|
@@ -411,7 +436,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
411
436
|
continue;
|
|
412
437
|
}
|
|
413
438
|
if (def.required && isBlankText(merged)) {
|
|
414
|
-
hooks.onMissingNonInteractive(
|
|
439
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.passwordRequiredNonEmpty', { key }));
|
|
415
440
|
}
|
|
416
441
|
out[key] = merged;
|
|
417
442
|
const errP = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
@@ -423,15 +448,15 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
423
448
|
if (def.validate) {
|
|
424
449
|
for (;;) {
|
|
425
450
|
const raw = await p.password({
|
|
426
|
-
message
|
|
427
|
-
validate: def.required ? (value) => (isBlankText(value) ? '
|
|
451
|
+
message,
|
|
452
|
+
validate: def.required ? (value) => (isBlankText(value) ? t('promptCatalog.common.required') : undefined) : undefined,
|
|
428
453
|
});
|
|
429
454
|
if (p.isCancel(raw)) {
|
|
430
455
|
hooks.onCancel();
|
|
431
456
|
}
|
|
432
457
|
const s = typeof raw === 'string' ? raw : '';
|
|
433
458
|
if (def.required && isBlankText(s)) {
|
|
434
|
-
p.log.error('
|
|
459
|
+
p.log.error(t('promptCatalog.common.required'));
|
|
435
460
|
continue;
|
|
436
461
|
}
|
|
437
462
|
const errP = await runPromptFieldValidate(def, s, { ...out, [key]: s });
|
|
@@ -445,8 +470,8 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
445
470
|
continue;
|
|
446
471
|
}
|
|
447
472
|
const raw = await p.password({
|
|
448
|
-
message
|
|
449
|
-
validate: def.required ? (value) => (isBlankText(value) ? '
|
|
473
|
+
message,
|
|
474
|
+
validate: def.required ? (value) => (isBlankText(value) ? t('promptCatalog.common.required') : undefined) : undefined,
|
|
450
475
|
});
|
|
451
476
|
if (p.isCancel(raw)) {
|
|
452
477
|
hooks.onCancel();
|
|
@@ -455,11 +480,15 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
455
480
|
continue;
|
|
456
481
|
}
|
|
457
482
|
if (def.type === 'integer') {
|
|
483
|
+
const message = resolvePromptText(def.message, locale, key);
|
|
484
|
+
const placeholder = def.placeholder !== undefined
|
|
485
|
+
? resolvePromptText(def.placeholder, locale)
|
|
486
|
+
: undefined;
|
|
458
487
|
if (!interactive) {
|
|
459
488
|
const merged = mergedInteger(key, def, resolveIv, useYesInitial);
|
|
460
489
|
if (merged === undefined) {
|
|
461
490
|
if (def.required) {
|
|
462
|
-
hooks.onMissingNonInteractive(
|
|
491
|
+
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.integerRequired', { key }));
|
|
463
492
|
}
|
|
464
493
|
const z = def.initialValue ?? 0;
|
|
465
494
|
out[key] = z;
|
|
@@ -482,16 +511,16 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
482
511
|
let last = lineDefault;
|
|
483
512
|
for (;;) {
|
|
484
513
|
const raw = await p.text({
|
|
485
|
-
message
|
|
514
|
+
message,
|
|
486
515
|
initialValue: last,
|
|
487
|
-
...(
|
|
516
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
488
517
|
validate: (value) => {
|
|
489
|
-
const
|
|
490
|
-
if (
|
|
491
|
-
return def.required ? '
|
|
518
|
+
const trimmed = value.trim();
|
|
519
|
+
if (trimmed === '') {
|
|
520
|
+
return def.required ? t('promptCatalog.common.required') : undefined;
|
|
492
521
|
}
|
|
493
|
-
if (!/^-?\d+$/.test(
|
|
494
|
-
return '
|
|
522
|
+
if (!/^-?\d+$/.test(trimmed)) {
|
|
523
|
+
return t('promptCatalog.common.mustBeInteger');
|
|
495
524
|
}
|
|
496
525
|
return undefined;
|
|
497
526
|
},
|
|
@@ -523,16 +552,16 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
523
552
|
continue;
|
|
524
553
|
}
|
|
525
554
|
const raw = await p.text({
|
|
526
|
-
message
|
|
555
|
+
message,
|
|
527
556
|
initialValue: lineDefault,
|
|
528
|
-
...(
|
|
557
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
529
558
|
validate: (value) => {
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
return def.required ? '
|
|
559
|
+
const trimmed = value.trim();
|
|
560
|
+
if (trimmed === '') {
|
|
561
|
+
return def.required ? t('promptCatalog.common.required') : undefined;
|
|
533
562
|
}
|
|
534
|
-
if (!/^-?\d+$/.test(
|
|
535
|
-
return '
|
|
563
|
+
if (!/^-?\d+$/.test(trimmed)) {
|
|
564
|
+
return t('promptCatalog.common.mustBeInteger');
|
|
536
565
|
}
|
|
537
566
|
return undefined;
|
|
538
567
|
},
|