@nocobase/cli 2.3.0-beta.5 → 2.4.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/dist/commands/api/swagger/get.js +96 -0
- package/dist/commands/api/swagger/index.js +20 -0
- package/dist/commands/api/swagger/list.js +92 -0
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/add.js +6 -0
- package/dist/commands/env/update.js +13 -0
- package/dist/commands/init.js +51 -5
- package/dist/commands/install.js +60 -3
- package/dist/commands/portal/config.js +99 -0
- package/dist/commands/portal/create.js +96 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +126 -0
- package/dist/commands/portal/dev.js +73 -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 +113 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/source/dev.js +1 -1
- package/dist/lib/api-client.js +35 -9
- package/dist/lib/app-client-entry-mode.js +28 -0
- package/dist/lib/app-managed-resources.js +2 -0
- package/dist/lib/auth-store.js +55 -2
- package/dist/lib/bootstrap.js +3 -1
- package/dist/lib/cli-config.js +20 -1
- package/dist/lib/env-auth.js +2 -36
- package/dist/lib/env-command-config.js +1 -0
- package/dist/lib/env-config.js +11 -0
- package/dist/lib/env-portal-config.js +30 -0
- package/dist/lib/env-proxy.js +154 -7
- package/dist/lib/managed-env-file.js +119 -9
- package/dist/lib/managed-init-env.js +4 -1
- package/dist/lib/portal-build-html.js +27 -0
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-config.js +119 -0
- package/dist/lib/portal-configure.js +110 -0
- package/dist/lib/portal-create.js +515 -0
- package/dist/lib/portal-deploy.js +266 -0
- package/dist/lib/portal-destroy.js +114 -0
- package/dist/lib/portal-dev.js +78 -0
- package/dist/lib/portal-env-files.js +54 -0
- package/dist/lib/portal-info.js +28 -0
- package/dist/lib/portal-list.js +205 -0
- package/dist/lib/portal-path-safety.js +76 -0
- package/dist/lib/portal-source.js +692 -0
- package/dist/lib/prompt-catalog-core.js +2 -2
- package/dist/lib/prompt-catalog-terminal.js +4 -5
- package/dist/lib/prompt-web-ui.js +12 -6
- package/dist/lib/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/dist/lib/run-npm.js +85 -20
- package/dist/lib/swagger-command.js +52 -0
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +245 -1
- package/dist/locale/zh-CN.json +245 -1
- package/package.json +5 -2
|
@@ -0,0 +1,98 @@
|
|
|
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 { 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 { listPortalWorkspaces, toPortalOutputItem } from '../../lib/portal-list.js';
|
|
15
|
+
import { printInfo, renderTable } from '../../lib/ui.js';
|
|
16
|
+
const portalListText = (key, values, fallback) => translateCli(`commands.portalList.${key}`, values, { fallback });
|
|
17
|
+
function formatBoolean(value) {
|
|
18
|
+
if (value === null) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
return value ? 'yes' : 'no';
|
|
22
|
+
}
|
|
23
|
+
export default class PortalList extends Command {
|
|
24
|
+
static summary = 'List portal records and development paths';
|
|
25
|
+
static examples = [
|
|
26
|
+
'<%= config.bin %> <%= command.id %>',
|
|
27
|
+
'<%= config.bin %> <%= command.id %> --env dev --yes',
|
|
28
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
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
|
+
'json-output': Flags.boolean({
|
|
41
|
+
char: 'j',
|
|
42
|
+
aliases: ['json'],
|
|
43
|
+
description: 'Print portal records as JSON',
|
|
44
|
+
default: false,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
async run() {
|
|
48
|
+
const { flags } = await this.parse(PortalList);
|
|
49
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
50
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
51
|
+
command: this,
|
|
52
|
+
requestedEnv,
|
|
53
|
+
yes: flags.yes,
|
|
54
|
+
});
|
|
55
|
+
if (!confirmed) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const scope = resolveDefaultConfigScope();
|
|
59
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
60
|
+
const env = await getEnv(envName, { scope });
|
|
61
|
+
if (!env) {
|
|
62
|
+
this.error(portalListText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
63
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
64
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
65
|
+
}
|
|
66
|
+
const result = await listPortalWorkspaces({
|
|
67
|
+
env,
|
|
68
|
+
envName,
|
|
69
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
70
|
+
});
|
|
71
|
+
const outputItems = result.items.map(toPortalOutputItem);
|
|
72
|
+
if (flags['json-output']) {
|
|
73
|
+
this.log(JSON.stringify(outputItems, null, 2));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!outputItems.length) {
|
|
77
|
+
printInfo(portalListText('messages.empty', undefined, 'No portal records found.'));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.log(renderTable([
|
|
81
|
+
portalListText('table.name', undefined, 'Name'),
|
|
82
|
+
portalListText('table.url', undefined, 'URL'),
|
|
83
|
+
portalListText('table.portalType', undefined, 'Portal type'),
|
|
84
|
+
portalListText('table.sourceStorage', undefined, 'Source storage'),
|
|
85
|
+
portalListText('table.path', undefined, 'Development path'),
|
|
86
|
+
portalListText('table.enabled', undefined, 'Enabled'),
|
|
87
|
+
portalListText('table.default', undefined, 'Default'),
|
|
88
|
+
], outputItems.map((item) => [
|
|
89
|
+
item.name,
|
|
90
|
+
item.url,
|
|
91
|
+
item.portalType,
|
|
92
|
+
item.sourceStorage,
|
|
93
|
+
item.developmentPath,
|
|
94
|
+
formatBoolean(item.enabled),
|
|
95
|
+
formatBoolean(item.isDefault),
|
|
96
|
+
])));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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, setEnvPortalPath } 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 { pullPortalSource } from '../../lib/portal-source.js';
|
|
15
|
+
import { printInfo, printSuccess, printWarning } from '../../lib/ui.js';
|
|
16
|
+
const portalPullText = (key, values, fallback) => translateCli(`commands.portalPull.${key}`, values, { fallback });
|
|
17
|
+
export default class PortalPull extends Command {
|
|
18
|
+
static summary = 'Pull portal source into local files';
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer --env prod --yes',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --path ./portals/customer',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> customer --git-repo git@github.com:nocobase/customer.git',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> customer --force',
|
|
25
|
+
'<%= config.bin %> <%= command.id %> customer --no-install',
|
|
26
|
+
];
|
|
27
|
+
static args = {
|
|
28
|
+
portal: Args.string({
|
|
29
|
+
required: true,
|
|
30
|
+
description: 'Portal name',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
static flags = {
|
|
34
|
+
env: Flags.string({
|
|
35
|
+
char: 'e',
|
|
36
|
+
description: 'CLI env name; omitted uses the current env',
|
|
37
|
+
}),
|
|
38
|
+
yes: Flags.boolean({
|
|
39
|
+
char: 'y',
|
|
40
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
41
|
+
default: false,
|
|
42
|
+
}),
|
|
43
|
+
force: Flags.boolean({
|
|
44
|
+
description: 'Delete the existing local files and pull them again',
|
|
45
|
+
default: false,
|
|
46
|
+
}),
|
|
47
|
+
path: Flags.string({
|
|
48
|
+
description: 'Portal workspace directory; defaults to the saved path, then ./<portal>',
|
|
49
|
+
}),
|
|
50
|
+
'git-repo': Flags.string({
|
|
51
|
+
description: 'Temporarily pull source from this Git repository without updating the portal source configuration',
|
|
52
|
+
}),
|
|
53
|
+
'git-branch': Flags.string({
|
|
54
|
+
description: 'Git branch for the temporary --git-repo pull; defaults to main',
|
|
55
|
+
}),
|
|
56
|
+
'git-path': Flags.string({
|
|
57
|
+
description: 'Directory inside the temporary Git repository; defaults to the repository root',
|
|
58
|
+
}),
|
|
59
|
+
install: Flags.boolean({
|
|
60
|
+
description: 'Run pnpm install after pulling the portal source',
|
|
61
|
+
default: true,
|
|
62
|
+
allowNo: true,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
async run() {
|
|
66
|
+
const { args, flags } = await this.parse(PortalPull);
|
|
67
|
+
if ((flags['git-branch'] || flags['git-path']) && !flags['git-repo']) {
|
|
68
|
+
this.error(portalPullText('errors.gitRepoRequiredForTemporaryPull', undefined, [
|
|
69
|
+
'--git-branch and --git-path require --git-repo for a temporary Git pull.',
|
|
70
|
+
'To update the portal configuration, use `nb portal config`.',
|
|
71
|
+
].join(' ')));
|
|
72
|
+
}
|
|
73
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
74
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
75
|
+
command: this,
|
|
76
|
+
requestedEnv,
|
|
77
|
+
yes: flags.yes,
|
|
78
|
+
});
|
|
79
|
+
if (!confirmed) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const scope = resolveDefaultConfigScope();
|
|
83
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
84
|
+
const env = await getEnv(envName, { scope });
|
|
85
|
+
if (!env) {
|
|
86
|
+
this.error(portalPullText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
87
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
88
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
89
|
+
}
|
|
90
|
+
const result = await pullPortalSource({
|
|
91
|
+
portal: args.portal,
|
|
92
|
+
env,
|
|
93
|
+
envName,
|
|
94
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
95
|
+
force: flags.force,
|
|
96
|
+
installDependencies: flags.install,
|
|
97
|
+
sourcePath: flags.path,
|
|
98
|
+
defaultSourcePath: true,
|
|
99
|
+
gitRepo: flags['git-repo'],
|
|
100
|
+
gitBranch: flags['git-branch'],
|
|
101
|
+
gitPath: flags['git-path'],
|
|
102
|
+
});
|
|
103
|
+
if (!result.changed) {
|
|
104
|
+
printInfo(result.noopReason ?? portalPullText('messages.noop', undefined, 'No pull is needed.'));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await setEnvPortalPath(envName, result.portal, result.portalDir, { scope });
|
|
108
|
+
printSuccess(portalPullText('messages.pulled', { portal: result.portal, portalDir: result.portalDir }, `Pulled portal source "${result.portal}" into ${result.portalDir}`));
|
|
109
|
+
if (result.installFailed) {
|
|
110
|
+
printWarning(portalPullText('messages.installFailed', { portalDir: result.portalDir }, `Dependency installation did not finish successfully. Run \`pnpm install\` manually in ${result.portalDir}.`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -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',
|
|
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
|
+
}
|
|
@@ -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
|
}
|
package/dist/lib/api-client.js
CHANGED
|
@@ -19,6 +19,7 @@ import { promises as fs } from 'node:fs';
|
|
|
19
19
|
import { basename, dirname } from 'node:path';
|
|
20
20
|
import { Readable } from 'node:stream';
|
|
21
21
|
import { pipeline } from 'node:stream/promises';
|
|
22
|
+
import { translateCli } from './cli-locale.js';
|
|
22
23
|
import { resolveServerRequestTarget } from './env-auth.js';
|
|
23
24
|
import { fetchWithPreservedAuthRedirect } from './http-request.js';
|
|
24
25
|
const CLI_REQUEST_SOURCE_HEADER = 'x-request-source';
|
|
@@ -40,7 +41,32 @@ function parseJsonInput(raw, flagName) {
|
|
|
40
41
|
function normalizeBaseUrl(baseUrl) {
|
|
41
42
|
return baseUrl.replace(/\/+$/, '');
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
function buildAuthFailureCommand(envName) {
|
|
45
|
+
const normalizedEnvName = String(envName ?? '').trim();
|
|
46
|
+
return normalizedEnvName ? `nb env auth ${normalizedEnvName}` : 'nb env auth';
|
|
47
|
+
}
|
|
48
|
+
function decorateAuthFailureResponse(data, status, envName) {
|
|
49
|
+
if (status !== 401) {
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
const cliCommand = buildAuthFailureCommand(envName);
|
|
53
|
+
const cliHint = translateCli('apiClient.authRequiredHint', { command: cliCommand }, {
|
|
54
|
+
fallback: 'Authentication failed or the saved session has expired. Run `{{command}}` to sign in again.',
|
|
55
|
+
});
|
|
56
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
57
|
+
return {
|
|
58
|
+
...data,
|
|
59
|
+
cliHint,
|
|
60
|
+
cliCommand,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
error: data,
|
|
65
|
+
cliHint,
|
|
66
|
+
cliCommand,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function parseResponse(response, envName) {
|
|
44
70
|
const text = await response.text();
|
|
45
71
|
let data = text;
|
|
46
72
|
if (text) {
|
|
@@ -54,10 +80,10 @@ async function parseResponse(response) {
|
|
|
54
80
|
return {
|
|
55
81
|
ok: response.ok,
|
|
56
82
|
status: response.status,
|
|
57
|
-
data,
|
|
83
|
+
data: decorateAuthFailureResponse(data, response.status, envName),
|
|
58
84
|
};
|
|
59
85
|
}
|
|
60
|
-
async function parseBinaryResponse(response, outputPath) {
|
|
86
|
+
async function parseBinaryResponse(response, outputPath, envName) {
|
|
61
87
|
if (response.ok && response.body) {
|
|
62
88
|
await fs.mkdir(dirname(outputPath), { recursive: true }).catch(() => undefined);
|
|
63
89
|
await pipeline(Readable.fromWeb(response.body), createWriteStream(outputPath));
|
|
@@ -69,7 +95,7 @@ async function parseBinaryResponse(response, outputPath) {
|
|
|
69
95
|
},
|
|
70
96
|
};
|
|
71
97
|
}
|
|
72
|
-
return parseResponse(response);
|
|
98
|
+
return parseResponse(response, envName);
|
|
73
99
|
}
|
|
74
100
|
function parseScalarValue(value, type) {
|
|
75
101
|
if (value === undefined) {
|
|
@@ -215,7 +241,7 @@ async function createMultipartBody(flags, operation) {
|
|
|
215
241
|
return hasValues ? formData : undefined;
|
|
216
242
|
}
|
|
217
243
|
export async function executeApiRequest(options) {
|
|
218
|
-
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
244
|
+
const { baseUrl, token, envName } = await resolveServerRequestTarget(options);
|
|
219
245
|
const headers = new Headers();
|
|
220
246
|
headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
|
|
221
247
|
headers.set(CLI_VERSION_HEADER, options.cliVersion);
|
|
@@ -283,12 +309,12 @@ export async function executeApiRequest(options) {
|
|
|
283
309
|
if (!outputPath) {
|
|
284
310
|
throw new Error('Missing required output path --output');
|
|
285
311
|
}
|
|
286
|
-
return parseBinaryResponse(response, outputPath);
|
|
312
|
+
return parseBinaryResponse(response, outputPath, envName);
|
|
287
313
|
}
|
|
288
|
-
return parseResponse(response);
|
|
314
|
+
return parseResponse(response, envName);
|
|
289
315
|
}
|
|
290
316
|
export async function executeRawApiRequest(options) {
|
|
291
|
-
const { baseUrl, token } = await resolveServerRequestTarget(options);
|
|
317
|
+
const { baseUrl, token, envName } = await resolveServerRequestTarget(options);
|
|
292
318
|
const headers = new Headers();
|
|
293
319
|
headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
|
|
294
320
|
if (token) {
|
|
@@ -332,7 +358,7 @@ export async function executeRawApiRequest(options) {
|
|
|
332
358
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
333
359
|
signal: controller?.signal,
|
|
334
360
|
});
|
|
335
|
-
return parseResponse(response);
|
|
361
|
+
return parseResponse(response, envName);
|
|
336
362
|
}
|
|
337
363
|
finally {
|
|
338
364
|
if (timeout) {
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
export const APP_CLIENT_ENTRY_MODES = [
|
|
10
|
+
'legacy-default',
|
|
11
|
+
'modern-default',
|
|
12
|
+
'modern-only',
|
|
13
|
+
'settings-default',
|
|
14
|
+
];
|
|
15
|
+
export const PUBLIC_APP_CLIENT_ENTRY_MODES = ['legacy-default', 'modern-default', 'modern-only'];
|
|
16
|
+
export function normalizeAppClientEntryMode(value) {
|
|
17
|
+
const text = String(value ?? '').trim();
|
|
18
|
+
return APP_CLIENT_ENTRY_MODES.includes(text) ? text : undefined;
|
|
19
|
+
}
|
|
20
|
+
export function normalizePublicAppClientEntryMode(value) {
|
|
21
|
+
const text = String(value ?? '').trim();
|
|
22
|
+
return PUBLIC_APP_CLIENT_ENTRY_MODES.includes(text)
|
|
23
|
+
? text
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
export function defaultAppClientEntryModeForDownloadVersion(version) {
|
|
27
|
+
return String(version ?? '').trim() === 'latest' ? 'legacy-default' : 'modern-only';
|
|
28
|
+
}
|
|
@@ -186,6 +186,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
|
|
|
186
186
|
const dbTablePrefix = trimValue(config.dbTablePrefix);
|
|
187
187
|
const dbUnderscored = typeof config.dbUnderscored === 'boolean' ? config.dbUnderscored : undefined;
|
|
188
188
|
const extractClientAssets = resolveDockerClientAssetsExtractEnabled(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
|
|
189
|
+
const appClientEntryMode = trimValue(config.appClientEntryMode);
|
|
189
190
|
const dockerRegistry = trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY;
|
|
190
191
|
const version = trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION;
|
|
191
192
|
const imageRef = resolveDockerImageRef(dockerRegistry, version, {
|
|
@@ -245,6 +246,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
|
|
|
245
246
|
const lifecycleEnvVars = managedAppLifecycleEnvVars();
|
|
246
247
|
args.push('-e', `APP_ENV=${lifecycleEnvVars.APP_ENV}`, '-e', `NODE_ENV=${lifecycleEnvVars.NODE_ENV}`, '-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:${DOCKER_APP_STORAGE_DESTINATION}`);
|
|
247
248
|
pushOptionalEnvArg(args, 'APP_PUBLIC_PATH', appPublicPath ? resolveAppPublicPath(appPublicPath) : undefined);
|
|
249
|
+
pushOptionalEnvArg(args, 'APP_CLIENT_ENTRY_MODE', appClientEntryMode);
|
|
248
250
|
pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema || undefined);
|
|
249
251
|
pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix || undefined);
|
|
250
252
|
pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -14,6 +14,7 @@ import { normalizeCliLocale } from './cli-locale.js';
|
|
|
14
14
|
import { normalizeEnvProxyConfig, normalizeEnvProxyProviderConfig, } from './env-proxy-config.js';
|
|
15
15
|
import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
|
|
16
16
|
import { ENV_CONFIG_SCHEMA_VERSION, normalizeEnvConfigSchemaVersion } from './env-config.js';
|
|
17
|
+
import { normalizeEnvPortalsConfig, } from './env-portal-config.js';
|
|
17
18
|
import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
|
|
18
19
|
function normalizeStoredEnvKind(value) {
|
|
19
20
|
const kind = String(value ?? '').trim();
|
|
@@ -78,11 +79,12 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
78
79
|
if (!entry) {
|
|
79
80
|
return entry;
|
|
80
81
|
}
|
|
81
|
-
const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, schemaVersion: _schemaVersion, ...rest } = entry;
|
|
82
|
+
const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, schemaVersion: _schemaVersion, portals: _portals, ...rest } = entry;
|
|
82
83
|
const normalizedKind = resolveEnvKind(entry);
|
|
83
84
|
const apiBaseUrl = readEnvApiBaseUrl(entry);
|
|
84
85
|
const schemaVersion = normalizeEnvConfigSchemaVersion(entry.schemaVersion);
|
|
85
86
|
const proxy = normalizeEnvProxyConfig(entry.proxy);
|
|
87
|
+
const portals = normalizeEnvPortalsConfig(entry.portals);
|
|
86
88
|
return {
|
|
87
89
|
...rest,
|
|
88
90
|
...(schemaVersion ? { schemaVersion } : {}),
|
|
@@ -90,6 +92,7 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
90
92
|
...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
|
|
91
93
|
...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
|
|
92
94
|
...(proxy ? { proxy } : {}),
|
|
95
|
+
...(portals ? { portals } : {}),
|
|
93
96
|
};
|
|
94
97
|
}
|
|
95
98
|
function normalizeAuthConfig(config) {
|
|
@@ -97,6 +100,7 @@ function normalizeAuthConfig(config) {
|
|
|
97
100
|
const locale = normalizeOptionalCliLocale(settings.locale);
|
|
98
101
|
const defaultUiHost = normalizeOptionalString(settings.init?.defaultUiHost);
|
|
99
102
|
const defaultApiHost = normalizeOptionalString(settings.init?.defaultApiHost);
|
|
103
|
+
const defaultPortalTemplate = normalizeOptionalString(settings.init?.defaultPortalTemplate);
|
|
100
104
|
const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
|
|
101
105
|
const logRetentionDays = typeof settings.log?.retentionDays === 'number' && Number.isInteger(settings.log.retentionDays)
|
|
102
106
|
? settings.log.retentionDays
|
|
@@ -117,11 +121,12 @@ function normalizeAuthConfig(config) {
|
|
|
117
121
|
name: config.name || config.dockerResourcePrefix,
|
|
118
122
|
settings: {
|
|
119
123
|
...(locale ? { locale } : {}),
|
|
120
|
-
...(defaultUiHost || defaultApiHost
|
|
124
|
+
...(defaultUiHost || defaultApiHost || defaultPortalTemplate
|
|
121
125
|
? {
|
|
122
126
|
init: {
|
|
123
127
|
...(defaultUiHost ? { defaultUiHost } : {}),
|
|
124
128
|
...(defaultApiHost ? { defaultApiHost } : {}),
|
|
129
|
+
...(defaultPortalTemplate ? { defaultPortalTemplate } : {}),
|
|
125
130
|
},
|
|
126
131
|
}
|
|
127
132
|
: {}),
|
|
@@ -330,6 +335,7 @@ export class Env {
|
|
|
330
335
|
put('APP_PORT', this.appPort);
|
|
331
336
|
put('APP_PUBLIC_PATH', this.config.appPublicPath ? resolveAppPublicPath(this.config.appPublicPath) : undefined);
|
|
332
337
|
put('CDN_BASE_URL', this.config.cdnBaseUrl);
|
|
338
|
+
put('APP_CLIENT_ENTRY_MODE', this.config.appClientEntryMode);
|
|
333
339
|
put('APP_KEY', this.config.appKey);
|
|
334
340
|
put('TZ', this.config.timezone);
|
|
335
341
|
put('DB_DIALECT', this.config.dbDialect);
|
|
@@ -519,6 +525,53 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
|
|
|
519
525
|
};
|
|
520
526
|
await saveAuthConfig(config, options);
|
|
521
527
|
}
|
|
528
|
+
export function resolveEnvPortalPath(config, portal) {
|
|
529
|
+
const portalName = normalizeOptionalString(portal);
|
|
530
|
+
if (!portalName) {
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
return normalizeOptionalString(config?.portals?.[portalName]?.path);
|
|
534
|
+
}
|
|
535
|
+
export async function setEnvPortalPath(envName, portal, portalPath, options = {}) {
|
|
536
|
+
const portalName = normalizeOptionalString(portal);
|
|
537
|
+
const normalizedPath = normalizeOptionalString(portalPath);
|
|
538
|
+
if (!portalName || !normalizedPath) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
await writeEnv(envName, (previous) => {
|
|
542
|
+
if (!previous) {
|
|
543
|
+
throw new Error(`Env "${envName}" is not configured`);
|
|
544
|
+
}
|
|
545
|
+
const portals = normalizeEnvPortalsConfig(previous.portals) ?? {};
|
|
546
|
+
return {
|
|
547
|
+
...previous,
|
|
548
|
+
portals: {
|
|
549
|
+
...portals,
|
|
550
|
+
[portalName]: {
|
|
551
|
+
...(portals[portalName] ?? {}),
|
|
552
|
+
path: normalizedPath,
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
}, options);
|
|
557
|
+
}
|
|
558
|
+
export async function unsetEnvPortalPath(envName, portal, options = {}) {
|
|
559
|
+
const portalName = normalizeOptionalString(portal);
|
|
560
|
+
if (!portalName) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
await writeEnv(envName, (previous) => {
|
|
564
|
+
if (!previous) {
|
|
565
|
+
throw new Error(`Env "${envName}" is not configured`);
|
|
566
|
+
}
|
|
567
|
+
const portals = normalizeEnvPortalsConfig(previous.portals) ?? {};
|
|
568
|
+
delete portals[portalName];
|
|
569
|
+
return {
|
|
570
|
+
...previous,
|
|
571
|
+
...(Object.keys(portals).length > 0 ? { portals } : { portals: undefined }),
|
|
572
|
+
};
|
|
573
|
+
}, options);
|
|
574
|
+
}
|
|
522
575
|
export function resolveEnvProxyEntry(config, provider) {
|
|
523
576
|
const proxy = normalizeEnvProxyConfig(config?.proxy);
|
|
524
577
|
const resolved = {
|
package/dist/lib/bootstrap.js
CHANGED
|
@@ -72,7 +72,9 @@ function hasVersionFlag(argv) {
|
|
|
72
72
|
function isBuiltinCommand(argv) {
|
|
73
73
|
const commandTokens = argv.filter((token) => token && !token.startsWith('-'));
|
|
74
74
|
const [topic, subtopic] = commandTokens;
|
|
75
|
-
return topic === 'env' ||
|
|
75
|
+
return (topic === 'env' ||
|
|
76
|
+
topic === 'resource' ||
|
|
77
|
+
(topic === 'api' && (subtopic === 'resource' || subtopic === 'swagger')));
|
|
76
78
|
}
|
|
77
79
|
export function shouldSkipRuntimeBootstrap(argv) {
|
|
78
80
|
return hasVersionFlag(argv) || isBuiltinCommand(argv);
|
package/dist/lib/cli-config.js
CHANGED
|
@@ -34,6 +34,7 @@ export const SUPPORTED_CLI_CONFIG_KEYS = [
|
|
|
34
34
|
'locale',
|
|
35
35
|
'default-ui-host',
|
|
36
36
|
'default-api-host',
|
|
37
|
+
'default-portal-template',
|
|
37
38
|
'update.policy',
|
|
38
39
|
'license.pkg-url',
|
|
39
40
|
'docker.network',
|
|
@@ -120,7 +121,10 @@ function pruneSettings(config) {
|
|
|
120
121
|
delete config.settings.locale;
|
|
121
122
|
}
|
|
122
123
|
const init = config.settings?.init;
|
|
123
|
-
if (init &&
|
|
124
|
+
if (init &&
|
|
125
|
+
!trimValue(init.defaultUiHost) &&
|
|
126
|
+
!trimValue(init.defaultApiHost) &&
|
|
127
|
+
!trimValue(init.defaultPortalTemplate)) {
|
|
124
128
|
delete config.settings?.init;
|
|
125
129
|
}
|
|
126
130
|
const update = config.settings?.update;
|
|
@@ -181,6 +185,8 @@ export function getExplicitCliConfigValue(config, key) {
|
|
|
181
185
|
return trimValue(config.settings?.init?.defaultUiHost);
|
|
182
186
|
case 'default-api-host':
|
|
183
187
|
return trimValue(config.settings?.init?.defaultApiHost);
|
|
188
|
+
case 'default-portal-template':
|
|
189
|
+
return trimValue(config.settings?.init?.defaultPortalTemplate);
|
|
184
190
|
case 'update.policy':
|
|
185
191
|
return normalizeCliUpdatePolicy(config.settings?.update?.policy);
|
|
186
192
|
case 'license.pkg-url':
|
|
@@ -233,6 +239,8 @@ export function getEffectiveCliConfigValue(config, key) {
|
|
|
233
239
|
return '127.0.0.1';
|
|
234
240
|
case 'default-api-host':
|
|
235
241
|
return '127.0.0.1';
|
|
242
|
+
case 'default-portal-template':
|
|
243
|
+
return explicit ?? '';
|
|
236
244
|
case 'update.policy':
|
|
237
245
|
return explicit ?? DEFAULT_UPDATE_POLICY;
|
|
238
246
|
case 'license.pkg-url':
|
|
@@ -377,6 +385,12 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
377
385
|
defaultApiHost: normalized,
|
|
378
386
|
};
|
|
379
387
|
break;
|
|
388
|
+
case 'default-portal-template':
|
|
389
|
+
config.settings.init = {
|
|
390
|
+
...(config.settings.init ?? {}),
|
|
391
|
+
defaultPortalTemplate: normalized,
|
|
392
|
+
};
|
|
393
|
+
break;
|
|
380
394
|
case 'update.policy':
|
|
381
395
|
config.settings.update = {
|
|
382
396
|
...(config.settings.update ?? {}),
|
|
@@ -512,6 +526,11 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
512
526
|
delete config.settings.init.defaultApiHost;
|
|
513
527
|
}
|
|
514
528
|
break;
|
|
529
|
+
case 'default-portal-template':
|
|
530
|
+
if (config.settings.init) {
|
|
531
|
+
delete config.settings.init.defaultPortalTemplate;
|
|
532
|
+
}
|
|
533
|
+
break;
|
|
515
534
|
case 'update.policy':
|
|
516
535
|
if (config.settings.update) {
|
|
517
536
|
delete config.settings.update.policy;
|