@nocobase/cli 3.0.0-alpha.4 → 3.0.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/dist/commands/portal/config.js +16 -5
- package/dist/commands/portal/create.js +17 -26
- package/dist/commands/portal/destroy.js +28 -6
- package/dist/commands/portal/dev.js +2 -0
- package/dist/commands/portal/list.js +5 -5
- package/dist/commands/portal/pull.js +32 -3
- package/dist/lib/api-client.js +35 -9
- package/dist/lib/auth-store.js +51 -1
- package/dist/lib/env-auth.js +1 -1
- package/dist/lib/env-config.js +5 -0
- package/dist/lib/env-portal-config.js +30 -0
- package/dist/lib/portal-build-html.js +27 -0
- package/dist/lib/portal-config.js +6 -20
- package/dist/lib/portal-configure.js +49 -56
- package/dist/lib/portal-create.js +96 -14
- package/dist/lib/portal-deploy.js +17 -49
- package/dist/lib/portal-destroy.js +34 -20
- package/dist/lib/portal-dev.js +4 -5
- package/dist/lib/portal-env-files.js +2 -1
- package/dist/lib/portal-info.js +2 -5
- package/dist/lib/portal-list.js +20 -26
- package/dist/lib/portal-path-safety.js +76 -0
- package/dist/lib/portal-source.js +186 -54
- package/dist/lib/run-npm.js +46 -0
- package/dist/locale/en-US.json +45 -14
- package/dist/locale/zh-CN.json +45 -14
- package/package.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
-
import { getCurrentEnvName, getEnv } from '../../lib/auth-store.js';
|
|
10
|
+
import { getCurrentEnvName, getEnv, setEnvPortalPath } from '../../lib/auth-store.js';
|
|
11
11
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
12
|
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
13
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
@@ -17,6 +17,7 @@ const portalConfigureText = (key, values, fallback) => translateCli(`commands.po
|
|
|
17
17
|
export default class PortalConfig extends Command {
|
|
18
18
|
static summary = 'Update portal source configuration';
|
|
19
19
|
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> customer --path ./portals/customer',
|
|
20
21
|
'<%= config.bin %> <%= command.id %> customer --source-storage nocobase',
|
|
21
22
|
'<%= config.bin %> <%= command.id %> customer --source-storage git --git-repo git@github.com:nocobase/customer-portal.git',
|
|
22
23
|
'<%= config.bin %> <%= command.id %> customer --git-branch main --git-path portals/customer',
|
|
@@ -41,6 +42,9 @@ export default class PortalConfig extends Command {
|
|
|
41
42
|
description: 'Where portal source code is managed',
|
|
42
43
|
options: ['nocobase', 'git'],
|
|
43
44
|
}),
|
|
45
|
+
path: Flags.string({
|
|
46
|
+
description: 'Portal development workspace directory',
|
|
47
|
+
}),
|
|
44
48
|
'git-repo': Flags.string({
|
|
45
49
|
description: 'Git repository URL used when --source-storage=git',
|
|
46
50
|
}),
|
|
@@ -79,10 +83,17 @@ export default class PortalConfig extends Command {
|
|
|
79
83
|
gitRepo: flags['git-repo'],
|
|
80
84
|
gitBranch: flags['git-branch'],
|
|
81
85
|
gitPath: flags['git-path'],
|
|
86
|
+
sourcePath: flags.path,
|
|
82
87
|
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
if (flags.path) {
|
|
89
|
+
await setEnvPortalPath(envName, result.portal, result.portalDir, { scope });
|
|
90
|
+
}
|
|
91
|
+
printSuccess(portalConfigureText('messages.updated', { portal: result.portal }, `Portal "${result.portal}" configuration updated.`));
|
|
92
|
+
if (result.pathUpdated) {
|
|
93
|
+
printInfo(portalConfigureText('messages.pathUpdated', { portalDir: result.portalDir }, `Development path: ${result.portalDir}`));
|
|
94
|
+
}
|
|
95
|
+
if (result.config) {
|
|
96
|
+
printInfo(portalConfigureText('messages.remoteSynced', undefined, 'Remote portal record: synced'));
|
|
97
|
+
}
|
|
87
98
|
}
|
|
88
99
|
}
|
|
@@ -7,21 +7,21 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
-
import { getEnv } from '../../lib/auth-store.js';
|
|
10
|
+
import { getCurrentEnvName, getEnv, setEnvPortalPath } from '../../lib/auth-store.js';
|
|
11
11
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
12
|
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
13
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
14
14
|
import { createPortalWorkspace } from '../../lib/portal-create.js';
|
|
15
|
-
import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
15
|
+
import { printInfo, printSuccess, printWarning } from '../../lib/ui.js';
|
|
16
16
|
const DEFAULT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
17
17
|
const portalCreateText = (key, values, fallback) => translateCli(`commands.portalCreate.${key}`, values, { fallback });
|
|
18
18
|
export default class PortalCreate extends Command {
|
|
19
19
|
static summary = 'Create a local AI portal from a template';
|
|
20
20
|
static examples = [
|
|
21
21
|
'<%= config.bin %> <%= command.id %> customer',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --path ./portals/customer',
|
|
22
23
|
'<%= config.bin %> <%= command.id %> customer --template @nocobase/portal-template-default',
|
|
23
24
|
'<%= config.bin %> <%= command.id %> customer --env dev --yes',
|
|
24
|
-
'<%= config.bin %> <%= command.id %> customer --source-storage git --git-repo git@github.com:nocobase/customer-portal.git',
|
|
25
25
|
];
|
|
26
26
|
static args = {
|
|
27
27
|
portal: Args.string({
|
|
@@ -46,24 +46,13 @@ export default class PortalCreate extends Command {
|
|
|
46
46
|
title: Flags.string({
|
|
47
47
|
description: 'Portal display title; defaults to a title generated from the portal slug',
|
|
48
48
|
}),
|
|
49
|
+
path: Flags.string({
|
|
50
|
+
description: 'Portal workspace directory; defaults to ./<portal>',
|
|
51
|
+
}),
|
|
49
52
|
force: Flags.boolean({
|
|
50
53
|
description: 'Delete the existing portal and recreate it',
|
|
51
54
|
default: false,
|
|
52
55
|
}),
|
|
53
|
-
'source-storage': Flags.string({
|
|
54
|
-
description: 'Where portal source code is managed',
|
|
55
|
-
options: ['nocobase', 'git'],
|
|
56
|
-
default: 'nocobase',
|
|
57
|
-
}),
|
|
58
|
-
'git-repo': Flags.string({
|
|
59
|
-
description: 'Git repository URL used when --source-storage=git',
|
|
60
|
-
}),
|
|
61
|
-
'git-branch': Flags.string({
|
|
62
|
-
description: 'Git branch used when --source-storage=git',
|
|
63
|
-
}),
|
|
64
|
-
'git-path': Flags.string({
|
|
65
|
-
description: 'Directory inside the Git repository for this portal; defaults to the repository root',
|
|
66
|
-
}),
|
|
67
56
|
};
|
|
68
57
|
async run() {
|
|
69
58
|
const { args, flags } = await this.parse(PortalCreate);
|
|
@@ -77,10 +66,11 @@ export default class PortalCreate extends Command {
|
|
|
77
66
|
return;
|
|
78
67
|
}
|
|
79
68
|
const scope = resolveDefaultConfigScope();
|
|
80
|
-
const
|
|
69
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
70
|
+
const env = await getEnv(envName, { scope });
|
|
81
71
|
if (!env) {
|
|
82
|
-
this.error(
|
|
83
|
-
? portalCreateText('errors.envNotConfigured', { envName
|
|
72
|
+
this.error(requestedEnv
|
|
73
|
+
? portalCreateText('errors.envNotConfigured', { envName }, `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`)
|
|
84
74
|
: portalCreateText('errors.noEnvConfigured', undefined, 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
85
75
|
}
|
|
86
76
|
const result = await createPortalWorkspace({
|
|
@@ -88,18 +78,19 @@ export default class PortalCreate extends Command {
|
|
|
88
78
|
title: flags.title,
|
|
89
79
|
template: flags.template,
|
|
90
80
|
env,
|
|
91
|
-
envName
|
|
81
|
+
envName,
|
|
92
82
|
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
93
83
|
force: flags.force,
|
|
94
|
-
|
|
95
|
-
gitRepo: flags['git-repo'],
|
|
96
|
-
gitBranch: flags['git-branch'],
|
|
97
|
-
gitPath: flags['git-path'],
|
|
84
|
+
sourcePath: flags.path,
|
|
98
85
|
onSkipInstall: (message) => printInfo(message),
|
|
99
86
|
});
|
|
100
|
-
|
|
87
|
+
await setEnvPortalPath(envName, result.portal, result.portalDir, { scope });
|
|
88
|
+
printSuccess(portalCreateText('messages.created', { portal: result.portal, portalDir: result.portalDir }, `Portal "${result.portal}" created at ${result.portalDir}`));
|
|
101
89
|
printInfo(portalCreateText('messages.app', { app: result.app }, `App: ${result.app}`));
|
|
102
90
|
printInfo(portalCreateText('messages.base', { base: result.portalBase }, `Base: ${result.portalBase}`));
|
|
103
91
|
printInfo(portalCreateText('messages.sourceStorage', { sourceStorage: result.sourceStorage }, `Source storage: ${result.sourceStorage}`));
|
|
92
|
+
if (result.installFailed) {
|
|
93
|
+
printWarning(portalCreateText('messages.installFailed', { portalDir: result.portalDir }, `Dependency installation did not finish successfully. Run \`pnpm install\` manually in ${result.portalDir}.`));
|
|
94
|
+
}
|
|
104
95
|
}
|
|
105
96
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
-
import { getCurrentEnvName, getEnv } from '../../lib/auth-store.js';
|
|
10
|
+
import { getCurrentEnvName, getEnv, unsetEnvPortalPath } from '../../lib/auth-store.js';
|
|
11
11
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
12
|
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
13
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
@@ -15,6 +15,9 @@ import { confirm } from "../../lib/inquirer.js";
|
|
|
15
15
|
import { destroyPortalWorkspace } from '../../lib/portal-destroy.js';
|
|
16
16
|
import { isInteractiveTerminal, printInfo, printSuccess } from '../../lib/ui.js';
|
|
17
17
|
const portalDestroyText = (key, values, fallback) => translateCli(`commands.portalDestroy.${key}`, values, { fallback });
|
|
18
|
+
function portalDestroyStatus(status) {
|
|
19
|
+
return portalDestroyText(`statuses.${status}`, undefined, status);
|
|
20
|
+
}
|
|
18
21
|
async function ensureDestroyConfirmed(options) {
|
|
19
22
|
if (options.yes) {
|
|
20
23
|
return true;
|
|
@@ -24,7 +27,7 @@ async function ensureDestroyConfirmed(options) {
|
|
|
24
27
|
}
|
|
25
28
|
try {
|
|
26
29
|
return Boolean(await confirm({
|
|
27
|
-
message: portalDestroyText('prompts.confirm', { portal: options.portal }, `Destroy portal "${options.portal}" and delete its
|
|
30
|
+
message: portalDestroyText('prompts.confirm', { portal: options.portal }, `Destroy portal "${options.portal}" and delete its deployment directory?`),
|
|
28
31
|
default: false,
|
|
29
32
|
}));
|
|
30
33
|
}
|
|
@@ -33,9 +36,10 @@ async function ensureDestroyConfirmed(options) {
|
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
export default class PortalDestroy extends Command {
|
|
36
|
-
static summary = 'Destroy a portal record and
|
|
39
|
+
static summary = 'Destroy a portal record and deployed files';
|
|
37
40
|
static examples = [
|
|
38
41
|
'<%= config.bin %> <%= command.id %> customer --yes',
|
|
42
|
+
'<%= config.bin %> <%= command.id %> customer --delete-dev-path --yes',
|
|
39
43
|
'<%= config.bin %> <%= command.id %> customer --env dev --yes',
|
|
40
44
|
'<%= config.bin %> <%= command.id %> customer --force --yes',
|
|
41
45
|
];
|
|
@@ -56,7 +60,12 @@ export default class PortalDestroy extends Command {
|
|
|
56
60
|
default: false,
|
|
57
61
|
}),
|
|
58
62
|
force: Flags.boolean({
|
|
59
|
-
description: 'Ignore missing portal records or
|
|
63
|
+
description: 'Ignore missing portal records or deployment files',
|
|
64
|
+
default: false,
|
|
65
|
+
}),
|
|
66
|
+
'delete-dev-path': Flags.boolean({
|
|
67
|
+
char: 'D',
|
|
68
|
+
description: 'Delete the portal development directory in addition to the deployed portal',
|
|
60
69
|
default: false,
|
|
61
70
|
}),
|
|
62
71
|
};
|
|
@@ -93,12 +102,25 @@ export default class PortalDestroy extends Command {
|
|
|
93
102
|
envName,
|
|
94
103
|
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
95
104
|
force: flags.force,
|
|
105
|
+
deleteDevPath: flags['delete-dev-path'],
|
|
96
106
|
});
|
|
107
|
+
await unsetEnvPortalPath(envName, result.portal, { scope });
|
|
97
108
|
printSuccess(portalDestroyText('messages.destroyed', { portal: result.portal }, `Portal "${result.portal}" destroyed.`));
|
|
98
109
|
printInfo(portalDestroyText('messages.mode', { mode: result.mode }, `Mode: ${result.mode}`));
|
|
99
110
|
printInfo(portalDestroyText('messages.app', { app: result.app }, `App: ${result.app}`));
|
|
100
111
|
printInfo(portalDestroyText('messages.base', { base: result.portalBase }, `Base: ${result.portalBase}`));
|
|
101
|
-
printInfo(portalDestroyText('messages.record', { status: result.recordDeleted ? 'deleted' : 'missing' }, `Record: ${result.recordDeleted ? 'deleted' : 'missing'}`));
|
|
102
|
-
|
|
112
|
+
printInfo(portalDestroyText('messages.record', { status: portalDestroyStatus(result.recordDeleted ? 'deleted' : 'missing') }, `Record: ${result.recordDeleted ? 'deleted' : 'missing'}`));
|
|
113
|
+
const deploymentPathStatus = result.deploymentPathDeleted ? 'deleted' : 'missing';
|
|
114
|
+
printInfo(portalDestroyText('messages.deploymentPath', { dir: result.deploymentPath, status: portalDestroyStatus(deploymentPathStatus) }, `Deployment path: ${deploymentPathStatus} (${result.deploymentPath})`));
|
|
115
|
+
let developmentPathStatus = 'missing';
|
|
116
|
+
if (result.developmentPath) {
|
|
117
|
+
developmentPathStatus = result.developmentPathDeleted ? 'deleted' : 'retained';
|
|
118
|
+
}
|
|
119
|
+
printInfo(portalDestroyText(result.developmentPath ? 'messages.developmentPath' : 'messages.developmentPathMissing', {
|
|
120
|
+
dir: result.developmentPath,
|
|
121
|
+
status: portalDestroyStatus(developmentPathStatus),
|
|
122
|
+
}, result.developmentPath
|
|
123
|
+
? `Development path: ${developmentPathStatus} (${result.developmentPath})`
|
|
124
|
+
: 'Development path: missing'));
|
|
103
125
|
}
|
|
104
126
|
}
|
|
@@ -59,6 +59,8 @@ export default class PortalDev extends Command {
|
|
|
59
59
|
await devPortalWorkspace({
|
|
60
60
|
portal: args.portal,
|
|
61
61
|
env,
|
|
62
|
+
envName,
|
|
63
|
+
cliVersion: String(this.config?.pjson?.version ?? '').trim(),
|
|
62
64
|
onStart: (result) => {
|
|
63
65
|
printInfo(portalDevText('messages.starting', { portal: result.portal }, `Starting portal "${result.portal}"...`));
|
|
64
66
|
printInfo(portalDevText('messages.mode', { mode: result.mode }, `Mode: ${result.mode}`));
|
|
@@ -21,7 +21,7 @@ function formatBoolean(value) {
|
|
|
21
21
|
return value ? 'yes' : 'no';
|
|
22
22
|
}
|
|
23
23
|
export default class PortalList extends Command {
|
|
24
|
-
static summary = 'List portal records and
|
|
24
|
+
static summary = 'List portal records and development paths';
|
|
25
25
|
static examples = [
|
|
26
26
|
'<%= config.bin %> <%= command.id %>',
|
|
27
27
|
'<%= config.bin %> <%= command.id %> --env dev --yes',
|
|
@@ -82,17 +82,17 @@ export default class PortalList extends Command {
|
|
|
82
82
|
portalListText('table.url', undefined, 'URL'),
|
|
83
83
|
portalListText('table.portalType', undefined, 'Portal type'),
|
|
84
84
|
portalListText('table.sourceStorage', undefined, 'Source storage'),
|
|
85
|
-
portalListText('table.path', undefined, '
|
|
85
|
+
portalListText('table.path', undefined, 'Development path'),
|
|
86
86
|
portalListText('table.enabled', undefined, 'Enabled'),
|
|
87
|
-
portalListText('table.
|
|
87
|
+
portalListText('table.default', undefined, 'Default'),
|
|
88
88
|
], outputItems.map((item) => [
|
|
89
89
|
item.name,
|
|
90
90
|
item.url,
|
|
91
91
|
item.portalType,
|
|
92
92
|
item.sourceStorage,
|
|
93
|
-
item.
|
|
93
|
+
item.developmentPath,
|
|
94
94
|
formatBoolean(item.enabled),
|
|
95
|
-
formatBoolean(item.
|
|
95
|
+
formatBoolean(item.isDefault),
|
|
96
96
|
])));
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Args, Command, Flags } from '@oclif/core';
|
|
10
|
-
import { getCurrentEnvName, getEnv } from '../../lib/auth-store.js';
|
|
10
|
+
import { getCurrentEnvName, getEnv, setEnvPortalPath } from '../../lib/auth-store.js';
|
|
11
11
|
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
12
|
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
13
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
14
14
|
import { pullPortalSource } from '../../lib/portal-source.js';
|
|
15
|
-
import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
15
|
+
import { printInfo, printSuccess, printWarning } from '../../lib/ui.js';
|
|
16
16
|
const portalPullText = (key, values, fallback) => translateCli(`commands.portalPull.${key}`, values, { fallback });
|
|
17
17
|
export default class PortalPull extends Command {
|
|
18
18
|
static summary = 'Pull portal source into local files';
|
|
19
19
|
static examples = [
|
|
20
20
|
'<%= config.bin %> <%= command.id %> customer',
|
|
21
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',
|
|
22
24
|
'<%= config.bin %> <%= command.id %> customer --force',
|
|
23
25
|
'<%= config.bin %> <%= command.id %> customer --no-install',
|
|
24
26
|
];
|
|
@@ -42,6 +44,18 @@ export default class PortalPull extends Command {
|
|
|
42
44
|
description: 'Delete the existing local files and pull them again',
|
|
43
45
|
default: false,
|
|
44
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
|
+
}),
|
|
45
59
|
install: Flags.boolean({
|
|
46
60
|
description: 'Run pnpm install after pulling the portal source',
|
|
47
61
|
default: true,
|
|
@@ -50,6 +64,12 @@ export default class PortalPull extends Command {
|
|
|
50
64
|
};
|
|
51
65
|
async run() {
|
|
52
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
|
+
}
|
|
53
73
|
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
54
74
|
const confirmed = await ensureCrossEnvConfirmed({
|
|
55
75
|
command: this,
|
|
@@ -74,11 +94,20 @@ export default class PortalPull extends Command {
|
|
|
74
94
|
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
75
95
|
force: flags.force,
|
|
76
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'],
|
|
77
102
|
});
|
|
78
103
|
if (!result.changed) {
|
|
79
104
|
printInfo(result.noopReason ?? portalPullText('messages.noop', undefined, 'No pull is needed.'));
|
|
80
105
|
return;
|
|
81
106
|
}
|
|
82
|
-
|
|
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
|
+
}
|
|
83
112
|
}
|
|
84
113
|
}
|
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) {
|
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) {
|
|
@@ -521,6 +524,53 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
|
|
|
521
524
|
};
|
|
522
525
|
await saveAuthConfig(config, options);
|
|
523
526
|
}
|
|
527
|
+
export function resolveEnvPortalPath(config, portal) {
|
|
528
|
+
const portalName = normalizeOptionalString(portal);
|
|
529
|
+
if (!portalName) {
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
532
|
+
return normalizeOptionalString(config?.portals?.[portalName]?.path);
|
|
533
|
+
}
|
|
534
|
+
export async function setEnvPortalPath(envName, portal, portalPath, options = {}) {
|
|
535
|
+
const portalName = normalizeOptionalString(portal);
|
|
536
|
+
const normalizedPath = normalizeOptionalString(portalPath);
|
|
537
|
+
if (!portalName || !normalizedPath) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
await writeEnv(envName, (previous) => {
|
|
541
|
+
if (!previous) {
|
|
542
|
+
throw new Error(`Env "${envName}" is not configured`);
|
|
543
|
+
}
|
|
544
|
+
const portals = normalizeEnvPortalsConfig(previous.portals) ?? {};
|
|
545
|
+
return {
|
|
546
|
+
...previous,
|
|
547
|
+
portals: {
|
|
548
|
+
...portals,
|
|
549
|
+
[portalName]: {
|
|
550
|
+
...(portals[portalName] ?? {}),
|
|
551
|
+
path: normalizedPath,
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
}, options);
|
|
556
|
+
}
|
|
557
|
+
export async function unsetEnvPortalPath(envName, portal, options = {}) {
|
|
558
|
+
const portalName = normalizeOptionalString(portal);
|
|
559
|
+
if (!portalName) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
await writeEnv(envName, (previous) => {
|
|
563
|
+
if (!previous) {
|
|
564
|
+
throw new Error(`Env "${envName}" is not configured`);
|
|
565
|
+
}
|
|
566
|
+
const portals = normalizeEnvPortalsConfig(previous.portals) ?? {};
|
|
567
|
+
delete portals[portalName];
|
|
568
|
+
return {
|
|
569
|
+
...previous,
|
|
570
|
+
...(Object.keys(portals).length > 0 ? { portals } : { portals: undefined }),
|
|
571
|
+
};
|
|
572
|
+
}, options);
|
|
573
|
+
}
|
|
524
574
|
export function resolveEnvProxyEntry(config, provider) {
|
|
525
575
|
const proxy = normalizeEnvProxyConfig(config?.proxy);
|
|
526
576
|
const resolved = {
|
package/dist/lib/env-auth.js
CHANGED
|
@@ -973,7 +973,7 @@ export async function resolveServerRequestTarget(options) {
|
|
|
973
973
|
: `Use --api-base-url or run \`nb init --ui --env ${envName}\` first.`,
|
|
974
974
|
].join('\n'));
|
|
975
975
|
}
|
|
976
|
-
return { baseUrl, token };
|
|
976
|
+
return { baseUrl, token, envName };
|
|
977
977
|
}
|
|
978
978
|
export async function authenticateEnvWithBasic(options) {
|
|
979
979
|
const envName = options.envName ?? (await getCurrentEnvName({ scope: options.scope }));
|
package/dist/lib/env-config.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { normalizeEnvProxyConfig } from './env-proxy-config.js';
|
|
10
10
|
import { resolveAppPublicPath } from './app-public-path.js';
|
|
11
|
+
import { normalizeEnvPortalsConfig } from './env-portal-config.js';
|
|
11
12
|
const STRING_ENV_CONFIG_KEYS = [
|
|
12
13
|
'source',
|
|
13
14
|
'downloadVersion',
|
|
@@ -118,5 +119,9 @@ export function buildStoredEnvConfig(input) {
|
|
|
118
119
|
if (proxy) {
|
|
119
120
|
envConfig.proxy = proxy;
|
|
120
121
|
}
|
|
122
|
+
const portals = normalizeEnvPortalsConfig(input.portals);
|
|
123
|
+
if (portals) {
|
|
124
|
+
envConfig.portals = portals;
|
|
125
|
+
}
|
|
121
126
|
return envConfig;
|
|
122
127
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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 trimValue(value) {
|
|
10
|
+
return String(value ?? '').trim();
|
|
11
|
+
}
|
|
12
|
+
function readRecord(value) {
|
|
13
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
14
|
+
}
|
|
15
|
+
export function normalizeEnvPortalsConfig(value) {
|
|
16
|
+
const input = readRecord(value);
|
|
17
|
+
const portals = {};
|
|
18
|
+
for (const [portal, rawEntry] of Object.entries(input)) {
|
|
19
|
+
const portalName = trimValue(portal);
|
|
20
|
+
if (!portalName) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const entry = readRecord(rawEntry);
|
|
24
|
+
const portalPath = trimValue(entry.path);
|
|
25
|
+
if (portalPath) {
|
|
26
|
+
portals[portalName] = { path: portalPath };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Object.keys(portals).length > 0 ? portals : undefined;
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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 { readFile, writeFile } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
const BUILD_HTML_SCRIPT_PATH = path.join('scripts', 'build-html.mjs');
|
|
12
|
+
const BUILD_HTML_ENV_FILES_PATTERN = /return\s+\[\s*["']\.env["']\s*,\s*["']\.env\.local["']\s*,\s*`\.env\.\$\{mode\}`\s*,\s*`\.env\.\$\{mode\}\.local`\s*\]\.map\(\s*\(?file\)?\s*=>\s*path\.join\(rootDir,\s*file\)\s*\);/m;
|
|
13
|
+
const BUILD_HTML_ENV_ONLY_REPLACEMENT = 'return [".env"].map((file) => path.join(rootDir, file));';
|
|
14
|
+
export async function ensurePortalBuildHtmlReadsEnvOnly(portalDir) {
|
|
15
|
+
const scriptPath = path.join(portalDir, BUILD_HTML_SCRIPT_PATH);
|
|
16
|
+
let content;
|
|
17
|
+
try {
|
|
18
|
+
content = await readFile(scriptPath, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const nextContent = content.replace(BUILD_HTML_ENV_FILES_PATTERN, BUILD_HTML_ENV_ONLY_REPLACEMENT);
|
|
24
|
+
if (nextContent !== content) {
|
|
25
|
+
await writeFile(scriptPath, nextContent, 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
}
|