@nocobase/cli 2.2.0-beta.1 → 2.2.0-beta.11
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/proxy-location.conf +1 -0
- package/bin/early-locale.js +89 -0
- package/bin/node-version.js +35 -0
- package/bin/run.js +9 -0
- package/bin/windows-admin.js +60 -0
- package/dist/commands/app/destroy.js +4 -3
- package/dist/commands/app/restart.js +38 -0
- package/dist/commands/app/shared.js +49 -3
- package/dist/commands/app/start.js +95 -0
- package/dist/commands/app/upgrade.js +11 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/examples/prompts-stages.js +2 -2
- package/dist/commands/examples/prompts-test.js +2 -2
- package/dist/commands/init.js +33 -11
- package/dist/commands/install.js +159 -107
- package/dist/commands/license/activate.js +4 -1
- package/dist/commands/license/shared.js +24 -15
- package/dist/commands/proxy/caddy/generate.js +93 -7
- package/dist/commands/proxy/nginx/generate.js +98 -7
- package/dist/commands/revision/create.js +1 -1
- package/dist/commands/self/check.js +1 -1
- package/dist/commands/self/update.js +4 -4
- package/dist/commands/skills/check.js +4 -5
- package/dist/commands/skills/install.js +18 -1
- package/dist/commands/skills/update.js +19 -4
- package/dist/commands/source/dev.js +9 -5
- package/dist/commands/source/download.js +85 -16
- package/dist/lib/api-command-compat.js +51 -8
- package/dist/lib/app-managed-resources.js +104 -5
- package/dist/lib/auth-store.js +102 -12
- package/dist/lib/cli-config.js +73 -1
- package/dist/lib/docker-image.js +94 -6
- package/dist/lib/env-auth.js +291 -45
- package/dist/lib/env-config.js +11 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +164 -58
- package/dist/lib/hook-script.js +160 -0
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-validators.js +1 -1
- package/dist/lib/prompt-web-ui.js +20 -13
- package/dist/lib/proxy-caddy.js +77 -9
- package/dist/lib/proxy-nginx.js +71 -11
- package/dist/lib/run-npm.js +4 -0
- package/dist/lib/self-manager.js +254 -46
- package/dist/lib/skills-manager.js +116 -23
- package/dist/lib/source-publish.js +2 -2
- package/dist/lib/startup-update.js +1 -1
- package/dist/locale/en-US.json +49 -43
- package/dist/locale/zh-CN.json +49 -43
- package/package.json +8 -2
- package/scripts/build.mjs +0 -34
- package/scripts/clean.mjs +0 -9
- package/tsconfig.json +0 -19
|
@@ -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) {
|
|
@@ -10,7 +10,7 @@ import { spawn } from 'node:child_process';
|
|
|
10
10
|
import net from 'node:net';
|
|
11
11
|
import { translateCli } from "./cli-locale.js";
|
|
12
12
|
const API_BASE_URL_EXAMPLE = 'http://localhost:13000/api';
|
|
13
|
-
const ENV_KEY_PATTERN = /^[A-Za-z0-
|
|
13
|
+
const ENV_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
14
14
|
const APP_PUBLIC_PATH_PATTERN = /^\/(?:[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*)?\/?$/;
|
|
15
15
|
const TCP_PORT_EXAMPLE = '13000';
|
|
16
16
|
const API_BASE_URL_REQUEST_TIMEOUT_MS = 5_000;
|
|
@@ -19,7 +19,8 @@ export const PWC_FORM_META_STEP = '_pwcStep';
|
|
|
19
19
|
/** Form POST JSON meta field: current field key when validating a single field. */
|
|
20
20
|
export const PWC_FORM_META_FIELD = '_pwcField';
|
|
21
21
|
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
22
|
-
const
|
|
22
|
+
const DEFAULT_PUBLIC_HOST = '127.0.0.1';
|
|
23
|
+
const LISTEN_HOST = '0.0.0.0';
|
|
23
24
|
function resolveUiText(text, locale, fallback = '') {
|
|
24
25
|
return resolveLocalizedText(text, { locale, fallback });
|
|
25
26
|
}
|
|
@@ -47,13 +48,24 @@ function isInputBlock(def) {
|
|
|
47
48
|
def.type === 'password' ||
|
|
48
49
|
def.type === 'integer');
|
|
49
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
|
+
}
|
|
50
62
|
/**
|
|
51
63
|
* Merges CLI/env **`userPreset`** with catalog block defaults, in the same key order and with the
|
|
52
64
|
* same `hidden` / `run` semantics as {@link isPromptBlockSkipped}, so the web form can prefill
|
|
53
65
|
* and reflow `hidden` fields (e.g. `integer` when `select` changes).
|
|
54
66
|
*/
|
|
55
67
|
export function buildWebFormValuesFromCatalog(catalog, userPreset = {}) {
|
|
56
|
-
const out =
|
|
68
|
+
const out = buildPromptComputationSeed(catalog, userPreset);
|
|
57
69
|
for (const [key, def] of Object.entries(catalog)) {
|
|
58
70
|
if (def.type === 'intro' || def.type === 'outro') {
|
|
59
71
|
continue;
|
|
@@ -108,7 +120,7 @@ function defaultValueForInput(key, def, out) {
|
|
|
108
120
|
* from current raw form data (e.g. after changing `select`). Matches how {@link isPromptBlockSkipped} uses `out` while iterating the catalog.
|
|
109
121
|
*/
|
|
110
122
|
export function reflowWebFormState(catalog, raw, userSeed = {}) {
|
|
111
|
-
const out =
|
|
123
|
+
const out = buildPromptComputationSeed(catalog, userSeed);
|
|
112
124
|
const show = {};
|
|
113
125
|
for (const [key, def] of Object.entries(catalog)) {
|
|
114
126
|
if (def.type === 'intro' || def.type === 'outro') {
|
|
@@ -705,7 +717,7 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
705
717
|
const initialShow = reflowWebFormState(merged, Object.fromEntries(Object.entries(formDefaults).map(([k, v]) => [k, v])), userSeed).show;
|
|
706
718
|
const submitPath = options.submitPath ?? DEFAULT_SUBMIT;
|
|
707
719
|
const reflowPath = options.reflowPath ?? DEFAULT_REFLOW;
|
|
708
|
-
const
|
|
720
|
+
const publicHost = options.host ?? DEFAULT_PUBLIC_HOST;
|
|
709
721
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
710
722
|
const pageTitle = resolveUiText(options.pageTitle, locale, t('promptCatalog.web.pageTitle'));
|
|
711
723
|
const h1 = resolveUiText(options.documentHeading, locale, t('promptCatalog.web.documentHeading'));
|
|
@@ -753,7 +765,7 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
753
765
|
}
|
|
754
766
|
};
|
|
755
767
|
const servePage = (port) => {
|
|
756
|
-
const base = `http://${
|
|
768
|
+
const base = `http://${publicHost}:${port}`;
|
|
757
769
|
const formInner = buildPwcFormHtml(catalog, formDefaults, initialShow, pwcStepDefs, 0, pwcNSteps, locale, uiText);
|
|
758
770
|
const wizardClientJson = JSON.stringify({ n: pwcNSteps, stepDefs: pwcStepDefs });
|
|
759
771
|
const pwcValStepUrl = pwcNSteps > 1 ? JSON.stringify(base + resolveValidateStepPath) : 'null';
|
|
@@ -2071,11 +2083,6 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
2071
2083
|
return page;
|
|
2072
2084
|
};
|
|
2073
2085
|
server = createServer((req, res) => {
|
|
2074
|
-
if (!req.socket.remoteAddress ||
|
|
2075
|
-
!['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(req.socket.remoteAddress)) {
|
|
2076
|
-
res.writeHead(403).end();
|
|
2077
|
-
return;
|
|
2078
|
-
}
|
|
2079
2086
|
if (req.method === 'GET' && (req.url === '/' || req.url === '')) {
|
|
2080
2087
|
const addr = server?.address();
|
|
2081
2088
|
const port = typeof addr === 'object' && addr ? Number(addr.port) : 0;
|
|
@@ -2212,15 +2219,15 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
2212
2219
|
}
|
|
2213
2220
|
res.writeHead(404).end();
|
|
2214
2221
|
});
|
|
2215
|
-
server.listen(options.port ?? 0,
|
|
2222
|
+
server.listen(options.port ?? 0, LISTEN_HOST, () => {
|
|
2216
2223
|
const addr = server?.address();
|
|
2217
2224
|
if (typeof addr !== 'object' || !addr) {
|
|
2218
2225
|
rejectAndClose(new Error('Failed to bind HTTP server'));
|
|
2219
2226
|
return;
|
|
2220
2227
|
}
|
|
2221
2228
|
const port = addr.port;
|
|
2222
|
-
const startUrl = `http://${
|
|
2223
|
-
options.onServerStart?.({ host, port, url: startUrl });
|
|
2229
|
+
const startUrl = `http://${publicHost}:${port}/`;
|
|
2230
|
+
options.onServerStart?.({ host: publicHost, listenHost: LISTEN_HOST, port, url: startUrl });
|
|
2224
2231
|
const onOpenBrowserError = options.onOpenBrowserError ?? ((u, err) => console.warn(String(err), u));
|
|
2225
2232
|
try {
|
|
2226
2233
|
openUrlInDefaultBrowser(startUrl, onOpenBrowserError);
|
package/dist/lib/proxy-caddy.js
CHANGED
|
@@ -9,14 +9,17 @@
|
|
|
9
9
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { dockerContainerExists, dockerContainerIsRunning, startDockerContainer, stopDockerContainer, } from './app-runtime.js';
|
|
12
|
+
import { loadAuthConfig } from './auth-store.js';
|
|
12
13
|
import { CADDY_PROXY_DRIVER_OPTIONS, DEFAULT_CADDY_PROXY_DRIVER, getCliConfigValue, normalizeCaddyProxyDriver, resolveDockerContainerPrefix, setCliConfigValue, } from './cli-config.js';
|
|
13
14
|
import { resolveCliHomeRoot } from './cli-home.js';
|
|
14
|
-
import { applyEnvProxyAppEntryOptions, buildEnvProxyCaddyBundle, buildEnvProxyMainConfig, mapProxyPathFromCliRoot, resolveEnvProxyMainOutputPath, } from './env-proxy.js';
|
|
15
|
-
import {
|
|
15
|
+
import { applyEnvProxyAppEntryOptions, buildManualEnvProxyCaddyBundle, buildEnvProxyCaddyBundle, buildEnvProxyMainConfig, mapProxyPathFromCliRoot, resolveEnvProxyMainOutputPath, } from './env-proxy.js';
|
|
16
|
+
import { normalizeEnvProxyConfig } from './env-proxy-config.js';
|
|
17
|
+
import { commandOutput, run } from './run-npm.js';
|
|
16
18
|
const DOCKER_CADDY_PROXY_CONTAINER_SUFFIX = 'caddy-proxy';
|
|
17
19
|
const DOCKER_CADDY_PROXY_IMAGE = 'caddy:latest';
|
|
18
20
|
const DOCKER_CADDY_PROXY_RUNTIME_ROOT = '/apps';
|
|
19
21
|
const DOCKER_CADDY_PROXY_CONF_DESTINATION = '/etc/caddy/Caddyfile';
|
|
22
|
+
const DEFAULT_DOCKER_CADDY_PROXY_PUBLISHED_PORTS = [80, 443];
|
|
20
23
|
async function readOptionalTextFile(filePath) {
|
|
21
24
|
try {
|
|
22
25
|
return await readFile(filePath, 'utf8');
|
|
@@ -62,8 +65,9 @@ export async function resolveCaddyProxyRuntimeContext(options) {
|
|
|
62
65
|
upstreamHost: resolveCaddyProxyUpstreamHost(driver),
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
|
-
export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext) {
|
|
68
|
+
export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext, options) {
|
|
66
69
|
const bundle = await buildEnvProxyCaddyBundle(runtime, {
|
|
70
|
+
cdnBaseUrl: options?.cdnBaseUrl,
|
|
67
71
|
runtimeCliRoot: runtimeContext.runtimeCliRoot,
|
|
68
72
|
upstreamHost: runtimeContext.upstreamHost,
|
|
69
73
|
});
|
|
@@ -82,6 +86,27 @@ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeCon
|
|
|
82
86
|
status,
|
|
83
87
|
};
|
|
84
88
|
}
|
|
89
|
+
export async function writeManualCaddyProxyBundle(input, appEntryOptions, runtimeContext, options) {
|
|
90
|
+
const bundle = await buildManualEnvProxyCaddyBundle(input, {
|
|
91
|
+
cdnBaseUrl: options?.cdnBaseUrl,
|
|
92
|
+
runtimeCliRoot: runtimeContext.runtimeCliRoot,
|
|
93
|
+
upstreamHost: input.upstreamHost || runtimeContext.upstreamHost,
|
|
94
|
+
});
|
|
95
|
+
const currentAppConfigContent = await readOptionalTextFile(bundle.appConfigPath);
|
|
96
|
+
const nextAppConfigContent = applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'caddy', appEntryOptions);
|
|
97
|
+
const status = currentAppConfigContent ? 'updated' : 'created';
|
|
98
|
+
await Promise.all([mkdir(bundle.entryDir, { recursive: true }), mkdir(bundle.publicDir, { recursive: true })]);
|
|
99
|
+
await Promise.all([
|
|
100
|
+
writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
|
|
101
|
+
writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
|
|
102
|
+
writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
|
|
103
|
+
writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
|
|
104
|
+
]);
|
|
105
|
+
return {
|
|
106
|
+
bundle,
|
|
107
|
+
status,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
85
110
|
function resolveLocalCaddyPidFilePath() {
|
|
86
111
|
return path.join(path.dirname(resolveEnvProxyMainOutputPath({ provider: 'caddy' })), 'caddy.pid');
|
|
87
112
|
}
|
|
@@ -149,11 +174,16 @@ async function reloadLocalCaddyProxy(runtimeContext) {
|
|
|
149
174
|
}
|
|
150
175
|
async function ensureDockerCaddyProxyContainer(runtimeContext) {
|
|
151
176
|
const containerName = await resolveCaddyProxyContainerName();
|
|
177
|
+
const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
|
|
178
|
+
const publishedPorts = await resolveDockerCaddyPublishedPorts();
|
|
152
179
|
if (await dockerContainerExists(containerName)) {
|
|
153
|
-
|
|
180
|
+
if (await dockerCaddyProxyContainerMatchesPublishedPorts(containerName, publishedPorts)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
await removeDockerCaddyProxyContainer(containerName);
|
|
154
184
|
}
|
|
155
185
|
const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
|
|
156
|
-
const
|
|
186
|
+
const dockerPortArgs = publishedPorts.flatMap((port) => ['-p', `${port}:${port}`]);
|
|
157
187
|
await run('docker', [
|
|
158
188
|
'run',
|
|
159
189
|
'-d',
|
|
@@ -161,8 +191,7 @@ async function ensureDockerCaddyProxyContainer(runtimeContext) {
|
|
|
161
191
|
containerName,
|
|
162
192
|
'--add-host',
|
|
163
193
|
'host.docker.internal:host-gateway',
|
|
164
|
-
|
|
165
|
-
'80:80',
|
|
194
|
+
...dockerPortArgs,
|
|
166
195
|
'-v',
|
|
167
196
|
`${hostCliRoot}:${DOCKER_CADDY_PROXY_RUNTIME_ROOT}`,
|
|
168
197
|
'-v',
|
|
@@ -173,14 +202,53 @@ async function ensureDockerCaddyProxyContainer(runtimeContext) {
|
|
|
173
202
|
stdio: 'ignore',
|
|
174
203
|
});
|
|
175
204
|
}
|
|
205
|
+
async function resolveDockerCaddyPublishedPorts() {
|
|
206
|
+
const config = await loadAuthConfig();
|
|
207
|
+
const ports = new Set(DEFAULT_DOCKER_CADDY_PROXY_PUBLISHED_PORTS);
|
|
208
|
+
for (const envConfig of Object.values(config.envs)) {
|
|
209
|
+
const port = normalizeEnvProxyConfig(envConfig.proxy)?.port;
|
|
210
|
+
if (port !== undefined) {
|
|
211
|
+
ports.add(port);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return Array.from(ports).sort((left, right) => left - right);
|
|
215
|
+
}
|
|
216
|
+
async function readDockerCaddyPublishedPorts(containerName) {
|
|
217
|
+
const output = await commandOutput('docker', ['inspect', '--format', '{{json .HostConfig.PortBindings}}', containerName], { errorName: 'docker inspect' });
|
|
218
|
+
const parsed = JSON.parse(output.trim() || '{}');
|
|
219
|
+
const ports = new Set();
|
|
220
|
+
for (const bindings of Object.values(parsed)) {
|
|
221
|
+
if (!Array.isArray(bindings)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
for (const binding of bindings) {
|
|
225
|
+
const port = Number.parseInt(String(binding?.HostPort ?? '').trim(), 10);
|
|
226
|
+
if (Number.isInteger(port) && port > 0) {
|
|
227
|
+
ports.add(port);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return Array.from(ports).sort((left, right) => left - right);
|
|
232
|
+
}
|
|
233
|
+
async function dockerCaddyProxyContainerMatchesPublishedPorts(containerName, expectedPorts) {
|
|
234
|
+
const currentPorts = await readDockerCaddyPublishedPorts(containerName);
|
|
235
|
+
return currentPorts.length === expectedPorts.length && currentPorts.every((port, index) => port === expectedPorts[index]);
|
|
236
|
+
}
|
|
237
|
+
async function removeDockerCaddyProxyContainer(containerName) {
|
|
238
|
+
await run('docker', ['rm', '-f', containerName], {
|
|
239
|
+
errorName: 'docker rm',
|
|
240
|
+
stdio: 'ignore',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
176
243
|
async function startDockerCaddyProxy(runtimeContext) {
|
|
177
244
|
const containerName = await resolveCaddyProxyContainerName();
|
|
178
245
|
await ensureCaddyProxyMainConfig(runtimeContext);
|
|
179
|
-
|
|
246
|
+
const existedBeforeEnsure = await dockerContainerExists(containerName);
|
|
247
|
+
await ensureDockerCaddyProxyContainer(runtimeContext);
|
|
248
|
+
if (existedBeforeEnsure && (await dockerContainerExists(containerName))) {
|
|
180
249
|
const state = await startDockerContainer(containerName, { stdio: 'ignore' });
|
|
181
250
|
return state === 'already-running' ? 'already-running' : 'started';
|
|
182
251
|
}
|
|
183
|
-
await ensureDockerCaddyProxyContainer(runtimeContext);
|
|
184
252
|
return 'started';
|
|
185
253
|
}
|
|
186
254
|
async function stopDockerCaddyProxy() {
|
package/dist/lib/proxy-nginx.js
CHANGED
|
@@ -9,14 +9,17 @@
|
|
|
9
9
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { dockerContainerExists, dockerContainerIsRunning, startDockerContainer, stopDockerContainer, } from './app-runtime.js';
|
|
12
|
+
import { loadAuthConfig } from './auth-store.js';
|
|
12
13
|
import { DEFAULT_NGINX_PROXY_DRIVER, getCliConfigValue, NGINX_PROXY_DRIVER_OPTIONS, normalizeNginxProxyDriver, resolveDockerContainerPrefix, setCliConfigValue, } from './cli-config.js';
|
|
13
14
|
import { resolveCliHomeRoot } from './cli-home.js';
|
|
14
|
-
import { applyEnvProxyAppEntryOptions, appConfigHasManagedNginxBlock, buildEnvProxyMainConfig, buildEnvProxyNginxBundle, extractManagedNginxConfigBlock, installEnvProxyProvider, mapProxyPathFromCliRoot, reloadEnvProxyProvider, resolveEnvProxyMainOutputPath, replaceManagedNginxConfigBlock, syncEnvProxyNginxSnippets, } from './env-proxy.js';
|
|
15
|
-
import {
|
|
15
|
+
import { applyEnvProxyAppEntryOptions, appConfigHasManagedNginxBlock, buildManualEnvProxyNginxBundle, buildEnvProxyMainConfig, buildEnvProxyNginxBundle, extractManagedNginxConfigBlock, installEnvProxyProvider, mapProxyPathFromCliRoot, reloadEnvProxyProvider, resolveEnvProxyMainOutputPath, replaceManagedNginxConfigBlock, syncEnvProxyNginxSnippets, } from './env-proxy.js';
|
|
16
|
+
import { normalizeEnvProxyConfig } from './env-proxy-config.js';
|
|
17
|
+
import { commandOutput, run } from './run-npm.js';
|
|
16
18
|
const DOCKER_NGINX_PROXY_CONTAINER_SUFFIX = 'nginx-proxy';
|
|
17
19
|
const DOCKER_NGINX_PROXY_IMAGE = 'nginx:latest';
|
|
18
20
|
const DOCKER_NGINX_PROXY_RUNTIME_ROOT = '/apps';
|
|
19
21
|
const DOCKER_NGINX_PROXY_CONF_DESTINATION = '/etc/nginx/conf.d/default.conf';
|
|
22
|
+
const DEFAULT_DOCKER_NGINX_PROXY_PUBLISHED_PORTS = [80, 443];
|
|
20
23
|
async function readOptionalTextFile(filePath) {
|
|
21
24
|
try {
|
|
22
25
|
return await readFile(filePath, 'utf8');
|
|
@@ -66,11 +69,23 @@ function buildNginxManagedBlockMissingMessage(appConfigPath) {
|
|
|
66
69
|
return (`The editable nginx app entry config at ${appConfigPath} does not contain the NocoBase managed block. ` +
|
|
67
70
|
'Restore the managed block or delete the file and regenerate the proxy config.');
|
|
68
71
|
}
|
|
69
|
-
export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext) {
|
|
72
|
+
export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext, options) {
|
|
70
73
|
const bundle = await buildEnvProxyNginxBundle(runtime, {
|
|
74
|
+
cdnBaseUrl: options?.cdnBaseUrl,
|
|
71
75
|
runtimeCliRoot: runtimeContext.runtimeCliRoot,
|
|
72
76
|
upstreamHost: runtimeContext.upstreamHost,
|
|
73
77
|
});
|
|
78
|
+
return await writeResolvedNginxProxyBundle(bundle, appEntryOptions, options);
|
|
79
|
+
}
|
|
80
|
+
export async function writeManualNginxProxyBundle(input, appEntryOptions, runtimeContext, options) {
|
|
81
|
+
const bundle = await buildManualEnvProxyNginxBundle(input, {
|
|
82
|
+
cdnBaseUrl: options?.cdnBaseUrl,
|
|
83
|
+
runtimeCliRoot: runtimeContext.runtimeCliRoot,
|
|
84
|
+
upstreamHost: runtimeContext.upstreamHost,
|
|
85
|
+
});
|
|
86
|
+
return await writeResolvedNginxProxyBundle(bundle, appEntryOptions, options);
|
|
87
|
+
}
|
|
88
|
+
async function writeResolvedNginxProxyBundle(bundle, appEntryOptions, options) {
|
|
74
89
|
const managedConfigBlock = extractManagedNginxConfigBlock(bundle.appConfigContent);
|
|
75
90
|
if (!managedConfigBlock) {
|
|
76
91
|
throw new Error('Failed to render the managed nginx config block.');
|
|
@@ -79,10 +94,12 @@ export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeCon
|
|
|
79
94
|
let nextAppConfigContent = applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'nginx', appEntryOptions);
|
|
80
95
|
let status = 'created';
|
|
81
96
|
if (currentAppConfigContent) {
|
|
82
|
-
if (!appConfigHasManagedNginxBlock(currentAppConfigContent)) {
|
|
97
|
+
if (!appConfigHasManagedNginxBlock(currentAppConfigContent) && !options?.force) {
|
|
83
98
|
throw new Error(buildNginxManagedBlockMissingMessage(bundle.appConfigPath));
|
|
84
99
|
}
|
|
85
|
-
nextAppConfigContent =
|
|
100
|
+
nextAppConfigContent = appConfigHasManagedNginxBlock(currentAppConfigContent)
|
|
101
|
+
? applyEnvProxyAppEntryOptions(replaceManagedNginxConfigBlock(currentAppConfigContent, managedConfigBlock), 'nginx', appEntryOptions)
|
|
102
|
+
: applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'nginx', appEntryOptions);
|
|
86
103
|
status = 'updated';
|
|
87
104
|
}
|
|
88
105
|
await Promise.all([mkdir(bundle.entryDir, { recursive: true }), mkdir(bundle.publicDir, { recursive: true })]);
|
|
@@ -217,11 +234,16 @@ async function reloadLocalNginxProxy(runtimeContext) {
|
|
|
217
234
|
}
|
|
218
235
|
async function ensureDockerNginxProxyContainer(runtimeContext) {
|
|
219
236
|
const containerName = await resolveNginxProxyContainerName();
|
|
237
|
+
const mainConfigPath = await ensureNginxProxyMainConfig(runtimeContext);
|
|
238
|
+
const publishedPorts = await resolveDockerNginxPublishedPorts();
|
|
220
239
|
if (await dockerContainerExists(containerName)) {
|
|
221
|
-
|
|
240
|
+
if (await dockerNginxProxyContainerMatchesPublishedPorts(containerName, publishedPorts)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
await removeDockerNginxProxyContainer(containerName);
|
|
222
244
|
}
|
|
223
245
|
const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
|
|
224
|
-
const
|
|
246
|
+
const dockerPortArgs = publishedPorts.flatMap((port) => ['-p', `${port}:${port}`]);
|
|
225
247
|
await run('docker', [
|
|
226
248
|
'run',
|
|
227
249
|
'-d',
|
|
@@ -229,8 +251,7 @@ async function ensureDockerNginxProxyContainer(runtimeContext) {
|
|
|
229
251
|
containerName,
|
|
230
252
|
'--add-host',
|
|
231
253
|
'host.docker.internal:host-gateway',
|
|
232
|
-
|
|
233
|
-
'80:80',
|
|
254
|
+
...dockerPortArgs,
|
|
234
255
|
'-v',
|
|
235
256
|
`${hostCliRoot}:${DOCKER_NGINX_PROXY_RUNTIME_ROOT}`,
|
|
236
257
|
'-v',
|
|
@@ -241,14 +262,53 @@ async function ensureDockerNginxProxyContainer(runtimeContext) {
|
|
|
241
262
|
stdio: 'ignore',
|
|
242
263
|
});
|
|
243
264
|
}
|
|
265
|
+
async function resolveDockerNginxPublishedPorts() {
|
|
266
|
+
const config = await loadAuthConfig();
|
|
267
|
+
const ports = new Set(DEFAULT_DOCKER_NGINX_PROXY_PUBLISHED_PORTS);
|
|
268
|
+
for (const envConfig of Object.values(config.envs)) {
|
|
269
|
+
const port = normalizeEnvProxyConfig(envConfig.proxy)?.port;
|
|
270
|
+
if (port !== undefined) {
|
|
271
|
+
ports.add(port);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return Array.from(ports).sort((left, right) => left - right);
|
|
275
|
+
}
|
|
276
|
+
async function readDockerNginxPublishedPorts(containerName) {
|
|
277
|
+
const output = await commandOutput('docker', ['inspect', '--format', '{{json .HostConfig.PortBindings}}', containerName], { errorName: 'docker inspect' });
|
|
278
|
+
const parsed = JSON.parse(output.trim() || '{}');
|
|
279
|
+
const ports = new Set();
|
|
280
|
+
for (const bindings of Object.values(parsed)) {
|
|
281
|
+
if (!Array.isArray(bindings)) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
for (const binding of bindings) {
|
|
285
|
+
const port = Number.parseInt(String(binding?.HostPort ?? '').trim(), 10);
|
|
286
|
+
if (Number.isInteger(port) && port > 0) {
|
|
287
|
+
ports.add(port);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return Array.from(ports).sort((left, right) => left - right);
|
|
292
|
+
}
|
|
293
|
+
async function dockerNginxProxyContainerMatchesPublishedPorts(containerName, expectedPorts) {
|
|
294
|
+
const currentPorts = await readDockerNginxPublishedPorts(containerName);
|
|
295
|
+
return currentPorts.length === expectedPorts.length && currentPorts.every((port, index) => port === expectedPorts[index]);
|
|
296
|
+
}
|
|
297
|
+
async function removeDockerNginxProxyContainer(containerName) {
|
|
298
|
+
await run('docker', ['rm', '-f', containerName], {
|
|
299
|
+
errorName: 'docker rm',
|
|
300
|
+
stdio: 'ignore',
|
|
301
|
+
});
|
|
302
|
+
}
|
|
244
303
|
async function startDockerNginxProxy(runtimeContext) {
|
|
245
304
|
const containerName = await resolveNginxProxyContainerName();
|
|
246
305
|
await ensureNginxProxyMainConfig(runtimeContext);
|
|
247
|
-
|
|
306
|
+
const existedBeforeEnsure = await dockerContainerExists(containerName);
|
|
307
|
+
await ensureDockerNginxProxyContainer(runtimeContext);
|
|
308
|
+
if (existedBeforeEnsure && (await dockerContainerExists(containerName))) {
|
|
248
309
|
const state = await startDockerContainer(containerName, { stdio: 'ignore' });
|
|
249
310
|
return state === 'already-running' ? 'already-running' : 'started';
|
|
250
311
|
}
|
|
251
|
-
await ensureDockerNginxProxyContainer(runtimeContext);
|
|
252
312
|
return 'started';
|
|
253
313
|
}
|
|
254
314
|
async function stopDockerNginxProxy() {
|
package/dist/lib/run-npm.js
CHANGED