@nocobase/cli 2.2.0-alpha.5 → 2.2.0-alpha.6
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/dist/commands/app/start.js +4 -1
- package/dist/commands/install.js +19 -93
- package/dist/commands/proxy/caddy/generate.js +12 -4
- package/dist/commands/proxy/nginx/generate.js +12 -4
- package/dist/lib/auth-store.js +61 -0
- package/dist/lib/env-config.js +5 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +10 -0
- package/dist/lib/proxy-caddy.js +53 -7
- package/dist/lib/proxy-nginx.js +53 -7
- package/package.json +2 -2
|
@@ -37,6 +37,9 @@ function buildLicenseSyncArgv(envName, options) {
|
|
|
37
37
|
}
|
|
38
38
|
function resolveHookCommand(value) {
|
|
39
39
|
const text = String(value ?? '').trim();
|
|
40
|
+
if (text === 'init') {
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
40
43
|
if (text === 'app:restart' || text === 'app:upgrade') {
|
|
41
44
|
return text;
|
|
42
45
|
}
|
|
@@ -225,7 +228,7 @@ export default class AppStart extends Command {
|
|
|
225
228
|
}),
|
|
226
229
|
'hook-command': Flags.string({
|
|
227
230
|
hidden: true,
|
|
228
|
-
options: ['app:start', 'app:restart', 'app:upgrade'],
|
|
231
|
+
options: ['init', 'app:start', 'app:restart', 'app:upgrade'],
|
|
229
232
|
}),
|
|
230
233
|
};
|
|
231
234
|
async run() {
|
package/dist/commands/install.js
CHANGED
|
@@ -2253,6 +2253,13 @@ export default class Install extends Command {
|
|
|
2253
2253
|
}
|
|
2254
2254
|
await this.config.runCommand('env:update', [params.envName]);
|
|
2255
2255
|
}
|
|
2256
|
+
buildAppStartArgv(params) {
|
|
2257
|
+
const argv = ['--env', params.envName, '--yes', '--no-sync-licensed-plugins', '--hook-command', 'init'];
|
|
2258
|
+
if (params.verbose) {
|
|
2259
|
+
argv.push('--verbose');
|
|
2260
|
+
}
|
|
2261
|
+
return argv;
|
|
2262
|
+
}
|
|
2256
2263
|
async runInstallHookIfNeeded(params) {
|
|
2257
2264
|
const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
|
|
2258
2265
|
const savedHookScript = Install.toOptionalPromptString(params.appResults.hookScript);
|
|
@@ -2497,7 +2504,6 @@ export default class Install extends Command {
|
|
|
2497
2504
|
const parsed = {
|
|
2498
2505
|
...flags,
|
|
2499
2506
|
};
|
|
2500
|
-
const defaultApiHost = await resolveDefaultApiHost();
|
|
2501
2507
|
if (parsed['skip-auth'] && (parsed['access-token'] !== undefined || parsed.token !== undefined)) {
|
|
2502
2508
|
this.error('--skip-auth cannot be used with --access-token or --token.');
|
|
2503
2509
|
}
|
|
@@ -2577,8 +2583,7 @@ export default class Install extends Command {
|
|
|
2577
2583
|
dbResults.dbUser = builtinDbPlan.dbUser;
|
|
2578
2584
|
dbResults.dbPassword = builtinDbPlan.dbPassword;
|
|
2579
2585
|
}
|
|
2580
|
-
let
|
|
2581
|
-
let localAppPlan;
|
|
2586
|
+
let shouldStartApp = false;
|
|
2582
2587
|
if (source === 'docker' || source === 'npm' || source === 'git') {
|
|
2583
2588
|
this.logStage('Preparing application');
|
|
2584
2589
|
if (source === 'docker') {
|
|
@@ -2590,111 +2595,28 @@ export default class Install extends Command {
|
|
|
2590
2595
|
printInfo('Application image ready.');
|
|
2591
2596
|
}
|
|
2592
2597
|
if (!parsed['prepare-only']) {
|
|
2593
|
-
|
|
2594
|
-
hookName: 'beforeAppInstall',
|
|
2595
|
-
envName,
|
|
2596
|
-
source,
|
|
2597
|
-
appResults,
|
|
2598
|
-
downloadResults,
|
|
2599
|
-
dbResults,
|
|
2600
|
-
rootResults,
|
|
2601
|
-
envAddResults,
|
|
2602
|
-
defaultApiHost,
|
|
2603
|
-
});
|
|
2604
|
-
dockerAppPlan = await this.installDockerApp({
|
|
2605
|
-
envName,
|
|
2606
|
-
dockerNetworkName,
|
|
2607
|
-
dockerContainerPrefix,
|
|
2608
|
-
appResults,
|
|
2609
|
-
downloadResults,
|
|
2610
|
-
dbResults,
|
|
2611
|
-
rootResults,
|
|
2612
|
-
builtinDbPlan,
|
|
2613
|
-
force: parsed.force,
|
|
2614
|
-
commandStdio,
|
|
2615
|
-
});
|
|
2616
|
-
appResults.appKey = dockerAppPlan.appKey;
|
|
2617
|
-
appResults.timeZone = dockerAppPlan.timeZone;
|
|
2598
|
+
shouldStartApp = true;
|
|
2618
2599
|
}
|
|
2619
2600
|
}
|
|
2620
2601
|
else if (source === 'npm' || source === 'git') {
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
? Install.resolveLocalProjectRoot({
|
|
2624
|
-
envName,
|
|
2625
|
-
appResults,
|
|
2626
|
-
downloadResults,
|
|
2627
|
-
})
|
|
2628
|
-
: await this.downloadLocalApp({
|
|
2602
|
+
if (!parsed['skip-download'] && !parsed['prepare-only']) {
|
|
2603
|
+
await this.downloadLocalApp({
|
|
2629
2604
|
envName,
|
|
2630
2605
|
appResults,
|
|
2631
2606
|
downloadResults,
|
|
2632
2607
|
verbose: parsed.verbose,
|
|
2633
2608
|
});
|
|
2634
|
-
if (!parsed['skip-download'] && !parsed['prepare-only']) {
|
|
2635
2609
|
printInfo('Application files ready.');
|
|
2636
2610
|
}
|
|
2637
2611
|
if (!parsed['prepare-only']) {
|
|
2638
|
-
|
|
2639
|
-
hookName: 'beforeAppInstall',
|
|
2640
|
-
envName,
|
|
2641
|
-
source,
|
|
2642
|
-
appResults,
|
|
2643
|
-
downloadResults,
|
|
2644
|
-
dbResults,
|
|
2645
|
-
rootResults,
|
|
2646
|
-
envAddResults,
|
|
2647
|
-
projectRoot,
|
|
2648
|
-
defaultApiHost,
|
|
2649
|
-
});
|
|
2650
|
-
localAppPlan = await this.startLocalApp({
|
|
2651
|
-
envName,
|
|
2652
|
-
source: localSource,
|
|
2653
|
-
projectRoot,
|
|
2654
|
-
appResults,
|
|
2655
|
-
dbResults,
|
|
2656
|
-
rootResults,
|
|
2657
|
-
commandStdio,
|
|
2658
|
-
});
|
|
2659
|
-
appResults.appKey = localAppPlan.appKey;
|
|
2660
|
-
appResults.timeZone = localAppPlan.timeZone;
|
|
2612
|
+
shouldStartApp = true;
|
|
2661
2613
|
}
|
|
2662
2614
|
}
|
|
2663
2615
|
}
|
|
2664
2616
|
else {
|
|
2665
2617
|
this.logDetail('Skipped app download and install.');
|
|
2666
2618
|
}
|
|
2667
|
-
if (
|
|
2668
|
-
this.logStage('Starting NocoBase');
|
|
2669
|
-
await this.waitForAppHealthCheck(Install.resolveApiBaseUrl({
|
|
2670
|
-
appResults,
|
|
2671
|
-
envAddResults,
|
|
2672
|
-
defaultApiHost,
|
|
2673
|
-
}), {
|
|
2674
|
-
containerName: dockerAppPlan?.containerName,
|
|
2675
|
-
verbose: parsed.verbose,
|
|
2676
|
-
});
|
|
2677
|
-
const displayApiBaseUrl = Install.resolveApiBaseUrl({
|
|
2678
|
-
appResults,
|
|
2679
|
-
envAddResults,
|
|
2680
|
-
defaultApiHost,
|
|
2681
|
-
});
|
|
2682
|
-
printInfo(`NocoBase is ready at ${formatInstallDisplayUrl(displayApiBaseUrl)}`);
|
|
2683
|
-
appResults.setupState = 'installed';
|
|
2684
|
-
await this.runInstallHookIfNeeded({
|
|
2685
|
-
hookName: 'afterAppStart',
|
|
2686
|
-
envName,
|
|
2687
|
-
source,
|
|
2688
|
-
appResults,
|
|
2689
|
-
downloadResults,
|
|
2690
|
-
dbResults,
|
|
2691
|
-
rootResults,
|
|
2692
|
-
envAddResults,
|
|
2693
|
-
projectRoot: localAppPlan?.projectRoot,
|
|
2694
|
-
defaultApiHost,
|
|
2695
|
-
});
|
|
2696
|
-
}
|
|
2697
|
-
if (dockerAppPlan || localAppPlan || builtinDbPlan) {
|
|
2619
|
+
if (shouldStartApp || builtinDbPlan) {
|
|
2698
2620
|
await this.saveInstalledEnv({
|
|
2699
2621
|
envName,
|
|
2700
2622
|
appResults,
|
|
@@ -2704,10 +2626,14 @@ export default class Install extends Command {
|
|
|
2704
2626
|
envAddResults,
|
|
2705
2627
|
});
|
|
2706
2628
|
}
|
|
2629
|
+
if (shouldStartApp) {
|
|
2630
|
+
this.logStage('Starting NocoBase');
|
|
2631
|
+
await this.config.runCommand('app:start', this.buildAppStartArgv({ envName, verbose: parsed.verbose }));
|
|
2632
|
+
}
|
|
2707
2633
|
await this.syncInstalledEnvConnection({
|
|
2708
2634
|
envName,
|
|
2709
2635
|
envAddResults,
|
|
2710
|
-
appReady:
|
|
2636
|
+
appReady: shouldStartApp,
|
|
2711
2637
|
skipAuth: Boolean(parsed['skip-auth']),
|
|
2712
2638
|
});
|
|
2713
2639
|
if (!parsed['prepare-only']) {
|
|
@@ -2716,7 +2642,7 @@ export default class Install extends Command {
|
|
|
2716
2642
|
if (parsed['prepare-only']) {
|
|
2717
2643
|
printInfo(`Preparation complete for "${envName}". Activate the license, then run \`nb app start --env ${envName}\`.`);
|
|
2718
2644
|
}
|
|
2719
|
-
else if (!
|
|
2645
|
+
else if (!shouldStartApp) {
|
|
2720
2646
|
printInfo(`Install config for "${envName}" has been saved.`);
|
|
2721
2647
|
}
|
|
2722
2648
|
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
|
|
11
|
+
import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
|
|
12
|
+
import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
|
|
11
13
|
import { getCaddyProxyDriver, writeManualCaddyProxyBundle, writeCaddyProxyBundle, resolveCaddyProxyRuntimeContext, } from '../../../lib/proxy-caddy.js';
|
|
12
14
|
import { normalizeProxyListenPort } from '../../../lib/proxy-nginx.js';
|
|
13
15
|
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
|
|
@@ -129,12 +131,18 @@ export default class ProxyCaddyGenerate extends Command {
|
|
|
129
131
|
announceTargetEnv(runtime.envName);
|
|
130
132
|
startTask(`Generating caddy proxy config for env "${runtime.envName}" with the ${driver} driver...`);
|
|
131
133
|
try {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'caddy');
|
|
135
|
+
const appEntryOptions = {
|
|
136
|
+
host: flags.host?.trim() || savedAppEntryOptions?.host,
|
|
137
|
+
port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
|
|
138
|
+
};
|
|
139
|
+
const { bundle, status } = await writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext, {
|
|
136
140
|
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
137
141
|
});
|
|
142
|
+
await setEnvProxyEntry(runtime.envName, 'caddy', {
|
|
143
|
+
host: appEntryOptions.host,
|
|
144
|
+
port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
|
|
145
|
+
}, { scope: resolveDefaultConfigScope() });
|
|
138
146
|
succeedTask(status === 'created'
|
|
139
147
|
? `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
|
|
140
148
|
: `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
|
|
11
|
+
import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
|
|
11
12
|
import { getNginxProxyDriver, normalizeProxyListenPort, resolveNginxProxyRuntimeContext, writeManualNginxProxyBundle, writeNginxProxyBundle, } from '../../../lib/proxy-nginx.js';
|
|
13
|
+
import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
|
|
12
14
|
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
|
|
13
15
|
export default class ProxyNginxGenerate extends Command {
|
|
14
16
|
static summary = 'Generate nginx proxy files for one managed env';
|
|
@@ -132,13 +134,19 @@ export default class ProxyNginxGenerate extends Command {
|
|
|
132
134
|
announceTargetEnv(runtime.envName);
|
|
133
135
|
startTask(`Generating nginx proxy config for env "${runtime.envName}" with the ${driver} driver...`);
|
|
134
136
|
try {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'nginx');
|
|
138
|
+
const appEntryOptions = {
|
|
139
|
+
host: flags.host?.trim() || savedAppEntryOptions?.host,
|
|
140
|
+
port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
|
|
141
|
+
};
|
|
142
|
+
const { bundle, status } = await writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext, {
|
|
139
143
|
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
140
144
|
force: flags.force,
|
|
141
145
|
});
|
|
146
|
+
await setEnvProxyEntry(runtime.envName, 'nginx', {
|
|
147
|
+
host: appEntryOptions.host,
|
|
148
|
+
port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
|
|
149
|
+
}, { scope: resolveDefaultConfigScope() });
|
|
142
150
|
succeedTask(status === 'created'
|
|
143
151
|
? `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
|
|
144
152
|
: `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -11,6 +11,7 @@ import path from 'node:path';
|
|
|
11
11
|
import { resolveAppPublicPath } from './app-public-path.js';
|
|
12
12
|
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
|
|
13
13
|
import { normalizeCliLocale } from './cli-locale.js';
|
|
14
|
+
import { normalizeEnvProxyConfig, normalizeEnvProxyProviderConfig, } from './env-proxy-config.js';
|
|
14
15
|
import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
|
|
15
16
|
import { ENV_CONFIG_SCHEMA_VERSION, normalizeEnvConfigSchemaVersion } from './env-config.js';
|
|
16
17
|
import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
|
|
@@ -81,12 +82,14 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
81
82
|
const normalizedKind = resolveEnvKind(entry);
|
|
82
83
|
const apiBaseUrl = readEnvApiBaseUrl(entry);
|
|
83
84
|
const schemaVersion = normalizeEnvConfigSchemaVersion(entry.schemaVersion);
|
|
85
|
+
const proxy = normalizeEnvProxyConfig(entry.proxy);
|
|
84
86
|
return {
|
|
85
87
|
...rest,
|
|
86
88
|
...(schemaVersion ? { schemaVersion } : {}),
|
|
87
89
|
...(normalizedKind ? { kind: normalizedKind } : {}),
|
|
88
90
|
...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
|
|
89
91
|
...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
|
|
92
|
+
...(proxy ? { proxy } : {}),
|
|
90
93
|
};
|
|
91
94
|
}
|
|
92
95
|
function normalizeAuthConfig(config) {
|
|
@@ -516,6 +519,64 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
|
|
|
516
519
|
};
|
|
517
520
|
await saveAuthConfig(config, options);
|
|
518
521
|
}
|
|
522
|
+
export function resolveEnvProxyEntry(config, provider) {
|
|
523
|
+
const proxy = normalizeEnvProxyConfig(config?.proxy);
|
|
524
|
+
const resolved = {
|
|
525
|
+
...(proxy?.host ? { host: proxy.host } : {}),
|
|
526
|
+
...(proxy?.port !== undefined ? { port: proxy.port } : {}),
|
|
527
|
+
...((provider === 'nginx' ? proxy?.nginx : proxy?.caddy) ?? {}),
|
|
528
|
+
};
|
|
529
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
530
|
+
}
|
|
531
|
+
export async function setEnvProxyEntry(envName, provider, entry, options = {}) {
|
|
532
|
+
await writeEnv(envName, (previous) => {
|
|
533
|
+
const currentProxy = normalizeEnvProxyConfig(previous?.proxy) ?? {};
|
|
534
|
+
const nextEntry = normalizeEnvProxyProviderConfig(entry);
|
|
535
|
+
const nextProxy = { ...currentProxy };
|
|
536
|
+
if (nextEntry && 'host' in nextEntry) {
|
|
537
|
+
const host = normalizeOptionalString(nextEntry.host);
|
|
538
|
+
if (host) {
|
|
539
|
+
nextProxy.host = host;
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
delete nextProxy.host;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (nextEntry && 'port' in nextEntry) {
|
|
546
|
+
const portValue = nextEntry.port;
|
|
547
|
+
const port = typeof portValue === 'number' && Number.isInteger(portValue) && portValue >= 1 && portValue <= 65535
|
|
548
|
+
? portValue
|
|
549
|
+
: undefined;
|
|
550
|
+
if (port !== undefined) {
|
|
551
|
+
nextProxy.port = port;
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
delete nextProxy.port;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const providerConfig = nextEntry && Object.keys(nextEntry).some((key) => key !== 'host' && key !== 'port')
|
|
558
|
+
? Object.fromEntries(Object.entries(nextEntry).filter(([key]) => key !== 'host' && key !== 'port'))
|
|
559
|
+
: undefined;
|
|
560
|
+
if (provider === 'nginx') {
|
|
561
|
+
if (providerConfig && Object.keys(providerConfig).length > 0) {
|
|
562
|
+
nextProxy.nginx = providerConfig;
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
delete nextProxy.nginx;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else if (providerConfig && Object.keys(providerConfig).length > 0) {
|
|
569
|
+
nextProxy.caddy = providerConfig;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
delete nextProxy.caddy;
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
...previous,
|
|
576
|
+
proxy: nextProxy,
|
|
577
|
+
};
|
|
578
|
+
}, options);
|
|
579
|
+
}
|
|
519
580
|
export async function clearEnvRootSetup(envName, options = {}) {
|
|
520
581
|
const config = await loadExactAuthConfig(options);
|
|
521
582
|
const current = config.envs[envName];
|
package/dist/lib/env-config.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
+
import { normalizeEnvProxyConfig } from './env-proxy-config.js';
|
|
9
10
|
import { resolveAppPublicPath } from './app-public-path.js';
|
|
10
11
|
const STRING_ENV_CONFIG_KEYS = [
|
|
11
12
|
'source',
|
|
@@ -110,5 +111,9 @@ export function buildStoredEnvConfig(input) {
|
|
|
110
111
|
if ((authType === 'basic' || authType === 'token') && accessToken) {
|
|
111
112
|
envConfig.accessToken = accessToken;
|
|
112
113
|
}
|
|
114
|
+
const proxy = normalizeEnvProxyConfig(input.proxy);
|
|
115
|
+
if (proxy) {
|
|
116
|
+
envConfig.proxy = proxy;
|
|
117
|
+
}
|
|
113
118
|
return envConfig;
|
|
114
119
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
function normalizeOptionalString(value) {
|
|
10
|
+
const normalized = String(value ?? '').trim();
|
|
11
|
+
return normalized || undefined;
|
|
12
|
+
}
|
|
13
|
+
function normalizeOptionalPort(value) {
|
|
14
|
+
const normalized = normalizeOptionalString(value);
|
|
15
|
+
if (!normalized || !/^\d+$/.test(normalized)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const port = Number(normalized);
|
|
19
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return port;
|
|
23
|
+
}
|
|
24
|
+
export function normalizeEnvProxyProviderConfig(value) {
|
|
25
|
+
if (!value || typeof value !== 'object') {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return { ...value };
|
|
29
|
+
}
|
|
30
|
+
export function normalizeEnvProxyConfig(value) {
|
|
31
|
+
if (!value || typeof value !== 'object') {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const proxy = value;
|
|
35
|
+
const host = normalizeOptionalString(proxy.host);
|
|
36
|
+
const port = normalizeOptionalPort(proxy.port);
|
|
37
|
+
const nginx = normalizeEnvProxyProviderConfig(proxy.nginx);
|
|
38
|
+
const caddy = normalizeEnvProxyProviderConfig(proxy.caddy);
|
|
39
|
+
if (!host && port === undefined && !nginx && !caddy) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
...(host ? { host } : {}),
|
|
44
|
+
...(port !== undefined ? { port } : {}),
|
|
45
|
+
...(nginx ? { nginx } : {}),
|
|
46
|
+
...(caddy ? { caddy } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
package/dist/lib/env-proxy.js
CHANGED
|
@@ -487,6 +487,7 @@ function rewriteHtmlAssetPublicPath(html, currentPublicPath, nextPublicPath) {
|
|
|
487
487
|
}
|
|
488
488
|
function buildNginxManagedConfigBlock(context) {
|
|
489
489
|
const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
|
|
490
|
+
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
490
491
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
491
492
|
const isRootMounted = context.appPublicPath === '/';
|
|
492
493
|
const appPublicPathRedirectBlock = isRootMounted
|
|
@@ -525,6 +526,10 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
525
526
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
526
527
|
' }',
|
|
527
528
|
'',
|
|
529
|
+
` location = ${apiBasePathNoTrailingSlash} {`,
|
|
530
|
+
` return 308 ${context.apiBasePath}$is_args$args;`,
|
|
531
|
+
' }',
|
|
532
|
+
'',
|
|
528
533
|
` location ^~ ${context.apiBasePath} {`,
|
|
529
534
|
` proxy_pass ${context.backendUrl};`,
|
|
530
535
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
@@ -989,6 +994,7 @@ function buildNginxOtherLocation(appPublicPath, v2PublicPath, modernClientPrefix
|
|
|
989
994
|
function renderNginxLocationTemplate(context) {
|
|
990
995
|
const proxyPassBlock = buildNginxProxyPassBlock(context.proxyHost, context.apiPort);
|
|
991
996
|
const wsProxyPassTarget = `http://${context.proxyHost}:${context.apiPort}${context.wsPath}`;
|
|
997
|
+
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
992
998
|
return ` location ~* ^${context.appPublicPath}storage/uploads/(.*\\.md)$ {
|
|
993
999
|
alias ${context.uploadsPath}/$1;
|
|
994
1000
|
default_type text/markdown;
|
|
@@ -1034,6 +1040,10 @@ function renderNginxLocationTemplate(context) {
|
|
|
1034
1040
|
${proxyPassBlock}
|
|
1035
1041
|
}${context.otherLocation}
|
|
1036
1042
|
|
|
1043
|
+
location = ${apiBasePathNoTrailingSlash} {
|
|
1044
|
+
return 308 ${context.apiBasePath}$is_args$args;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1037
1047
|
location ^~ ${context.apiBasePath} {
|
|
1038
1048
|
${proxyPassBlock}
|
|
1039
1049
|
}
|
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
15
|
import { applyEnvProxyAppEntryOptions, buildManualEnvProxyCaddyBundle, buildEnvProxyCaddyBundle, buildEnvProxyMainConfig, mapProxyPathFromCliRoot, resolveEnvProxyMainOutputPath, } from './env-proxy.js';
|
|
15
|
-
import {
|
|
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');
|
|
@@ -171,11 +174,16 @@ async function reloadLocalCaddyProxy(runtimeContext) {
|
|
|
171
174
|
}
|
|
172
175
|
async function ensureDockerCaddyProxyContainer(runtimeContext) {
|
|
173
176
|
const containerName = await resolveCaddyProxyContainerName();
|
|
177
|
+
const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
|
|
178
|
+
const publishedPorts = await resolveDockerCaddyPublishedPorts();
|
|
174
179
|
if (await dockerContainerExists(containerName)) {
|
|
175
|
-
|
|
180
|
+
if (await dockerCaddyProxyContainerMatchesPublishedPorts(containerName, publishedPorts)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
await removeDockerCaddyProxyContainer(containerName);
|
|
176
184
|
}
|
|
177
185
|
const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
|
|
178
|
-
const
|
|
186
|
+
const dockerPortArgs = publishedPorts.flatMap((port) => ['-p', `${port}:${port}`]);
|
|
179
187
|
await run('docker', [
|
|
180
188
|
'run',
|
|
181
189
|
'-d',
|
|
@@ -183,8 +191,7 @@ async function ensureDockerCaddyProxyContainer(runtimeContext) {
|
|
|
183
191
|
containerName,
|
|
184
192
|
'--add-host',
|
|
185
193
|
'host.docker.internal:host-gateway',
|
|
186
|
-
|
|
187
|
-
'80:80',
|
|
194
|
+
...dockerPortArgs,
|
|
188
195
|
'-v',
|
|
189
196
|
`${hostCliRoot}:${DOCKER_CADDY_PROXY_RUNTIME_ROOT}`,
|
|
190
197
|
'-v',
|
|
@@ -195,14 +202,53 @@ async function ensureDockerCaddyProxyContainer(runtimeContext) {
|
|
|
195
202
|
stdio: 'ignore',
|
|
196
203
|
});
|
|
197
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
|
+
}
|
|
198
243
|
async function startDockerCaddyProxy(runtimeContext) {
|
|
199
244
|
const containerName = await resolveCaddyProxyContainerName();
|
|
200
245
|
await ensureCaddyProxyMainConfig(runtimeContext);
|
|
201
|
-
|
|
246
|
+
const existedBeforeEnsure = await dockerContainerExists(containerName);
|
|
247
|
+
await ensureDockerCaddyProxyContainer(runtimeContext);
|
|
248
|
+
if (existedBeforeEnsure && (await dockerContainerExists(containerName))) {
|
|
202
249
|
const state = await startDockerContainer(containerName, { stdio: 'ignore' });
|
|
203
250
|
return state === 'already-running' ? 'already-running' : 'started';
|
|
204
251
|
}
|
|
205
|
-
await ensureDockerCaddyProxyContainer(runtimeContext);
|
|
206
252
|
return 'started';
|
|
207
253
|
}
|
|
208
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
15
|
import { applyEnvProxyAppEntryOptions, appConfigHasManagedNginxBlock, buildManualEnvProxyNginxBundle, buildEnvProxyMainConfig, buildEnvProxyNginxBundle, extractManagedNginxConfigBlock, installEnvProxyProvider, mapProxyPathFromCliRoot, reloadEnvProxyProvider, resolveEnvProxyMainOutputPath, replaceManagedNginxConfigBlock, syncEnvProxyNginxSnippets, } from './env-proxy.js';
|
|
15
|
-
import {
|
|
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');
|
|
@@ -231,11 +234,16 @@ async function reloadLocalNginxProxy(runtimeContext) {
|
|
|
231
234
|
}
|
|
232
235
|
async function ensureDockerNginxProxyContainer(runtimeContext) {
|
|
233
236
|
const containerName = await resolveNginxProxyContainerName();
|
|
237
|
+
const mainConfigPath = await ensureNginxProxyMainConfig(runtimeContext);
|
|
238
|
+
const publishedPorts = await resolveDockerNginxPublishedPorts();
|
|
234
239
|
if (await dockerContainerExists(containerName)) {
|
|
235
|
-
|
|
240
|
+
if (await dockerNginxProxyContainerMatchesPublishedPorts(containerName, publishedPorts)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
await removeDockerNginxProxyContainer(containerName);
|
|
236
244
|
}
|
|
237
245
|
const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
|
|
238
|
-
const
|
|
246
|
+
const dockerPortArgs = publishedPorts.flatMap((port) => ['-p', `${port}:${port}`]);
|
|
239
247
|
await run('docker', [
|
|
240
248
|
'run',
|
|
241
249
|
'-d',
|
|
@@ -243,8 +251,7 @@ async function ensureDockerNginxProxyContainer(runtimeContext) {
|
|
|
243
251
|
containerName,
|
|
244
252
|
'--add-host',
|
|
245
253
|
'host.docker.internal:host-gateway',
|
|
246
|
-
|
|
247
|
-
'80:80',
|
|
254
|
+
...dockerPortArgs,
|
|
248
255
|
'-v',
|
|
249
256
|
`${hostCliRoot}:${DOCKER_NGINX_PROXY_RUNTIME_ROOT}`,
|
|
250
257
|
'-v',
|
|
@@ -255,14 +262,53 @@ async function ensureDockerNginxProxyContainer(runtimeContext) {
|
|
|
255
262
|
stdio: 'ignore',
|
|
256
263
|
});
|
|
257
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
|
+
}
|
|
258
303
|
async function startDockerNginxProxy(runtimeContext) {
|
|
259
304
|
const containerName = await resolveNginxProxyContainerName();
|
|
260
305
|
await ensureNginxProxyMainConfig(runtimeContext);
|
|
261
|
-
|
|
306
|
+
const existedBeforeEnsure = await dockerContainerExists(containerName);
|
|
307
|
+
await ensureDockerNginxProxyContainer(runtimeContext);
|
|
308
|
+
if (existedBeforeEnsure && (await dockerContainerExists(containerName))) {
|
|
262
309
|
const state = await startDockerContainer(containerName, { stdio: 'ignore' });
|
|
263
310
|
return state === 'already-running' ? 'already-running' : 'started';
|
|
264
311
|
}
|
|
265
|
-
await ensureDockerNginxProxyContainer(runtimeContext);
|
|
266
312
|
return 'started';
|
|
267
313
|
}
|
|
268
314
|
async function stopDockerNginxProxy() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "2.2.0-alpha.
|
|
3
|
+
"version": "2.2.0-alpha.6",
|
|
4
4
|
"description": "NocoBase Command Line Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/generated/command-registry.js",
|
|
@@ -144,5 +144,5 @@
|
|
|
144
144
|
"type": "git",
|
|
145
145
|
"url": "git+https://github.com/nocobase/nocobase.git"
|
|
146
146
|
},
|
|
147
|
-
"gitHead": "
|
|
147
|
+
"gitHead": "f55b252811f6af01d08cd3814171867b70c8ef7a"
|
|
148
148
|
}
|