@nocobase/cli 2.2.0-beta.8 → 2.2.0-test.15
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 +24 -0
- package/dist/commands/app/start.js +25 -2
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/init.js +131 -4
- package/dist/commands/install.js +179 -131
- package/dist/commands/portal/create.js +105 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +104 -0
- package/dist/commands/portal/dev.js +71 -0
- package/dist/commands/portal/index.js +20 -0
- package/dist/commands/portal/info.js +82 -0
- package/dist/commands/portal/list.js +98 -0
- package/dist/commands/portal/pull.js +77 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/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/dev.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 +71 -1
- package/dist/lib/cli-config.js +74 -2
- package/dist/lib/docker-image.js +94 -6
- package/dist/lib/env-config.js +8 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +266 -61
- package/dist/lib/managed-init-env.js +6 -1
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-create.js +488 -0
- package/dist/lib/portal-deploy.js +275 -0
- package/dist/lib/portal-destroy.js +100 -0
- package/dist/lib/portal-dev.js +79 -0
- package/dist/lib/portal-env-files.js +53 -0
- package/dist/lib/portal-info.js +31 -0
- package/dist/lib/portal-list.js +197 -0
- package/dist/lib/portal-source.js +416 -0
- package/dist/lib/portal-template.js +190 -0
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-web-ui.js +13 -2
- package/dist/lib/proxy-caddy.js +77 -9
- package/dist/lib/proxy-nginx.js +71 -11
- package/dist/lib/run-npm.js +17 -16
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +192 -38
- package/dist/locale/zh-CN.json +192 -38
- package/package.json +6 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { getCurrentEnvName, getEnv } from '../../lib/auth-store.js';
|
|
11
|
+
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
14
|
+
import { pushPortalSource } from '../../lib/portal-source.js';
|
|
15
|
+
import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
16
|
+
const portalPushText = (key, values, fallback) => translateCli(`commands.portalPush.${key}`, values, { fallback });
|
|
17
|
+
export default class PortalPush extends Command {
|
|
18
|
+
static summary = 'Push local Portal source changes to source storage';
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer --env prod --yes',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --message "Update customer portal"',
|
|
23
|
+
];
|
|
24
|
+
static args = {
|
|
25
|
+
portal: Args.string({
|
|
26
|
+
required: true,
|
|
27
|
+
description: 'Portal name/slug',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
static flags = {
|
|
31
|
+
env: Flags.string({
|
|
32
|
+
char: 'e',
|
|
33
|
+
description: 'CLI env name; omitted uses the current env',
|
|
34
|
+
}),
|
|
35
|
+
yes: Flags.boolean({
|
|
36
|
+
char: 'y',
|
|
37
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
38
|
+
default: false,
|
|
39
|
+
}),
|
|
40
|
+
message: Flags.string({
|
|
41
|
+
char: 'm',
|
|
42
|
+
description: 'Source update message; used as the Git commit message for Git-managed source',
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
const { args, flags } = await this.parse(PortalPush);
|
|
47
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
48
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
49
|
+
command: this,
|
|
50
|
+
requestedEnv,
|
|
51
|
+
yes: flags.yes,
|
|
52
|
+
});
|
|
53
|
+
if (!confirmed) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const scope = resolveDefaultConfigScope();
|
|
57
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
58
|
+
const env = await getEnv(envName, { scope });
|
|
59
|
+
if (!env) {
|
|
60
|
+
this.error(portalPushText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
61
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
62
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
63
|
+
}
|
|
64
|
+
const result = await pushPortalSource({
|
|
65
|
+
portal: args.portal,
|
|
66
|
+
env,
|
|
67
|
+
envName,
|
|
68
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
69
|
+
message: flags.message,
|
|
70
|
+
});
|
|
71
|
+
if (!result.changed) {
|
|
72
|
+
printInfo(result.noopReason ?? portalPushText('messages.noop', undefined, 'No push is needed.'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
printSuccess(portalPushText('messages.pushed', { portal: result.portal, sourceRevision: result.sourceRevision ?? '' }, result.sourceRevision
|
|
76
|
+
? `Pushed Portal source "${result.portal}" (${result.sourceRevision}).`
|
|
77
|
+
: `Pushed Portal source "${result.portal}".`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -8,20 +8,51 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
|
|
11
|
-
import {
|
|
11
|
+
import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
|
|
12
|
+
import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
|
|
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';
|
|
14
16
|
export default class ProxyCaddyGenerate extends Command {
|
|
15
17
|
static summary = 'Generate caddy proxy files for one managed env';
|
|
16
18
|
static examples = [
|
|
19
|
+
'<%= config.bin %> proxy caddy generate --host app1.example.com',
|
|
17
20
|
'<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com',
|
|
18
21
|
'<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com --port 8080',
|
|
22
|
+
'<%= config.bin %> proxy caddy generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
|
|
19
23
|
];
|
|
20
24
|
static flags = {
|
|
21
25
|
env: Flags.string({
|
|
22
26
|
char: 'e',
|
|
23
|
-
description: 'CLI env name to generate proxy files for',
|
|
24
|
-
|
|
27
|
+
description: 'CLI env name to generate proxy files for. Defaults to the current env when omitted',
|
|
28
|
+
}),
|
|
29
|
+
manual: Flags.boolean({
|
|
30
|
+
description: 'Generate proxy files from explicit runtime flags instead of a saved env',
|
|
31
|
+
default: false,
|
|
32
|
+
}),
|
|
33
|
+
name: Flags.string({
|
|
34
|
+
description: 'Output bundle name used under .nocobase/proxy/caddy in manual mode',
|
|
35
|
+
}),
|
|
36
|
+
'storage-path': Flags.string({
|
|
37
|
+
description: 'Path to the NocoBase storage directory in manual mode',
|
|
38
|
+
}),
|
|
39
|
+
'dist-root-path': Flags.string({
|
|
40
|
+
description: 'Path to the dist-client root directory used to generate index-v1.html and index-v2.html in manual mode',
|
|
41
|
+
}),
|
|
42
|
+
'runtime-version': Flags.string({
|
|
43
|
+
description: 'Frontend runtime version under dist-root-path in manual mode',
|
|
44
|
+
}),
|
|
45
|
+
'app-public-path': Flags.string({
|
|
46
|
+
description: 'Public base path served by the proxied app in manual mode. Defaults to /',
|
|
47
|
+
}),
|
|
48
|
+
'upstream-host': Flags.string({
|
|
49
|
+
description: 'Upstream host used by caddy reverse_proxy in manual mode',
|
|
50
|
+
}),
|
|
51
|
+
'upstream-port': Flags.string({
|
|
52
|
+
description: 'Upstream port used by caddy reverse_proxy in manual mode',
|
|
53
|
+
}),
|
|
54
|
+
'cdn-base-url': Flags.string({
|
|
55
|
+
description: 'Client asset CDN base URL used when generating runtime HTML',
|
|
25
56
|
}),
|
|
26
57
|
host: Flags.string({
|
|
27
58
|
description: 'Host exposed by the caddy site block, such as example.com or localhost',
|
|
@@ -35,9 +66,56 @@ export default class ProxyCaddyGenerate extends Command {
|
|
|
35
66
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
36
67
|
const requestedPort = flags.port?.trim() || undefined;
|
|
37
68
|
const normalizedPort = normalizeProxyListenPort(requestedPort);
|
|
69
|
+
const manual = Boolean(flags.manual);
|
|
38
70
|
if (requestedPort && !normalizedPort) {
|
|
39
71
|
this.error(`Invalid proxy entry port "${requestedPort}". Use an integer between 1 and 65535.`);
|
|
40
72
|
}
|
|
73
|
+
if (manual && requestedEnv) {
|
|
74
|
+
this.error('`--manual` cannot be combined with `--env`.');
|
|
75
|
+
}
|
|
76
|
+
if (manual) {
|
|
77
|
+
const name = flags.name?.trim() || undefined;
|
|
78
|
+
const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
|
|
79
|
+
const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
|
|
80
|
+
const storagePath = flags['storage-path']?.trim() || undefined;
|
|
81
|
+
const distRootPath = flags['dist-root-path']?.trim() || undefined;
|
|
82
|
+
const runtimeVersion = flags['runtime-version']?.trim() || undefined;
|
|
83
|
+
if (requestedUpstreamPort && !upstreamPort) {
|
|
84
|
+
this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
|
|
85
|
+
}
|
|
86
|
+
if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
|
|
87
|
+
this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
|
|
88
|
+
}
|
|
89
|
+
const driver = await getCaddyProxyDriver();
|
|
90
|
+
const runtimeContext = await resolveCaddyProxyRuntimeContext();
|
|
91
|
+
announceTargetEnv(name);
|
|
92
|
+
startTask(`Generating caddy proxy config for env "${name}" with the ${driver} driver...`);
|
|
93
|
+
try {
|
|
94
|
+
const { bundle, status } = await writeManualCaddyProxyBundle({
|
|
95
|
+
name,
|
|
96
|
+
storagePath,
|
|
97
|
+
distRootPath,
|
|
98
|
+
runtimeVersion,
|
|
99
|
+
appPublicPath: flags['app-public-path']?.trim() || undefined,
|
|
100
|
+
upstreamHost: flags['upstream-host']?.trim() || undefined,
|
|
101
|
+
upstreamPort,
|
|
102
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
103
|
+
}, {
|
|
104
|
+
host: flags.host?.trim() || undefined,
|
|
105
|
+
port: normalizedPort,
|
|
106
|
+
}, runtimeContext, {
|
|
107
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
108
|
+
});
|
|
109
|
+
succeedTask(status === 'created'
|
|
110
|
+
? `Saved caddy proxy files for env "${name}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
|
|
111
|
+
: `Saved caddy proxy files for env "${name}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
failTask(`Failed to generate caddy proxy config for env "${name}".`);
|
|
115
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
41
119
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
42
120
|
if (!runtime) {
|
|
43
121
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
@@ -53,10 +131,18 @@ export default class ProxyCaddyGenerate extends Command {
|
|
|
53
131
|
announceTargetEnv(runtime.envName);
|
|
54
132
|
startTask(`Generating caddy proxy config for env "${runtime.envName}" with the ${driver} driver...`);
|
|
55
133
|
try {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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, {
|
|
140
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
141
|
+
});
|
|
142
|
+
await setEnvProxyEntry(runtime.envName, 'caddy', {
|
|
143
|
+
host: appEntryOptions.host,
|
|
144
|
+
port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
|
|
145
|
+
}, { scope: resolveDefaultConfigScope() });
|
|
60
146
|
succeedTask(status === 'created'
|
|
61
147
|
? `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
|
|
62
148
|
: `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
|
|
@@ -8,19 +8,54 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
|
|
11
|
-
import {
|
|
11
|
+
import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
|
|
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';
|
|
15
17
|
static examples = [
|
|
18
|
+
'<%= config.bin %> proxy nginx generate --host app1.example.com',
|
|
16
19
|
'<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com',
|
|
17
20
|
'<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com --port 8080',
|
|
21
|
+
'<%= config.bin %> proxy nginx generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
|
|
18
22
|
];
|
|
19
23
|
static flags = {
|
|
20
24
|
env: Flags.string({
|
|
21
25
|
char: 'e',
|
|
22
|
-
description: 'CLI env name to generate proxy files for',
|
|
23
|
-
|
|
26
|
+
description: 'CLI env name to generate proxy files for. Defaults to the current env when omitted',
|
|
27
|
+
}),
|
|
28
|
+
manual: Flags.boolean({
|
|
29
|
+
description: 'Generate proxy files from explicit runtime flags instead of a saved env',
|
|
30
|
+
default: false,
|
|
31
|
+
}),
|
|
32
|
+
name: Flags.string({
|
|
33
|
+
description: 'Output bundle name used under .nocobase/proxy/nginx in manual mode',
|
|
34
|
+
}),
|
|
35
|
+
'storage-path': Flags.string({
|
|
36
|
+
description: 'Path to the NocoBase storage directory in manual mode',
|
|
37
|
+
}),
|
|
38
|
+
'dist-root-path': Flags.string({
|
|
39
|
+
description: 'Path to the dist-client root directory used to generate index-v1.html and index-v2.html in manual mode',
|
|
40
|
+
}),
|
|
41
|
+
'runtime-version': Flags.string({
|
|
42
|
+
description: 'Frontend runtime version under dist-root-path in manual mode',
|
|
43
|
+
}),
|
|
44
|
+
'app-public-path': Flags.string({
|
|
45
|
+
description: 'Public base path served by the proxied app in manual mode. Defaults to /',
|
|
46
|
+
}),
|
|
47
|
+
'upstream-host': Flags.string({
|
|
48
|
+
description: 'Upstream host used by nginx proxy_pass in manual mode',
|
|
49
|
+
}),
|
|
50
|
+
'upstream-port': Flags.string({
|
|
51
|
+
description: 'Upstream port used by nginx proxy_pass in manual mode',
|
|
52
|
+
}),
|
|
53
|
+
'cdn-base-url': Flags.string({
|
|
54
|
+
description: 'Client asset CDN base URL used when generating runtime HTML',
|
|
55
|
+
}),
|
|
56
|
+
force: Flags.boolean({
|
|
57
|
+
description: 'Overwrite existing app.conf even when the managed block is missing',
|
|
58
|
+
default: false,
|
|
24
59
|
}),
|
|
25
60
|
host: Flags.string({
|
|
26
61
|
description: 'Host exposed by the nginx entry config, such as example.com or localhost',
|
|
@@ -34,9 +69,56 @@ export default class ProxyNginxGenerate extends Command {
|
|
|
34
69
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
35
70
|
const requestedPort = flags.port?.trim() || undefined;
|
|
36
71
|
const normalizedPort = normalizeProxyListenPort(requestedPort);
|
|
72
|
+
const manual = Boolean(flags.manual);
|
|
37
73
|
if (requestedPort && !normalizedPort) {
|
|
38
74
|
this.error(`Invalid proxy entry port "${requestedPort}". Use an integer between 1 and 65535.`);
|
|
39
75
|
}
|
|
76
|
+
if (manual && requestedEnv) {
|
|
77
|
+
this.error('`--manual` cannot be combined with `--env`.');
|
|
78
|
+
}
|
|
79
|
+
if (manual) {
|
|
80
|
+
const name = flags.name?.trim() || undefined;
|
|
81
|
+
const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
|
|
82
|
+
const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
|
|
83
|
+
const storagePath = flags['storage-path']?.trim() || undefined;
|
|
84
|
+
const distRootPath = flags['dist-root-path']?.trim() || undefined;
|
|
85
|
+
const runtimeVersion = flags['runtime-version']?.trim() || undefined;
|
|
86
|
+
if (requestedUpstreamPort && !upstreamPort) {
|
|
87
|
+
this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
|
|
88
|
+
}
|
|
89
|
+
if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
|
|
90
|
+
this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
|
|
91
|
+
}
|
|
92
|
+
const driver = await getNginxProxyDriver();
|
|
93
|
+
const runtimeContext = await resolveNginxProxyRuntimeContext();
|
|
94
|
+
announceTargetEnv(name);
|
|
95
|
+
startTask(`Generating nginx proxy config for env "${name}" with the ${driver} driver...`);
|
|
96
|
+
try {
|
|
97
|
+
const { bundle, status } = await writeManualNginxProxyBundle({
|
|
98
|
+
name,
|
|
99
|
+
storagePath,
|
|
100
|
+
distRootPath,
|
|
101
|
+
runtimeVersion,
|
|
102
|
+
appPublicPath: flags['app-public-path']?.trim() || undefined,
|
|
103
|
+
upstreamHost: flags['upstream-host']?.trim() || undefined,
|
|
104
|
+
upstreamPort,
|
|
105
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
106
|
+
}, {
|
|
107
|
+
host: flags.host?.trim() || undefined,
|
|
108
|
+
port: normalizedPort,
|
|
109
|
+
}, runtimeContext, {
|
|
110
|
+
force: flags.force,
|
|
111
|
+
});
|
|
112
|
+
succeedTask(status === 'created'
|
|
113
|
+
? `Saved nginx proxy files for env "${name}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
|
|
114
|
+
: `Saved nginx proxy files for env "${name}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
failTask(`Failed to generate nginx proxy config for env "${name}".`);
|
|
118
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
40
122
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
41
123
|
if (!runtime) {
|
|
42
124
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
@@ -52,10 +134,19 @@ export default class ProxyNginxGenerate extends Command {
|
|
|
52
134
|
announceTargetEnv(runtime.envName);
|
|
53
135
|
startTask(`Generating nginx proxy config for env "${runtime.envName}" with the ${driver} driver...`);
|
|
54
136
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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, {
|
|
143
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
144
|
+
force: flags.force,
|
|
145
|
+
});
|
|
146
|
+
await setEnvProxyEntry(runtime.envName, 'nginx', {
|
|
147
|
+
host: appEntryOptions.host,
|
|
148
|
+
port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
|
|
149
|
+
}, { scope: resolveDefaultConfigScope() });
|
|
59
150
|
succeedTask(status === 'created'
|
|
60
151
|
? `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
|
|
61
152
|
: `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
|
|
@@ -140,7 +140,7 @@ export default class SourceDev extends Command {
|
|
|
140
140
|
: `Run \`nb app stop --env ${runtime.envName}\` before starting dev mode, or choose another dev port with --port.`,
|
|
141
141
|
].join('\n'));
|
|
142
142
|
}
|
|
143
|
-
const npmArgs = ['dev', '--rsbuild'];
|
|
143
|
+
const npmArgs = ['dev', '--rsbuild', '--quickstart'];
|
|
144
144
|
if (flags['db-sync']) {
|
|
145
145
|
npmArgs.push('--db-sync');
|
|
146
146
|
}
|
|
@@ -12,9 +12,10 @@ import path from 'node:path';
|
|
|
12
12
|
import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
13
13
|
import { runPromptCatalog, } from "../../lib/prompt-catalog.js";
|
|
14
14
|
import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, resolveCliLocale, translateCli, } from "../../lib/cli-locale.js";
|
|
15
|
-
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
15
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveOfficialDockerRegistry, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
16
16
|
import { getEnv } from '../../lib/auth-store.js';
|
|
17
17
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
18
|
+
import { getCliConfigValue } from '../../lib/cli-config.js';
|
|
18
19
|
import { buildBeforeDependencyInstallHookContext, runBeforeDependencyInstallHook, } from '../../lib/hook-script.js';
|
|
19
20
|
import { run } from "../../lib/run-npm.js";
|
|
20
21
|
import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
|
|
@@ -344,8 +345,8 @@ export default class SourceDownload extends Command {
|
|
|
344
345
|
label: downloadText('prompts.dockerPlatform.autoLabel'),
|
|
345
346
|
hint: downloadText('prompts.dockerPlatform.autoHint'),
|
|
346
347
|
},
|
|
347
|
-
{ value: 'linux/amd64', label: '
|
|
348
|
-
{ value: 'linux/arm64', label: '
|
|
348
|
+
{ value: 'linux/amd64', label: 'amd64' },
|
|
349
|
+
{ value: 'linux/arm64', label: 'arm64' },
|
|
349
350
|
],
|
|
350
351
|
initialValue: DEFAULT_DOCKER_PLATFORM,
|
|
351
352
|
yesInitialValue: DEFAULT_DOCKER_PLATFORM,
|
|
@@ -450,9 +451,9 @@ export default class SourceDownload extends Command {
|
|
|
450
451
|
}
|
|
451
452
|
return outputAbs;
|
|
452
453
|
}
|
|
453
|
-
dockerTarPath(flags, outputAbs) {
|
|
454
|
+
async dockerTarPath(flags, outputAbs) {
|
|
454
455
|
const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
|
|
455
|
-
defaultRegistry:
|
|
456
|
+
defaultRegistry: await this.resolveConfiguredDockerRegistryDefault(),
|
|
456
457
|
defaultVersion: 'latest',
|
|
457
458
|
});
|
|
458
459
|
const safeBase = imageRef.replace(/[\\/:]/g, '-');
|
|
@@ -462,9 +463,11 @@ export default class SourceDownload extends Command {
|
|
|
462
463
|
* Defaults for prompts only. Keys present in **`preset`** are omitted so `runPromptCatalog` uses
|
|
463
464
|
* **`values`** (preset) alone for those steps — no duplicate prefill for skipped prompts.
|
|
464
465
|
*/
|
|
465
|
-
|
|
466
|
+
async resolveConfiguredDockerRegistryDefault() {
|
|
467
|
+
return resolveOfficialDockerRegistry(await getCliConfigValue('nb-image-registry'));
|
|
468
|
+
}
|
|
469
|
+
buildInitialValuesFromParsed(flags, preset, defaultDockerRegistry) {
|
|
466
470
|
const initialValues = {};
|
|
467
|
-
const localeDefaultDockerRegistry = defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE);
|
|
468
471
|
const source = flags.source?.trim();
|
|
469
472
|
if (source) {
|
|
470
473
|
initialValues.source = source;
|
|
@@ -488,7 +491,7 @@ export default class SourceDownload extends Command {
|
|
|
488
491
|
initialValues.dockerRegistry = String(flags['docker-registry'] ?? '').trim();
|
|
489
492
|
}
|
|
490
493
|
else {
|
|
491
|
-
initialValues.dockerRegistry =
|
|
494
|
+
initialValues.dockerRegistry = defaultDockerRegistry;
|
|
492
495
|
}
|
|
493
496
|
initialValues.dockerPlatform = normalizeDockerPlatform(flags['docker-platform']);
|
|
494
497
|
initialValues.dockerSave = flags['docker-save'];
|
|
@@ -569,7 +572,7 @@ export default class SourceDownload extends Command {
|
|
|
569
572
|
}
|
|
570
573
|
return flags.build;
|
|
571
574
|
}
|
|
572
|
-
mapCatalogResultsToResolved(results, flags) {
|
|
575
|
+
mapCatalogResultsToResolved(results, flags, defaultDockerRegistry) {
|
|
573
576
|
const source = String(results.source);
|
|
574
577
|
const version = resolveVersionFromResults(results, flags.version) || 'latest';
|
|
575
578
|
const devDependencies = source === 'npm' ? Boolean(results.devDependencies) : undefined;
|
|
@@ -583,7 +586,7 @@ export default class SourceDownload extends Command {
|
|
|
583
586
|
const dockerRegistry = source === 'docker'
|
|
584
587
|
? results.dockerRegistry !== undefined
|
|
585
588
|
? String(results.dockerRegistry).trim() || undefined
|
|
586
|
-
: flags['docker-registry']?.trim() ||
|
|
589
|
+
: flags['docker-registry']?.trim() || defaultDockerRegistry
|
|
587
590
|
: undefined;
|
|
588
591
|
const dockerPlatform = source === 'docker'
|
|
589
592
|
? normalizeDockerPlatform(results.dockerPlatform !== undefined ? results.dockerPlatform : flags['docker-platform'])
|
|
@@ -628,7 +631,8 @@ export default class SourceDownload extends Command {
|
|
|
628
631
|
this.error('Download source is required in non-interactive mode. Use --source npm, --source git, or --source docker.');
|
|
629
632
|
}
|
|
630
633
|
const presetValues = this.buildPresetValuesFromFlags(flags);
|
|
631
|
-
const
|
|
634
|
+
const defaultDockerRegistry = await this.resolveConfiguredDockerRegistryDefault();
|
|
635
|
+
const initialValues = this.buildInitialValuesFromParsed(flags, presetValues, defaultDockerRegistry);
|
|
632
636
|
const results = await runPromptCatalog(SourceDownload.prompts, {
|
|
633
637
|
initialValues,
|
|
634
638
|
values: presetValues,
|
|
@@ -654,7 +658,7 @@ export default class SourceDownload extends Command {
|
|
|
654
658
|
if (flags['docker-save'] && source !== 'docker') {
|
|
655
659
|
this.error('--docker-save is only available when --source docker is selected.');
|
|
656
660
|
}
|
|
657
|
-
return this.mapCatalogResultsToResolved(results, flags);
|
|
661
|
+
return this.mapCatalogResultsToResolved(results, flags, defaultDockerRegistry);
|
|
658
662
|
}
|
|
659
663
|
npmRegistryUrl(flags) {
|
|
660
664
|
const url = flags['npm-registry']?.trim();
|
|
@@ -784,7 +788,7 @@ export default class SourceDownload extends Command {
|
|
|
784
788
|
}
|
|
785
789
|
async downloadFromDocker(flags) {
|
|
786
790
|
const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
|
|
787
|
-
defaultRegistry:
|
|
791
|
+
defaultRegistry: await this.resolveConfiguredDockerRegistryDefault(),
|
|
788
792
|
defaultVersion: 'latest',
|
|
789
793
|
});
|
|
790
794
|
const platform = dockerPlatformArg(flags['docker-platform']);
|
|
@@ -811,7 +815,7 @@ export default class SourceDownload extends Command {
|
|
|
811
815
|
await fsp.rm(outAbs, { recursive: true, force: true });
|
|
812
816
|
}
|
|
813
817
|
await fsp.mkdir(outAbs, { recursive: true });
|
|
814
|
-
const tarPath = this.dockerTarPath(flags, outAbs);
|
|
818
|
+
const tarPath = await this.dockerTarPath(flags, outAbs);
|
|
815
819
|
this.log(`Saving Docker image tarball to ${tarPath}`);
|
|
816
820
|
await this.runExternalCommand('docker', ['save', '-o', tarPath, imageRef], {
|
|
817
821
|
errorName: 'docker save',
|
|
@@ -14,7 +14,7 @@ import { dockerContainerExists, managedAppLifecycleEnvVars, runLocalNocoBaseComm
|
|
|
14
14
|
import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin-db.js';
|
|
15
15
|
import { resolveConfiguredStoragePath } from './env-paths.js';
|
|
16
16
|
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
17
|
-
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
|
|
17
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageContainerPort, resolveDockerImageRef, } from "./docker-image.js";
|
|
18
18
|
import { resolveHookScriptPath } from './hook-script.js';
|
|
19
19
|
import { commandSucceeds, ensureDockerDaemonRunning, run } from './run-npm.js';
|
|
20
20
|
import Install from '../commands/install.js';
|
|
@@ -192,6 +192,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
|
|
|
192
192
|
defaultRegistry: DEFAULT_DOCKER_REGISTRY,
|
|
193
193
|
defaultVersion: DEFAULT_DOCKER_VERSION,
|
|
194
194
|
});
|
|
195
|
+
const containerPort = resolveDockerImageContainerPort(imageRef);
|
|
195
196
|
const missing = [];
|
|
196
197
|
if (!storagePath) {
|
|
197
198
|
missing.push('storagePath');
|
|
@@ -233,7 +234,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
|
|
|
233
234
|
args.push('--platform', dockerPlatform);
|
|
234
235
|
}
|
|
235
236
|
if (appPort) {
|
|
236
|
-
args.push('-p', `${appPort}
|
|
237
|
+
args.push('-p', `${appPort}:${containerPort}`);
|
|
237
238
|
}
|
|
238
239
|
if (envFile) {
|
|
239
240
|
args.push('--env-file', envFile);
|
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) {
|
|
@@ -94,6 +97,7 @@ function normalizeAuthConfig(config) {
|
|
|
94
97
|
const locale = normalizeOptionalCliLocale(settings.locale);
|
|
95
98
|
const defaultUiHost = normalizeOptionalString(settings.init?.defaultUiHost);
|
|
96
99
|
const defaultApiHost = normalizeOptionalString(settings.init?.defaultApiHost);
|
|
100
|
+
const defaultPortalTemplate = normalizeOptionalString(settings.init?.defaultPortalTemplate);
|
|
97
101
|
const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
|
|
98
102
|
const logRetentionDays = typeof settings.log?.retentionDays === 'number' && Number.isInteger(settings.log.retentionDays)
|
|
99
103
|
? settings.log.retentionDays
|
|
@@ -114,23 +118,31 @@ function normalizeAuthConfig(config) {
|
|
|
114
118
|
name: config.name || config.dockerResourcePrefix,
|
|
115
119
|
settings: {
|
|
116
120
|
...(locale ? { locale } : {}),
|
|
117
|
-
...(defaultUiHost || defaultApiHost
|
|
121
|
+
...(defaultUiHost || defaultApiHost || defaultPortalTemplate
|
|
118
122
|
? {
|
|
119
123
|
init: {
|
|
120
124
|
...(defaultUiHost ? { defaultUiHost } : {}),
|
|
121
125
|
...(defaultApiHost ? { defaultApiHost } : {}),
|
|
126
|
+
...(defaultPortalTemplate ? { defaultPortalTemplate } : {}),
|
|
122
127
|
},
|
|
123
128
|
}
|
|
124
129
|
: {}),
|
|
125
130
|
...(updatePolicy ? { update: { policy: updatePolicy } } : {}),
|
|
126
131
|
...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
|
|
127
132
|
...(settings.docker?.network || settings.docker?.containerPrefix
|
|
133
|
+
|| settings.docker?.nbImageRegistry || settings.docker?.nbImageVariant
|
|
128
134
|
? {
|
|
129
135
|
docker: {
|
|
130
136
|
...(settings.docker?.network ? { network: normalizeOptionalString(settings.docker.network) } : {}),
|
|
131
137
|
...(settings.docker?.containerPrefix
|
|
132
138
|
? { containerPrefix: normalizeOptionalString(settings.docker.containerPrefix) }
|
|
133
139
|
: {}),
|
|
140
|
+
...(settings.docker?.nbImageRegistry
|
|
141
|
+
? { nbImageRegistry: normalizeOptionalString(settings.docker.nbImageRegistry) }
|
|
142
|
+
: {}),
|
|
143
|
+
...(settings.docker?.nbImageVariant
|
|
144
|
+
? { nbImageVariant: normalizeOptionalString(settings.docker.nbImageVariant) }
|
|
145
|
+
: {}),
|
|
134
146
|
},
|
|
135
147
|
}
|
|
136
148
|
: {}),
|
|
@@ -509,6 +521,64 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
|
|
|
509
521
|
};
|
|
510
522
|
await saveAuthConfig(config, options);
|
|
511
523
|
}
|
|
524
|
+
export function resolveEnvProxyEntry(config, provider) {
|
|
525
|
+
const proxy = normalizeEnvProxyConfig(config?.proxy);
|
|
526
|
+
const resolved = {
|
|
527
|
+
...(proxy?.host ? { host: proxy.host } : {}),
|
|
528
|
+
...(proxy?.port !== undefined ? { port: proxy.port } : {}),
|
|
529
|
+
...((provider === 'nginx' ? proxy?.nginx : proxy?.caddy) ?? {}),
|
|
530
|
+
};
|
|
531
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
532
|
+
}
|
|
533
|
+
export async function setEnvProxyEntry(envName, provider, entry, options = {}) {
|
|
534
|
+
await writeEnv(envName, (previous) => {
|
|
535
|
+
const currentProxy = normalizeEnvProxyConfig(previous?.proxy) ?? {};
|
|
536
|
+
const nextEntry = normalizeEnvProxyProviderConfig(entry);
|
|
537
|
+
const nextProxy = { ...currentProxy };
|
|
538
|
+
if (nextEntry && 'host' in nextEntry) {
|
|
539
|
+
const host = normalizeOptionalString(nextEntry.host);
|
|
540
|
+
if (host) {
|
|
541
|
+
nextProxy.host = host;
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
delete nextProxy.host;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (nextEntry && 'port' in nextEntry) {
|
|
548
|
+
const portValue = nextEntry.port;
|
|
549
|
+
const port = typeof portValue === 'number' && Number.isInteger(portValue) && portValue >= 1 && portValue <= 65535
|
|
550
|
+
? portValue
|
|
551
|
+
: undefined;
|
|
552
|
+
if (port !== undefined) {
|
|
553
|
+
nextProxy.port = port;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
delete nextProxy.port;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const providerConfig = nextEntry && Object.keys(nextEntry).some((key) => key !== 'host' && key !== 'port')
|
|
560
|
+
? Object.fromEntries(Object.entries(nextEntry).filter(([key]) => key !== 'host' && key !== 'port'))
|
|
561
|
+
: undefined;
|
|
562
|
+
if (provider === 'nginx') {
|
|
563
|
+
if (providerConfig && Object.keys(providerConfig).length > 0) {
|
|
564
|
+
nextProxy.nginx = providerConfig;
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
delete nextProxy.nginx;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else if (providerConfig && Object.keys(providerConfig).length > 0) {
|
|
571
|
+
nextProxy.caddy = providerConfig;
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
delete nextProxy.caddy;
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
...previous,
|
|
578
|
+
proxy: nextProxy,
|
|
579
|
+
};
|
|
580
|
+
}, options);
|
|
581
|
+
}
|
|
512
582
|
export async function clearEnvRootSetup(envName, options = {}) {
|
|
513
583
|
const config = await loadExactAuthConfig(options);
|
|
514
584
|
const current = config.envs[envName];
|