@nocobase/cli 2.2.0-beta.9 → 2.2.0-test.16
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/assets/env-proxy/nginx/snippets/uploads-location.conf +4 -1
- package/dist/commands/app/start.js +21 -1
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/init.js +131 -4
- package/dist/commands/install.js +129 -6
- package/dist/commands/portal/create.js +104 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +104 -0
- package/dist/commands/portal/dev.js +71 -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 +77 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/source/dev.js +1 -1
- package/dist/commands/source/download.js +2 -2
- package/dist/lib/auth-store.js +3 -1
- package/dist/lib/cli-config.js +23 -2
- package/dist/lib/env-config.js +3 -0
- package/dist/lib/env-proxy.js +102 -3
- package/dist/lib/managed-init-env.js +6 -1
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-create.js +488 -0
- package/dist/lib/portal-deploy.js +275 -0
- package/dist/lib/portal-destroy.js +100 -0
- package/dist/lib/portal-dev.js +79 -0
- package/dist/lib/portal-env-files.js +53 -0
- package/dist/lib/portal-info.js +31 -0
- package/dist/lib/portal-list.js +197 -0
- package/dist/lib/portal-source.js +416 -0
- package/dist/lib/portal-template.js +190 -0
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-web-ui.js +13 -2
- package/dist/lib/run-npm.js +17 -16
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +191 -37
- package/dist/locale/zh-CN.json +191 -37
- package/package.json +5 -3
|
@@ -0,0 +1,190 @@
|
|
|
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 { cp, mkdir, mkdtemp, rm, stat } from 'node:fs/promises';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { buildPortalCommandEnv } from './portal-command-env.js';
|
|
14
|
+
import { resolvePortalTemplate } from './portal-create.js';
|
|
15
|
+
import { run } from './run-npm.js';
|
|
16
|
+
const DEFAULT_PORTAL_APP_NAME = 'main';
|
|
17
|
+
const DEFAULT_PORTAL_NAME = 'admin';
|
|
18
|
+
const PORTAL_CLIENT_PREFIX = 'x';
|
|
19
|
+
function trimValue(value) {
|
|
20
|
+
return String(value ?? '').trim();
|
|
21
|
+
}
|
|
22
|
+
function normalizePortalName(value) {
|
|
23
|
+
const segment = String(value || '')
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/^\/+|\/+$/g, '');
|
|
26
|
+
return segment || DEFAULT_PORTAL_NAME;
|
|
27
|
+
}
|
|
28
|
+
function normalizePortalAppName(value) {
|
|
29
|
+
const segment = String(value || '')
|
|
30
|
+
.trim()
|
|
31
|
+
.replace(/^\/+|\/+$/g, '');
|
|
32
|
+
return segment || DEFAULT_PORTAL_APP_NAME;
|
|
33
|
+
}
|
|
34
|
+
function validatePortalSegment(kind, value) {
|
|
35
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
|
|
36
|
+
throw new Error(`Invalid ${kind} "${value}". Use letters, numbers, underscores, or hyphens, and start with a letter or number.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function pathExists(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
await stat(filePath);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function resolveLocalTemplatePath(templateSource) {
|
|
49
|
+
if (templateSource.startsWith('file://')) {
|
|
50
|
+
return fileURLToPath(templateSource);
|
|
51
|
+
}
|
|
52
|
+
return templateSource;
|
|
53
|
+
}
|
|
54
|
+
function isGitTemplateSource(templateSource) {
|
|
55
|
+
return (templateSource.startsWith('git@') ||
|
|
56
|
+
templateSource.startsWith('git+') ||
|
|
57
|
+
/^https?:\/\//i.test(templateSource) ||
|
|
58
|
+
templateSource.endsWith('.git'));
|
|
59
|
+
}
|
|
60
|
+
async function getLocalTemplateDir(templateSource) {
|
|
61
|
+
let localPath;
|
|
62
|
+
try {
|
|
63
|
+
localPath = resolveLocalTemplatePath(templateSource);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = await stat(localPath);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
if (!result.isDirectory()) {
|
|
76
|
+
throw new Error(`Portal template "${templateSource}" is invalid: expected a directory.`);
|
|
77
|
+
}
|
|
78
|
+
return localPath;
|
|
79
|
+
}
|
|
80
|
+
async function resolveInitialPortalTemplate(params) {
|
|
81
|
+
const localTemplateDir = await getLocalTemplateDir(params.templateSource);
|
|
82
|
+
if (localTemplateDir) {
|
|
83
|
+
return {
|
|
84
|
+
dir: localTemplateDir,
|
|
85
|
+
source: params.templateSource,
|
|
86
|
+
type: 'local',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (!isGitTemplateSource(params.templateSource)) {
|
|
90
|
+
return resolvePortalTemplate(params.templateSource, {
|
|
91
|
+
npmRegistry: params.npmRegistry,
|
|
92
|
+
runCommand: params.runCommand,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
await params.runCommand('git', ['clone', '--depth', '1', params.templateSource, params.tempDir], {
|
|
96
|
+
errorName: 'git clone',
|
|
97
|
+
stdio: params.verbose ? 'inherit' : 'ignore',
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
dir: params.tempDir,
|
|
101
|
+
source: params.templateSource,
|
|
102
|
+
type: 'local',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function copyTemplate(sourceDir, targetDir) {
|
|
106
|
+
await mkdir(path.dirname(targetDir), { recursive: true });
|
|
107
|
+
await cp(sourceDir, targetDir, {
|
|
108
|
+
recursive: true,
|
|
109
|
+
filter: (source) => !source.split(path.sep).includes('.git'),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function buildPortalHtml(params) {
|
|
113
|
+
const stdio = params.verbose ? 'inherit' : 'ignore';
|
|
114
|
+
await params.runCommand('yarn', ['build:html'], {
|
|
115
|
+
cwd: params.portalDir,
|
|
116
|
+
env: buildPortalCommandEnv({
|
|
117
|
+
NOCOBASE_API_URL: '/api',
|
|
118
|
+
NOCOBASE_PORTAL_BASE: `/${PORTAL_CLIENT_PREFIX}/${params.portalName}/`,
|
|
119
|
+
}),
|
|
120
|
+
envMode: 'replace',
|
|
121
|
+
errorName: 'yarn build:html',
|
|
122
|
+
stdio,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
export async function prepareInitialPortalTemplate(options) {
|
|
126
|
+
const developmentMode = trimValue(options.developmentMode);
|
|
127
|
+
if (developmentMode !== 'vibe-coding') {
|
|
128
|
+
return { prepared: false, skippedReason: 'no-code' };
|
|
129
|
+
}
|
|
130
|
+
const storagePath = trimValue(options.storagePath);
|
|
131
|
+
if (!storagePath) {
|
|
132
|
+
throw new Error('Cannot prepare an initial Portal template without a storage path.');
|
|
133
|
+
}
|
|
134
|
+
const templateUrl = trimValue(options.portalTemplate);
|
|
135
|
+
if (!templateUrl) {
|
|
136
|
+
throw new Error('Initial Portal template is required when development mode is "vibe-coding".');
|
|
137
|
+
}
|
|
138
|
+
const appName = normalizePortalAppName(options.appName);
|
|
139
|
+
const portalName = normalizePortalName(options.portalName);
|
|
140
|
+
validatePortalSegment('portal app name', appName);
|
|
141
|
+
validatePortalSegment('Portal name', portalName);
|
|
142
|
+
const portalDir = path.join(storagePath, 'portals', appName, portalName);
|
|
143
|
+
if (await pathExists(portalDir)) {
|
|
144
|
+
if (await pathExists(path.join(portalDir, 'dist', 'index.html'))) {
|
|
145
|
+
return { prepared: false, skippedReason: 'already-prepared' };
|
|
146
|
+
}
|
|
147
|
+
await rm(portalDir, { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
options.onStartTask?.(`Preparing Portal "${portalName}" from template...`);
|
|
150
|
+
const runCommand = options.runCommand ?? run;
|
|
151
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'nocobase-portal-template-'));
|
|
152
|
+
let cleanupPortalDir = false;
|
|
153
|
+
let template;
|
|
154
|
+
try {
|
|
155
|
+
template = await resolveInitialPortalTemplate({
|
|
156
|
+
templateSource: templateUrl,
|
|
157
|
+
tempDir,
|
|
158
|
+
npmRegistry: options.npmRegistry,
|
|
159
|
+
verbose: options.verbose,
|
|
160
|
+
runCommand,
|
|
161
|
+
});
|
|
162
|
+
const templateDir = template.dir;
|
|
163
|
+
if (!(await pathExists(path.join(templateDir, 'package.json')))) {
|
|
164
|
+
throw new Error(`Portal template "${templateUrl}" is invalid: package.json is missing.`);
|
|
165
|
+
}
|
|
166
|
+
cleanupPortalDir = true;
|
|
167
|
+
await copyTemplate(templateDir, portalDir);
|
|
168
|
+
await rm(path.join(portalDir, 'node_modules'), { recursive: true, force: true });
|
|
169
|
+
await buildPortalHtml({
|
|
170
|
+
portalDir,
|
|
171
|
+
portalName,
|
|
172
|
+
verbose: options.verbose,
|
|
173
|
+
runCommand,
|
|
174
|
+
});
|
|
175
|
+
cleanupPortalDir = false;
|
|
176
|
+
options.onSucceedTask?.(`Portal "${portalName}" is ready.`);
|
|
177
|
+
return { prepared: true };
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (cleanupPortalDir) {
|
|
181
|
+
await rm(portalDir, { recursive: true, force: true });
|
|
182
|
+
}
|
|
183
|
+
options.onFailTask?.(`Failed to prepare Portal "${portalName}".`);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
188
|
+
await template?.cleanup?.();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -11,6 +11,17 @@ import { exit, stdin as stdinStream, stdout as stdoutStream } from 'node:process
|
|
|
11
11
|
import { createCliTranslate } from "./cli-locale.js";
|
|
12
12
|
import { confirm, select, input, password } from "./inquirer.js";
|
|
13
13
|
import { createPromptCatalogHooks, hasIvKey, isBlankText, isPromptBlockSkipped, mergedBoolean, mergedInteger, mergedPassword, mergedSelect, mergedText, resolvePromptCatalogLocale, resolvePromptText, runPromptFieldValidate, selectOptionValues, tryApplyPreset, } from "./prompt-catalog-core.js";
|
|
14
|
+
function buildPromptComputationSeed(catalog, initialValues) {
|
|
15
|
+
const catalogKeys = new Set(Object.keys(catalog));
|
|
16
|
+
const seed = {};
|
|
17
|
+
for (const [key, value] of Object.entries(initialValues)) {
|
|
18
|
+
if (catalogKeys.has(key) || value === undefined || value === null) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
seed[key] = value;
|
|
22
|
+
}
|
|
23
|
+
return seed;
|
|
24
|
+
}
|
|
14
25
|
function adaptInquirerValidate(validate) {
|
|
15
26
|
if (!validate) {
|
|
16
27
|
return undefined;
|
|
@@ -113,14 +124,16 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
113
124
|
const hooks = createTerminalHooks(locale, options.hooks);
|
|
114
125
|
const interactive = Boolean(stdinStream.isTTY && stdoutStream.isTTY && !options.yes);
|
|
115
126
|
const preset = options.values ?? {};
|
|
127
|
+
const computationSeed = buildPromptComputationSeed(catalog, resolveIv);
|
|
116
128
|
const out = {};
|
|
117
129
|
const renderer = createInquirerRenderer();
|
|
118
130
|
for (const [key, def] of Object.entries(catalog)) {
|
|
119
|
-
|
|
131
|
+
const valuesSoFar = { ...computationSeed, ...out };
|
|
132
|
+
if (isPromptBlockSkipped(def, valuesSoFar)) {
|
|
120
133
|
continue;
|
|
121
134
|
}
|
|
122
135
|
if (tryApplyPreset(key, def, preset, out, hooks, locale)) {
|
|
123
|
-
const errV = await runPromptFieldValidate(def, out[key], out);
|
|
136
|
+
const errV = await runPromptFieldValidate(def, out[key], { ...computationSeed, ...out });
|
|
124
137
|
if (errV) {
|
|
125
138
|
hooks.onMissingNonInteractive(errV);
|
|
126
139
|
}
|
|
@@ -135,7 +148,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
135
148
|
continue;
|
|
136
149
|
}
|
|
137
150
|
if (def.type === 'run') {
|
|
138
|
-
await def.run(out, options.command);
|
|
151
|
+
await def.run({ ...computationSeed, ...out }, options.command);
|
|
139
152
|
continue;
|
|
140
153
|
}
|
|
141
154
|
if (def.type === 'text') {
|
|
@@ -144,18 +157,18 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
144
157
|
? resolvePromptText(def.placeholder, locale)
|
|
145
158
|
: undefined;
|
|
146
159
|
if (!interactive) {
|
|
147
|
-
const merged = mergedText(key, def, resolveIv, useYesInitial,
|
|
160
|
+
const merged = mergedText(key, def, resolveIv, useYesInitial, valuesSoFar);
|
|
148
161
|
if (def.required && isBlankText(merged)) {
|
|
149
162
|
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.textRequired', { key }));
|
|
150
163
|
}
|
|
151
164
|
out[key] = merged;
|
|
152
|
-
const errT = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
165
|
+
const errT = await runPromptFieldValidate(def, merged, { ...computationSeed, ...out, [key]: merged });
|
|
153
166
|
if (errT) {
|
|
154
167
|
hooks.onMissingNonInteractive(errT);
|
|
155
168
|
}
|
|
156
169
|
continue;
|
|
157
170
|
}
|
|
158
|
-
const merged = mergedText(key, def, promptIv, false,
|
|
171
|
+
const merged = mergedText(key, def, promptIv, false, valuesSoFar);
|
|
159
172
|
const raw = await callPrompt(() => renderer.text({
|
|
160
173
|
message,
|
|
161
174
|
initialValue: merged,
|
|
@@ -168,7 +181,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
168
181
|
return undefined;
|
|
169
182
|
}
|
|
170
183
|
const currentValue = typeof value === 'string' ? value : String(value ?? '');
|
|
171
|
-
const result = runPromptFieldValidate(def, currentValue, { ...out, [key]: currentValue });
|
|
184
|
+
const result = runPromptFieldValidate(def, currentValue, { ...computationSeed, ...out, [key]: currentValue });
|
|
172
185
|
return result;
|
|
173
186
|
},
|
|
174
187
|
}), renderer, hooks);
|
|
@@ -180,7 +193,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
180
193
|
if (!interactive) {
|
|
181
194
|
const b = mergedBoolean(key, def, resolveIv, useYesInitial);
|
|
182
195
|
out[key] = b;
|
|
183
|
-
const errB = await runPromptFieldValidate(def, b, { ...out, [key]: b });
|
|
196
|
+
const errB = await runPromptFieldValidate(def, b, { ...computationSeed, ...out, [key]: b });
|
|
184
197
|
if (errB) {
|
|
185
198
|
hooks.onMissingNonInteractive(errB);
|
|
186
199
|
}
|
|
@@ -191,7 +204,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
191
204
|
for (;;) {
|
|
192
205
|
const raw = await callPrompt(() => renderer.confirm({ message, initialValue: merged }), renderer, hooks);
|
|
193
206
|
const b = Boolean(raw);
|
|
194
|
-
const errB = await runPromptFieldValidate(def, b, { ...out, [key]: b });
|
|
207
|
+
const errB = await runPromptFieldValidate(def, b, { ...computationSeed, ...out, [key]: b });
|
|
195
208
|
if (errB) {
|
|
196
209
|
renderer.error(errB);
|
|
197
210
|
continue;
|
|
@@ -224,7 +237,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
224
237
|
: t('promptCatalog.nonInteractive.selectMissingDefault', { key }));
|
|
225
238
|
}
|
|
226
239
|
out[key] = merged;
|
|
227
|
-
const errS = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
240
|
+
const errS = await runPromptFieldValidate(def, merged, { ...computationSeed, ...out, [key]: merged });
|
|
228
241
|
if (errS) {
|
|
229
242
|
hooks.onMissingNonInteractive(errS);
|
|
230
243
|
}
|
|
@@ -248,7 +261,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
248
261
|
initialValue: uiInitial,
|
|
249
262
|
}), renderer, hooks);
|
|
250
263
|
const picked = raw;
|
|
251
|
-
const errS = await runPromptFieldValidate(def, picked, { ...out, [key]: picked });
|
|
264
|
+
const errS = await runPromptFieldValidate(def, picked, { ...computationSeed, ...out, [key]: picked });
|
|
252
265
|
if (errS) {
|
|
253
266
|
renderer.error(errS);
|
|
254
267
|
continue;
|
|
@@ -269,13 +282,13 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
269
282
|
if (def.type === 'password') {
|
|
270
283
|
const message = resolvePromptText(def.message, locale, key);
|
|
271
284
|
if (!interactive) {
|
|
272
|
-
const merged = mergedPassword(key, def, resolveIv, useYesInitial);
|
|
285
|
+
const merged = mergedPassword(key, def, resolveIv, useYesInitial, valuesSoFar);
|
|
273
286
|
if (merged === undefined) {
|
|
274
287
|
if (def.required) {
|
|
275
288
|
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.passwordRequired', { key }));
|
|
276
289
|
}
|
|
277
290
|
out[key] = '';
|
|
278
|
-
const errPE = await runPromptFieldValidate(def, '', { ...out, [key]: '' });
|
|
291
|
+
const errPE = await runPromptFieldValidate(def, '', { ...computationSeed, ...out, [key]: '' });
|
|
279
292
|
if (errPE) {
|
|
280
293
|
hooks.onMissingNonInteractive(errPE);
|
|
281
294
|
}
|
|
@@ -285,7 +298,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
285
298
|
hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.passwordRequiredNonEmpty', { key }));
|
|
286
299
|
}
|
|
287
300
|
out[key] = merged;
|
|
288
|
-
const errP = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
301
|
+
const errP = await runPromptFieldValidate(def, merged, { ...computationSeed, ...out, [key]: merged });
|
|
289
302
|
if (errP) {
|
|
290
303
|
hooks.onMissingNonInteractive(errP);
|
|
291
304
|
}
|
|
@@ -302,7 +315,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
302
315
|
return undefined;
|
|
303
316
|
}
|
|
304
317
|
const currentValue = typeof value === 'string' ? value : String(value ?? '');
|
|
305
|
-
const result = runPromptFieldValidate(def, currentValue, { ...out, [key]: currentValue });
|
|
318
|
+
const result = runPromptFieldValidate(def, currentValue, { ...computationSeed, ...out, [key]: currentValue });
|
|
306
319
|
return result;
|
|
307
320
|
},
|
|
308
321
|
}), renderer, hooks);
|
|
@@ -322,14 +335,14 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
322
335
|
}
|
|
323
336
|
const z = def.initialValue ?? 0;
|
|
324
337
|
out[key] = z;
|
|
325
|
-
const errI = await runPromptFieldValidate(def, z, { ...out, [key]: z });
|
|
338
|
+
const errI = await runPromptFieldValidate(def, z, { ...computationSeed, ...out, [key]: z });
|
|
326
339
|
if (errI) {
|
|
327
340
|
hooks.onMissingNonInteractive(errI);
|
|
328
341
|
}
|
|
329
342
|
continue;
|
|
330
343
|
}
|
|
331
344
|
out[key] = merged;
|
|
332
|
-
const errI2 = await runPromptFieldValidate(def, merged, { ...out, [key]: merged });
|
|
345
|
+
const errI2 = await runPromptFieldValidate(def, merged, { ...computationSeed, ...out, [key]: merged });
|
|
333
346
|
if (errI2) {
|
|
334
347
|
hooks.onMissingNonInteractive(errI2);
|
|
335
348
|
}
|
|
@@ -349,7 +362,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
349
362
|
}
|
|
350
363
|
if (def.validate) {
|
|
351
364
|
const z = def.initialValue ?? 0;
|
|
352
|
-
return runPromptFieldValidate(def, z, { ...out, [key]: z });
|
|
365
|
+
return runPromptFieldValidate(def, z, { ...computationSeed, ...out, [key]: z });
|
|
353
366
|
}
|
|
354
367
|
return undefined;
|
|
355
368
|
}
|
|
@@ -360,7 +373,7 @@ export async function runPromptCatalog(catalog, options = {}) {
|
|
|
360
373
|
return undefined;
|
|
361
374
|
}
|
|
362
375
|
const n = Number.parseInt(trimmed, 10);
|
|
363
|
-
return runPromptFieldValidate(def, n, { ...out, [key]: n });
|
|
376
|
+
return runPromptFieldValidate(def, n, { ...computationSeed, ...out, [key]: n });
|
|
364
377
|
},
|
|
365
378
|
}), renderer, hooks);
|
|
366
379
|
if (typeof raw === 'string' && raw.trim() === '' && !def.required) {
|
|
@@ -48,13 +48,24 @@ function isInputBlock(def) {
|
|
|
48
48
|
def.type === 'password' ||
|
|
49
49
|
def.type === 'integer');
|
|
50
50
|
}
|
|
51
|
+
function buildPromptComputationSeed(catalog, userPreset) {
|
|
52
|
+
const catalogKeys = new Set(Object.keys(catalog));
|
|
53
|
+
const seed = {};
|
|
54
|
+
for (const [key, value] of Object.entries(userPreset)) {
|
|
55
|
+
if (catalogKeys.has(key) || value === undefined || value === null) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
seed[key] = value;
|
|
59
|
+
}
|
|
60
|
+
return seed;
|
|
61
|
+
}
|
|
51
62
|
/**
|
|
52
63
|
* Merges CLI/env **`userPreset`** with catalog block defaults, in the same key order and with the
|
|
53
64
|
* same `hidden` / `run` semantics as {@link isPromptBlockSkipped}, so the web form can prefill
|
|
54
65
|
* and reflow `hidden` fields (e.g. `integer` when `select` changes).
|
|
55
66
|
*/
|
|
56
67
|
export function buildWebFormValuesFromCatalog(catalog, userPreset = {}) {
|
|
57
|
-
const out =
|
|
68
|
+
const out = buildPromptComputationSeed(catalog, userPreset);
|
|
58
69
|
for (const [key, def] of Object.entries(catalog)) {
|
|
59
70
|
if (def.type === 'intro' || def.type === 'outro') {
|
|
60
71
|
continue;
|
|
@@ -109,7 +120,7 @@ function defaultValueForInput(key, def, out) {
|
|
|
109
120
|
* from current raw form data (e.g. after changing `select`). Matches how {@link isPromptBlockSkipped} uses `out` while iterating the catalog.
|
|
110
121
|
*/
|
|
111
122
|
export function reflowWebFormState(catalog, raw, userSeed = {}) {
|
|
112
|
-
const out =
|
|
123
|
+
const out = buildPromptComputationSeed(catalog, userSeed);
|
|
113
124
|
const show = {};
|
|
114
125
|
for (const [key, def] of Object.entries(catalog)) {
|
|
115
126
|
if (def.type === 'intro' || def.type === 'outro') {
|
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,6 +65,15 @@ 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
|
}
|
|
68
|
+
function buildProcessEnv(options) {
|
|
69
|
+
if (options?.envMode === 'replace') {
|
|
70
|
+
return options.env ?? {};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
...process.env,
|
|
74
|
+
...options?.env,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
64
77
|
function createMissingCommandError(name, label, error) {
|
|
65
78
|
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : undefined;
|
|
66
79
|
if (code !== 'ENOENT') {
|
|
@@ -146,10 +159,7 @@ export async function run(name, args, options) {
|
|
|
146
159
|
const child = spawn(command, [...args], {
|
|
147
160
|
stdio,
|
|
148
161
|
cwd,
|
|
149
|
-
env:
|
|
150
|
-
...process.env,
|
|
151
|
-
...options?.env,
|
|
152
|
-
},
|
|
162
|
+
env: buildProcessEnv(options),
|
|
153
163
|
windowsHide: process.platform === 'win32',
|
|
154
164
|
});
|
|
155
165
|
if (options?.stdio === 'pipe' || shouldTeeInheritedOutput(options)) {
|
|
@@ -268,10 +278,7 @@ export async function commandSucceeds(name, args, options) {
|
|
|
268
278
|
return await new Promise((resolve, reject) => {
|
|
269
279
|
const child = spawn(command, [...args], {
|
|
270
280
|
cwd,
|
|
271
|
-
env:
|
|
272
|
-
...process.env,
|
|
273
|
-
...options?.env,
|
|
274
|
-
},
|
|
281
|
+
env: buildProcessEnv(options),
|
|
275
282
|
stdio: 'ignore',
|
|
276
283
|
windowsHide: process.platform === 'win32',
|
|
277
284
|
});
|
|
@@ -302,10 +309,7 @@ export async function commandOutput(name, args, options) {
|
|
|
302
309
|
return await new Promise((resolve, reject) => {
|
|
303
310
|
const child = spawn(command, [...args], {
|
|
304
311
|
cwd,
|
|
305
|
-
env:
|
|
306
|
-
...process.env,
|
|
307
|
-
...options?.env,
|
|
308
|
-
},
|
|
312
|
+
env: buildProcessEnv(options),
|
|
309
313
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
310
314
|
windowsHide: process.platform === 'win32',
|
|
311
315
|
});
|
|
@@ -364,10 +368,7 @@ export async function commandOutputViaFile(name, args, options) {
|
|
|
364
368
|
const result = await new Promise((resolve, reject) => {
|
|
365
369
|
const child = spawn(command, [...args], {
|
|
366
370
|
cwd,
|
|
367
|
-
env:
|
|
368
|
-
...process.env,
|
|
369
|
-
...options?.env,
|
|
370
|
-
},
|
|
371
|
+
env: buildProcessEnv(options),
|
|
371
372
|
stdio: ['ignore', stdoutHandle.fd, stderrHandle.fd],
|
|
372
373
|
windowsHide: process.platform === 'win32',
|
|
373
374
|
});
|
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));
|