@nocobase/cli 2.2.0-beta.8 → 2.3.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/env-proxy/nginx/app.conf.tpl +23 -0
- package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
- package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
- package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
- package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
- package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +18 -0
- package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
- package/dist/commands/app/start.js +4 -1
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/init.js +11 -1
- package/dist/commands/install.js +61 -130
- 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/source/download.js +18 -14
- package/dist/lib/app-managed-resources.js +3 -2
- package/dist/lib/auth-store.js +68 -0
- package/dist/lib/cli-config.js +54 -1
- package/dist/lib/docker-image.js +94 -6
- package/dist/lib/env-config.js +5 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +193 -59
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-web-ui.js +13 -2
- package/dist/lib/proxy-caddy.js +77 -9
- package/dist/lib/proxy-nginx.js +71 -11
- package/dist/locale/en-US.json +38 -38
- package/dist/locale/zh-CN.json +38 -38
- package/package.json +3 -2
package/dist/lib/env-proxy.js
CHANGED
|
@@ -259,7 +259,7 @@ export function applyEnvProxyAppEntryOptions(content, provider, options) {
|
|
|
259
259
|
function toCaddyPathMatcher(prefixPath) {
|
|
260
260
|
return `${prefixPath}*`;
|
|
261
261
|
}
|
|
262
|
-
export async function loadEnvProxySettings(runtime) {
|
|
262
|
+
export async function loadEnvProxySettings(runtime, options) {
|
|
263
263
|
const { envFilePath, envValues } = await readManagedRuntimeEnvValues(runtime);
|
|
264
264
|
const appPublicPath = resolveAppPublicPath(runtime.env.config.appPublicPath || envValues.APP_PUBLIC_PATH || DEFAULT_APP_PUBLIC_PATH);
|
|
265
265
|
const apiClientShareToken = /^true$/i.test(String(envValues.API_CLIENT_SHARE_TOKEN ?? '').trim());
|
|
@@ -274,7 +274,9 @@ export async function loadEnvProxySettings(runtime) {
|
|
|
274
274
|
wsPath: prefixRuntimePath(appPublicPath, envValues.WS_PATH || DEFAULT_WS_PATH),
|
|
275
275
|
pluginStaticsPath: prefixRuntimePath(appPublicPath, envValues.PLUGIN_STATICS_PATH || DEFAULT_PLUGIN_STATICS_PATH, { trailingSlash: true }),
|
|
276
276
|
modernClientPrefix: normalizeModernClientPrefix(envValues.APP_MODERN_CLIENT_PREFIX),
|
|
277
|
-
cdnBaseUrl: trimValue(
|
|
277
|
+
cdnBaseUrl: trimValue(options?.cdnBaseUrl) ??
|
|
278
|
+
trimValue(runtime.env.envVars?.CDN_BASE_URL) ??
|
|
279
|
+
trimValue(envValues.CDN_BASE_URL),
|
|
278
280
|
apiClientStoragePrefix: trimValue(envValues.API_CLIENT_STORAGE_PREFIX) ?? DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
279
281
|
apiClientStorageType: trimValue(envValues.API_CLIENT_STORAGE_TYPE) ?? DEFAULT_API_CLIENT_STORAGE_TYPE,
|
|
280
282
|
apiClientShareToken,
|
|
@@ -284,6 +286,53 @@ export async function loadEnvProxySettings(runtime) {
|
|
|
284
286
|
},
|
|
285
287
|
};
|
|
286
288
|
}
|
|
289
|
+
function createManualProxyEnvSettings(input) {
|
|
290
|
+
const appPublicPath = resolveAppPublicPath(input.appPublicPath || DEFAULT_APP_PUBLIC_PATH);
|
|
291
|
+
return {
|
|
292
|
+
appPublicPath,
|
|
293
|
+
apiBasePath: prefixRuntimePath(appPublicPath, DEFAULT_API_BASE_PATH, {
|
|
294
|
+
trailingSlash: true,
|
|
295
|
+
}),
|
|
296
|
+
distPath: resolveDistPublicPath(appPublicPath),
|
|
297
|
+
wsPath: prefixRuntimePath(appPublicPath, DEFAULT_WS_PATH),
|
|
298
|
+
pluginStaticsPath: prefixRuntimePath(appPublicPath, DEFAULT_PLUGIN_STATICS_PATH, {
|
|
299
|
+
trailingSlash: true,
|
|
300
|
+
}),
|
|
301
|
+
modernClientPrefix: DEFAULT_MODERN_CLIENT_PREFIX,
|
|
302
|
+
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
303
|
+
apiClientStoragePrefix: DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
304
|
+
apiClientStorageType: DEFAULT_API_CLIENT_STORAGE_TYPE,
|
|
305
|
+
apiClientShareToken: false,
|
|
306
|
+
wsUrl: '',
|
|
307
|
+
esmCdnBaseUrl: DEFAULT_ESM_CDN_BASE_URL,
|
|
308
|
+
esmCdnSuffix: '',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function normalizeManualNginxInput(input) {
|
|
312
|
+
const upstreamPort = trimValue(input.upstreamPort) ?? trimValue(input.appPort);
|
|
313
|
+
return {
|
|
314
|
+
name: String(input.name).trim(),
|
|
315
|
+
storagePath: String(input.storagePath).trim(),
|
|
316
|
+
distRootPath: String(input.distRootPath).trim(),
|
|
317
|
+
runtimeVersion: String(input.runtimeVersion).trim(),
|
|
318
|
+
appPublicPath: trimValue(input.appPublicPath),
|
|
319
|
+
upstreamHost: trimValue(input.upstreamHost),
|
|
320
|
+
upstreamPort,
|
|
321
|
+
appPort: trimValue(input.appPort),
|
|
322
|
+
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function normalizeProxyPort(value) {
|
|
326
|
+
const normalized = trimValue(value);
|
|
327
|
+
if (!normalized || !/^\d+$/.test(normalized)) {
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
const port = Number(normalized);
|
|
331
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
return normalized;
|
|
335
|
+
}
|
|
287
336
|
async function parseVersionFromPackageJson(content, sourceLabel) {
|
|
288
337
|
let parsed;
|
|
289
338
|
try {
|
|
@@ -438,7 +487,9 @@ function rewriteHtmlAssetPublicPath(html, currentPublicPath, nextPublicPath) {
|
|
|
438
487
|
}
|
|
439
488
|
function buildNginxManagedConfigBlock(context) {
|
|
440
489
|
const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
|
|
490
|
+
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
441
491
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
492
|
+
const fileAccessPath = `${context.appPublicPath}files/`;
|
|
442
493
|
const isRootMounted = context.appPublicPath === '/';
|
|
443
494
|
const appPublicPathRedirectBlock = isRootMounted
|
|
444
495
|
? ''
|
|
@@ -476,6 +527,24 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
476
527
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
477
528
|
' }',
|
|
478
529
|
'',
|
|
530
|
+
` location ^~ ${fileAccessPath} {`,
|
|
531
|
+
` proxy_pass ${context.backendUrl};`,
|
|
532
|
+
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
533
|
+
' }',
|
|
534
|
+
...(!isRootMounted
|
|
535
|
+
? [
|
|
536
|
+
'',
|
|
537
|
+
' location ^~ /files/ {',
|
|
538
|
+
` proxy_pass ${context.backendUrl};`,
|
|
539
|
+
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
540
|
+
' }',
|
|
541
|
+
]
|
|
542
|
+
: []),
|
|
543
|
+
'',
|
|
544
|
+
` location = ${apiBasePathNoTrailingSlash} {`,
|
|
545
|
+
` return 308 ${context.apiBasePath}$is_args$args;`,
|
|
546
|
+
' }',
|
|
547
|
+
'',
|
|
479
548
|
` location ^~ ${context.apiBasePath} {`,
|
|
480
549
|
` proxy_pass ${context.backendUrl};`,
|
|
481
550
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
@@ -525,65 +594,92 @@ function buildNginxRuntimeConfig(context, variant) {
|
|
|
525
594
|
function buildCaddyRuntimeConfig(context, variant) {
|
|
526
595
|
return buildNginxRuntimeConfig(context, variant);
|
|
527
596
|
}
|
|
528
|
-
async function buildEnvProxyNginxRenderContext(
|
|
529
|
-
const apiPort = trimValue(runtime.env.appPort ?? runtime.env.config.appPort);
|
|
530
|
-
if (!apiPort) {
|
|
531
|
-
throw new Error(translateCli('commands.envProxy.errors.missingAppPort', { envName: runtime.envName }, {
|
|
532
|
-
fallback: `Missing appPort for env "${runtime.envName}". Save or update the app port before generating proxy config.`,
|
|
533
|
-
}));
|
|
534
|
-
}
|
|
535
|
-
const activeVersion = trimValue(await readDistClientActiveVersion(runtime.env.storagePath));
|
|
536
|
-
if (!activeVersion) {
|
|
537
|
-
throw new Error(translateCli('commands.envProxy.errors.missingVersion', { envName: runtime.envName }, {
|
|
538
|
-
fallback: `Couldn't determine the app version for env "${runtime.envName}". Run \`nb env update ${runtime.envName}\` and try again.`,
|
|
539
|
-
}));
|
|
540
|
-
}
|
|
541
|
-
const { envFilePath, settings } = await loadEnvProxySettings(runtime);
|
|
597
|
+
async function buildEnvProxyNginxRenderContext(source, options) {
|
|
542
598
|
const proxyHost = await resolveProxyUpstreamHost(options);
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
const
|
|
599
|
+
const upstreamPort = normalizeProxyPort(options?.upstreamPort) ?? source.apiPort;
|
|
600
|
+
const backendUrl = `http://${proxyHost}:${upstreamPort}`;
|
|
601
|
+
const cdnBaseUrl = source.settings.cdnBaseUrl ?? buildDefaultCdnBaseUrl(source.settings.appPublicPath, source.activeVersion);
|
|
602
|
+
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope });
|
|
603
|
+
const publicDir = resolveEnvProxyNginxPublicOutputDir(source.envName, { scope: options?.scope });
|
|
547
604
|
const snippetsDir = resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope });
|
|
548
|
-
const distRootDir =
|
|
549
|
-
const uploadsDir = path.join(
|
|
605
|
+
const distRootDir = source.distRootPath;
|
|
606
|
+
const uploadsDir = path.join(source.storagePath, 'uploads');
|
|
550
607
|
const mappedEntryDir = await mapProxyPathFromCliRoot(entryDir, options);
|
|
551
608
|
const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
|
|
552
609
|
const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
|
|
553
610
|
const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
|
|
554
611
|
const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
|
|
555
|
-
const v2PublicPath = `${settings.appPublicPath.replace(/\/$/, '')}/${settings.modernClientPrefix}/`;
|
|
612
|
+
const v2PublicPath = `${source.settings.appPublicPath.replace(/\/$/, '')}/${source.settings.modernClientPrefix}/`;
|
|
556
613
|
return {
|
|
557
|
-
envName:
|
|
558
|
-
envFilePath,
|
|
559
|
-
apiBasePath: settings.apiBasePath,
|
|
560
|
-
apiClientShareToken: settings.apiClientShareToken,
|
|
561
|
-
apiClientStoragePrefix: settings.apiClientStoragePrefix,
|
|
562
|
-
apiClientStorageType: settings.apiClientStorageType,
|
|
563
|
-
apiPort,
|
|
564
|
-
appPublicPath: settings.appPublicPath,
|
|
614
|
+
envName: source.envName,
|
|
615
|
+
envFilePath: source.envFilePath,
|
|
616
|
+
apiBasePath: source.settings.apiBasePath,
|
|
617
|
+
apiClientShareToken: source.settings.apiClientShareToken,
|
|
618
|
+
apiClientStoragePrefix: source.settings.apiClientStoragePrefix,
|
|
619
|
+
apiClientStorageType: source.settings.apiClientStorageType,
|
|
620
|
+
apiPort: source.apiPort,
|
|
621
|
+
appPublicPath: source.settings.appPublicPath,
|
|
565
622
|
backendUrl,
|
|
566
623
|
cdnBaseUrl: ensureTrailingSlash(cdnBaseUrl),
|
|
567
|
-
distPath: settings.distPath,
|
|
624
|
+
distPath: source.settings.distPath,
|
|
568
625
|
distRootDir: mappedDistRootDir,
|
|
569
626
|
entryDir: mappedEntryDir,
|
|
570
627
|
publicDir: mappedPublicDir,
|
|
571
|
-
esmCdnBaseUrl: settings.esmCdnBaseUrl,
|
|
572
|
-
esmCdnSuffix: settings.esmCdnSuffix,
|
|
573
|
-
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(
|
|
574
|
-
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(
|
|
575
|
-
modernClientPrefix: settings.modernClientPrefix,
|
|
628
|
+
esmCdnBaseUrl: source.settings.esmCdnBaseUrl,
|
|
629
|
+
esmCdnSuffix: source.settings.esmCdnSuffix,
|
|
630
|
+
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
|
|
631
|
+
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
|
|
632
|
+
modernClientPrefix: source.settings.modernClientPrefix,
|
|
576
633
|
proxyHost,
|
|
577
634
|
snippetsDir: mappedSnippetsDir,
|
|
578
635
|
uploadsDir: mappedUploadsDir,
|
|
579
636
|
v2PublicPath,
|
|
580
|
-
wsPath: settings.wsPath,
|
|
581
|
-
wsUrl: settings.wsUrl,
|
|
637
|
+
wsPath: source.settings.wsPath,
|
|
638
|
+
wsUrl: source.settings.wsUrl,
|
|
639
|
+
activeVersion: source.activeVersion,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
async function resolveRuntimeNginxBundleSource(runtime, options) {
|
|
643
|
+
const apiPort = trimValue(runtime.env.appPort ?? runtime.env.config.appPort);
|
|
644
|
+
if (!apiPort) {
|
|
645
|
+
throw new Error(translateCli('commands.envProxy.errors.missingAppPort', { envName: runtime.envName }, {
|
|
646
|
+
fallback: `Missing appPort for env "${runtime.envName}". Save or update the app port before generating proxy config.`,
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
const activeVersion = trimValue(await readDistClientActiveVersion(runtime.env.storagePath));
|
|
650
|
+
if (!activeVersion) {
|
|
651
|
+
throw new Error(translateCli('commands.envProxy.errors.missingVersion', { envName: runtime.envName }, {
|
|
652
|
+
fallback: `Couldn't determine the app version for env "${runtime.envName}". Run \`nb env update ${runtime.envName}\` and try again.`,
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
const { envFilePath, settings } = await loadEnvProxySettings(runtime, options);
|
|
656
|
+
return {
|
|
657
|
+
envName: runtime.envName,
|
|
658
|
+
envFilePath,
|
|
659
|
+
storagePath: runtime.env.storagePath,
|
|
660
|
+
distRootPath: resolveDistClientRoot(runtime.env.storagePath),
|
|
661
|
+
settings,
|
|
662
|
+
apiPort,
|
|
582
663
|
activeVersion,
|
|
583
664
|
};
|
|
584
665
|
}
|
|
666
|
+
async function resolveManualNginxBundleSource(input) {
|
|
667
|
+
const normalized = normalizeManualNginxInput(input);
|
|
668
|
+
return {
|
|
669
|
+
envName: normalized.name,
|
|
670
|
+
envFilePath: undefined,
|
|
671
|
+
storagePath: normalized.storagePath,
|
|
672
|
+
distRootPath: normalized.distRootPath,
|
|
673
|
+
settings: createManualProxyEnvSettings(normalized),
|
|
674
|
+
apiPort: normalized.upstreamPort,
|
|
675
|
+
activeVersion: normalized.runtimeVersion,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
585
678
|
async function buildEnvProxyCaddyRenderContext(runtime, options) {
|
|
586
|
-
return await
|
|
679
|
+
return await buildEnvProxyCaddyRenderContextFromSource(await resolveRuntimeNginxBundleSource(runtime), options);
|
|
680
|
+
}
|
|
681
|
+
async function buildEnvProxyCaddyRenderContextFromSource(source, options) {
|
|
682
|
+
return await buildEnvProxyNginxRenderContext(source, {
|
|
587
683
|
...options,
|
|
588
684
|
provider: 'caddy',
|
|
589
685
|
});
|
|
@@ -636,11 +732,21 @@ export function resolveEnvProxyCaddyIndexOutputPath(envName, variant, options) {
|
|
|
636
732
|
return path.join(resolveEnvProxyCaddyPublicOutputDir(envName, { scope: options?.scope }), `index-${variant}.html`);
|
|
637
733
|
}
|
|
638
734
|
export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
639
|
-
|
|
735
|
+
return await buildNginxBundleFromSource(await resolveRuntimeNginxBundleSource(runtime, options), options);
|
|
736
|
+
}
|
|
737
|
+
export async function buildManualEnvProxyNginxBundle(input, options) {
|
|
738
|
+
return await buildNginxBundleFromSource(await resolveManualNginxBundleSource(input), {
|
|
739
|
+
...options,
|
|
740
|
+
upstreamHost: trimValue(input.upstreamHost) ?? options?.upstreamHost,
|
|
741
|
+
upstreamPort: normalizeProxyPort(input.upstreamPort) ?? options?.upstreamPort,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
async function buildNginxBundleFromSource(source, options) {
|
|
745
|
+
const context = await buildEnvProxyNginxRenderContext(source, options);
|
|
640
746
|
const appTemplate = await readEnvProxyNginxAssetText('app.conf.tpl');
|
|
641
747
|
const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
|
|
642
|
-
const sourceIndexV1Path = path.join(
|
|
643
|
-
const sourceIndexV2Path = path.join(
|
|
748
|
+
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
749
|
+
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
644
750
|
const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
|
|
645
751
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
646
752
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
@@ -667,13 +773,13 @@ export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
|
667
773
|
wsPath: context.wsPath,
|
|
668
774
|
};
|
|
669
775
|
return {
|
|
670
|
-
envName:
|
|
776
|
+
envName: source.envName,
|
|
671
777
|
envFilePath: context.envFilePath,
|
|
672
|
-
entryDir: resolveEnvProxyEntryDir(
|
|
673
|
-
publicDir: resolveEnvProxyNginxPublicOutputDir(
|
|
674
|
-
appConfigPath: resolveEnvProxyAppOutputPath(
|
|
675
|
-
indexV1Path: resolveEnvProxyNginxIndexOutputPath(
|
|
676
|
-
indexV2Path: resolveEnvProxyNginxIndexOutputPath(
|
|
778
|
+
entryDir: resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
779
|
+
publicDir: resolveEnvProxyNginxPublicOutputDir(source.envName, { scope: options?.scope }),
|
|
780
|
+
appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
781
|
+
indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
782
|
+
indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
677
783
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
|
|
678
784
|
snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
|
|
679
785
|
appPublicPath: context.appPublicPath,
|
|
@@ -694,9 +800,19 @@ export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
|
694
800
|
};
|
|
695
801
|
}
|
|
696
802
|
export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
803
|
+
return await buildCaddyBundleFromSource(await resolveRuntimeNginxBundleSource(runtime), options);
|
|
804
|
+
}
|
|
805
|
+
export async function buildManualEnvProxyCaddyBundle(input, options) {
|
|
806
|
+
return await buildCaddyBundleFromSource(await resolveManualNginxBundleSource(input), {
|
|
807
|
+
...options,
|
|
808
|
+
upstreamHost: trimValue(input.upstreamHost) ?? options?.upstreamHost,
|
|
809
|
+
upstreamPort: normalizeProxyPort(input.upstreamPort) ?? options?.upstreamPort,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
async function buildCaddyBundleFromSource(source, options) {
|
|
813
|
+
const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
|
|
814
|
+
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
815
|
+
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
700
816
|
const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
|
|
701
817
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
702
818
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
@@ -707,9 +823,9 @@ export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
|
707
823
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
708
824
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
709
825
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
710
|
-
const appConfigPath = resolveEnvProxyAppOutputPath(
|
|
711
|
-
const entryDir = resolveEnvProxyEntryDir(
|
|
712
|
-
const publicDir = resolveEnvProxyCaddyPublicOutputDir(
|
|
826
|
+
const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
827
|
+
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
828
|
+
const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
|
|
713
829
|
const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
|
|
714
830
|
const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
|
|
715
831
|
appPublicPath: context.appPublicPath,
|
|
@@ -725,13 +841,13 @@ export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
|
725
841
|
wsPath: context.wsPath,
|
|
726
842
|
}, renderedPublicDir);
|
|
727
843
|
return {
|
|
728
|
-
envName:
|
|
844
|
+
envName: source.envName,
|
|
729
845
|
envFilePath: context.envFilePath,
|
|
730
846
|
entryDir,
|
|
731
847
|
publicDir,
|
|
732
848
|
appConfigPath,
|
|
733
|
-
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(
|
|
734
|
-
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(
|
|
849
|
+
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
850
|
+
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
735
851
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
|
|
736
852
|
appPublicPath: context.appPublicPath,
|
|
737
853
|
apiBasePath: context.apiBasePath,
|
|
@@ -893,6 +1009,7 @@ function buildNginxOtherLocation(appPublicPath, v2PublicPath, modernClientPrefix
|
|
|
893
1009
|
function renderNginxLocationTemplate(context) {
|
|
894
1010
|
const proxyPassBlock = buildNginxProxyPassBlock(context.proxyHost, context.apiPort);
|
|
895
1011
|
const wsProxyPassTarget = `http://${context.proxyHost}:${context.apiPort}${context.wsPath}`;
|
|
1012
|
+
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
896
1013
|
return ` location ~* ^${context.appPublicPath}storage/uploads/(.*\\.md)$ {
|
|
897
1014
|
alias ${context.uploadsPath}/$1;
|
|
898
1015
|
default_type text/markdown;
|
|
@@ -938,6 +1055,10 @@ function renderNginxLocationTemplate(context) {
|
|
|
938
1055
|
${proxyPassBlock}
|
|
939
1056
|
}${context.otherLocation}
|
|
940
1057
|
|
|
1058
|
+
location = ${apiBasePathNoTrailingSlash} {
|
|
1059
|
+
return 308 ${context.apiBasePath}$is_args$args;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
941
1062
|
location ^~ ${context.apiBasePath} {
|
|
942
1063
|
${proxyPassBlock}
|
|
943
1064
|
}
|
|
@@ -989,6 +1110,7 @@ function buildCaddyContextCommentLines(siteAddress, context, publicDir) {
|
|
|
989
1110
|
}
|
|
990
1111
|
function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
991
1112
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1113
|
+
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
992
1114
|
const distPathMatcher = toCaddyPathMatcher(context.distPath);
|
|
993
1115
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
994
1116
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
@@ -1055,7 +1177,19 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1055
1177
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1056
1178
|
' }',
|
|
1057
1179
|
'',
|
|
1058
|
-
' # Keep API and WS routes above the SPA fallbacks.',
|
|
1180
|
+
' # Keep file, API and WS routes above the SPA fallbacks.',
|
|
1181
|
+
` handle ${fileAccessPathMatcher} {`,
|
|
1182
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1183
|
+
' }',
|
|
1184
|
+
...(context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
1185
|
+
? []
|
|
1186
|
+
: [
|
|
1187
|
+
'',
|
|
1188
|
+
' handle /files/* {',
|
|
1189
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1190
|
+
' }',
|
|
1191
|
+
]),
|
|
1192
|
+
'',
|
|
1059
1193
|
` handle ${apiPathMatcher} {`,
|
|
1060
1194
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1061
1195
|
' }',
|
|
@@ -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') {
|