@nocobase/cli 2.3.0-alpha.1 → 3.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +4 -1
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/init.js +13 -5
- package/dist/commands/install.js +104 -3
- package/dist/commands/portal/config.js +88 -0
- 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 +84 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/source/dev.js +1 -1
- package/dist/lib/api-client.js +7 -0
- package/dist/lib/auth-store.js +3 -1
- package/dist/lib/cli-config.js +20 -1
- package/dist/lib/env-auth.js +2 -2
- package/dist/lib/env-config.js +3 -0
- package/dist/lib/env-proxy.js +141 -8
- package/dist/lib/managed-env-file.js +58 -2
- package/dist/lib/managed-init-env.js +6 -1
- package/dist/lib/naming.js +9 -0
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-config.js +133 -0
- package/dist/lib/portal-configure.js +117 -0
- package/dist/lib/portal-create.js +433 -0
- package/dist/lib/portal-deploy.js +298 -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 +211 -0
- package/dist/lib/portal-source.js +523 -0
- package/dist/lib/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/dist/lib/run-npm.js +17 -16
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +178 -0
- package/dist/locale/zh-CN.json +178 -0
- package/nocobase-ctl.config.json +111 -0
- package/package.json +5 -2
|
@@ -0,0 +1,104 @@
|
|
|
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 { confirm } from "../../lib/inquirer.js";
|
|
15
|
+
import { destroyPortalWorkspace } from '../../lib/portal-destroy.js';
|
|
16
|
+
import { isInteractiveTerminal, printInfo, printSuccess } from '../../lib/ui.js';
|
|
17
|
+
const portalDestroyText = (key, values, fallback) => translateCli(`commands.portalDestroy.${key}`, values, { fallback });
|
|
18
|
+
async function ensureDestroyConfirmed(options) {
|
|
19
|
+
if (options.yes) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (!isInteractiveTerminal()) {
|
|
23
|
+
options.command.error(portalDestroyText('errors.confirmationRequired', undefined, 'Refusing to destroy a portal in non-interactive mode without --yes.'));
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return Boolean(await confirm({
|
|
27
|
+
message: portalDestroyText('prompts.confirm', { portal: options.portal }, `Destroy portal "${options.portal}" and delete its storage directory?`),
|
|
28
|
+
default: false,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export default class PortalDestroy extends Command {
|
|
36
|
+
static summary = 'Destroy a portal record and local files';
|
|
37
|
+
static examples = [
|
|
38
|
+
'<%= config.bin %> <%= command.id %> customer --yes',
|
|
39
|
+
'<%= config.bin %> <%= command.id %> customer --env dev --yes',
|
|
40
|
+
'<%= config.bin %> <%= command.id %> customer --force --yes',
|
|
41
|
+
];
|
|
42
|
+
static args = {
|
|
43
|
+
portal: Args.string({
|
|
44
|
+
required: true,
|
|
45
|
+
description: 'Portal name',
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
static flags = {
|
|
49
|
+
env: Flags.string({
|
|
50
|
+
char: 'e',
|
|
51
|
+
description: 'CLI env name; omitted uses the current env',
|
|
52
|
+
}),
|
|
53
|
+
yes: Flags.boolean({
|
|
54
|
+
char: 'y',
|
|
55
|
+
description: 'Skip confirmation prompts',
|
|
56
|
+
default: false,
|
|
57
|
+
}),
|
|
58
|
+
force: Flags.boolean({
|
|
59
|
+
description: 'Ignore missing portal records or local files',
|
|
60
|
+
default: false,
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
async run() {
|
|
64
|
+
const { args, flags } = await this.parse(PortalDestroy);
|
|
65
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
66
|
+
const crossEnvConfirmed = await ensureCrossEnvConfirmed({
|
|
67
|
+
command: this,
|
|
68
|
+
requestedEnv,
|
|
69
|
+
yes: flags.yes,
|
|
70
|
+
});
|
|
71
|
+
if (!crossEnvConfirmed) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const destroyConfirmed = await ensureDestroyConfirmed({
|
|
75
|
+
command: this,
|
|
76
|
+
portal: args.portal,
|
|
77
|
+
yes: flags.yes,
|
|
78
|
+
});
|
|
79
|
+
if (!destroyConfirmed) {
|
|
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(portalDestroyText(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 destroyPortalWorkspace({
|
|
91
|
+
portal: args.portal,
|
|
92
|
+
env,
|
|
93
|
+
envName,
|
|
94
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
95
|
+
force: flags.force,
|
|
96
|
+
});
|
|
97
|
+
printSuccess(portalDestroyText('messages.destroyed', { portal: result.portal }, `Portal "${result.portal}" destroyed.`));
|
|
98
|
+
printInfo(portalDestroyText('messages.mode', { mode: result.mode }, `Mode: ${result.mode}`));
|
|
99
|
+
printInfo(portalDestroyText('messages.app', { app: result.app }, `App: ${result.app}`));
|
|
100
|
+
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
|
+
printInfo(portalDestroyText('messages.workspace', { dir: result.portalDir, status: result.workspaceDeleted ? 'deleted' : 'missing' }, `Portal files: ${result.workspaceDeleted ? 'deleted' : 'missing'} (${result.portalDir})`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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 { devPortalWorkspace } from '../../lib/portal-dev.js';
|
|
15
|
+
import { printInfo } from '../../lib/ui.js';
|
|
16
|
+
const portalDevText = (key, values, fallback) => translateCli(`commands.portalDev.${key}`, values, { fallback });
|
|
17
|
+
export default class PortalDev extends Command {
|
|
18
|
+
static summary = 'Start a portal in development mode';
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer --env dev --yes',
|
|
22
|
+
];
|
|
23
|
+
static args = {
|
|
24
|
+
portal: Args.string({
|
|
25
|
+
required: true,
|
|
26
|
+
description: 'Portal name',
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
static flags = {
|
|
30
|
+
env: Flags.string({
|
|
31
|
+
char: 'e',
|
|
32
|
+
description: 'CLI env name; omitted uses the current env',
|
|
33
|
+
}),
|
|
34
|
+
yes: Flags.boolean({
|
|
35
|
+
char: 'y',
|
|
36
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
37
|
+
default: false,
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
async run() {
|
|
41
|
+
const { args, flags } = await this.parse(PortalDev);
|
|
42
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
43
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
44
|
+
command: this,
|
|
45
|
+
requestedEnv,
|
|
46
|
+
yes: flags.yes,
|
|
47
|
+
});
|
|
48
|
+
if (!confirmed) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const scope = resolveDefaultConfigScope();
|
|
52
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
53
|
+
const env = await getEnv(envName, { scope });
|
|
54
|
+
if (!env) {
|
|
55
|
+
this.error(portalDevText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
56
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
57
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
58
|
+
}
|
|
59
|
+
await devPortalWorkspace({
|
|
60
|
+
portal: args.portal,
|
|
61
|
+
env,
|
|
62
|
+
onStart: (result) => {
|
|
63
|
+
printInfo(portalDevText('messages.starting', { portal: result.portal }, `Starting portal "${result.portal}"...`));
|
|
64
|
+
printInfo(portalDevText('messages.mode', { mode: result.mode }, `Mode: ${result.mode}`));
|
|
65
|
+
printInfo(portalDevText('messages.app', { app: result.app }, `App: ${result.app}`));
|
|
66
|
+
printInfo(portalDevText('messages.base', { base: result.portalBase }, `Base: ${result.portalBase}`));
|
|
67
|
+
printInfo(portalDevText('messages.dir', { dir: result.portalDir }, `Dir: ${result.portalDir}`));
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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, loadHelpClass } from '@oclif/core';
|
|
10
|
+
export default class Portal extends Command {
|
|
11
|
+
static summary = 'Manage portals';
|
|
12
|
+
async run() {
|
|
13
|
+
await this.parse(Portal);
|
|
14
|
+
const Help = await loadHelpClass(this.config);
|
|
15
|
+
await new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions).showHelp([
|
|
16
|
+
this.id ?? 'portal',
|
|
17
|
+
...this.argv,
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
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 { findPortalListItem, formatPortalInfo } from '../../lib/portal-info.js';
|
|
15
|
+
import { listPortalWorkspaces, toPortalOutputItem } from '../../lib/portal-list.js';
|
|
16
|
+
const portalInfoText = (key, values, fallback) => translateCli(`commands.portalInfo.${key}`, values, { fallback });
|
|
17
|
+
export default class PortalInfo extends Command {
|
|
18
|
+
static summary = 'Show portal record and local file details';
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer --env dev --yes',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --json',
|
|
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
|
+
'json-output': Flags.boolean({
|
|
41
|
+
char: 'j',
|
|
42
|
+
aliases: ['json'],
|
|
43
|
+
description: 'Print portal details as JSON',
|
|
44
|
+
default: false,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
async run() {
|
|
48
|
+
const { args, flags } = await this.parse(PortalInfo);
|
|
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(portalInfoText(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 portal = findPortalListItem(result.items, args.portal);
|
|
72
|
+
if (!portal) {
|
|
73
|
+
this.error(portalInfoText('errors.notFound', { portal: args.portal }, `Portal "${args.portal}" was not found.`));
|
|
74
|
+
}
|
|
75
|
+
const outputItem = toPortalOutputItem(portal);
|
|
76
|
+
if (flags['json-output']) {
|
|
77
|
+
this.log(JSON.stringify(outputItem, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.log(formatPortalInfo(portal));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -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 local sync status';
|
|
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, 'Local path'),
|
|
86
|
+
portalListText('table.enabled', undefined, 'Enabled'),
|
|
87
|
+
portalListText('table.localSynced', undefined, 'Local synced'),
|
|
88
|
+
], outputItems.map((item) => [
|
|
89
|
+
item.name,
|
|
90
|
+
item.url,
|
|
91
|
+
item.portalType,
|
|
92
|
+
item.sourceStorage,
|
|
93
|
+
item.localPath,
|
|
94
|
+
formatBoolean(item.enabled),
|
|
95
|
+
formatBoolean(item.localSynced),
|
|
96
|
+
])));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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 { pullPortalSource } from '../../lib/portal-source.js';
|
|
15
|
+
import { printInfo, printSuccess } 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 --force',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> customer --no-install',
|
|
24
|
+
];
|
|
25
|
+
static args = {
|
|
26
|
+
portal: Args.string({
|
|
27
|
+
required: true,
|
|
28
|
+
description: 'Portal name',
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
static flags = {
|
|
32
|
+
env: Flags.string({
|
|
33
|
+
char: 'e',
|
|
34
|
+
description: 'CLI env name; omitted uses the current env',
|
|
35
|
+
}),
|
|
36
|
+
yes: Flags.boolean({
|
|
37
|
+
char: 'y',
|
|
38
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
39
|
+
default: false,
|
|
40
|
+
}),
|
|
41
|
+
force: Flags.boolean({
|
|
42
|
+
description: 'Delete the existing local files and pull them again',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
install: Flags.boolean({
|
|
46
|
+
description: 'Run pnpm install after pulling the portal source',
|
|
47
|
+
default: true,
|
|
48
|
+
allowNo: true,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
async run() {
|
|
52
|
+
const { args, flags } = await this.parse(PortalPull);
|
|
53
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
54
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
55
|
+
command: this,
|
|
56
|
+
requestedEnv,
|
|
57
|
+
yes: flags.yes,
|
|
58
|
+
});
|
|
59
|
+
if (!confirmed) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const scope = resolveDefaultConfigScope();
|
|
63
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
64
|
+
const env = await getEnv(envName, { scope });
|
|
65
|
+
if (!env) {
|
|
66
|
+
this.error(portalPullText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
67
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
68
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
69
|
+
}
|
|
70
|
+
const result = await pullPortalSource({
|
|
71
|
+
portal: args.portal,
|
|
72
|
+
env,
|
|
73
|
+
envName,
|
|
74
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
75
|
+
force: flags.force,
|
|
76
|
+
installDependencies: flags.install,
|
|
77
|
+
});
|
|
78
|
+
if (!result.changed) {
|
|
79
|
+
printInfo(result.noopReason ?? portalPullText('messages.noop', undefined, 'No pull is needed.'));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
printSuccess(portalPullText('messages.pulled', { portal: result.portal, portalDir: result.portalDir }, `Pulled portal source "${result.portal}" into ${result.portalDir}.`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -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
|
@@ -199,6 +199,13 @@ async function createMultipartBody(flags, operation) {
|
|
|
199
199
|
if (value === undefined) {
|
|
200
200
|
continue;
|
|
201
201
|
}
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
for (const item of value) {
|
|
204
|
+
formData.append(parameter.name, typeof item === 'object' ? JSON.stringify(item) : String(item));
|
|
205
|
+
}
|
|
206
|
+
hasValues = hasValues || value.length > 0;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
202
209
|
formData.append(parameter.name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
203
210
|
hasValues = true;
|
|
204
211
|
}
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -97,6 +97,7 @@ function normalizeAuthConfig(config) {
|
|
|
97
97
|
const locale = normalizeOptionalCliLocale(settings.locale);
|
|
98
98
|
const defaultUiHost = normalizeOptionalString(settings.init?.defaultUiHost);
|
|
99
99
|
const defaultApiHost = normalizeOptionalString(settings.init?.defaultApiHost);
|
|
100
|
+
const defaultPortalTemplate = normalizeOptionalString(settings.init?.defaultPortalTemplate);
|
|
100
101
|
const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
|
|
101
102
|
const logRetentionDays = typeof settings.log?.retentionDays === 'number' && Number.isInteger(settings.log.retentionDays)
|
|
102
103
|
? settings.log.retentionDays
|
|
@@ -117,11 +118,12 @@ function normalizeAuthConfig(config) {
|
|
|
117
118
|
name: config.name || config.dockerResourcePrefix,
|
|
118
119
|
settings: {
|
|
119
120
|
...(locale ? { locale } : {}),
|
|
120
|
-
...(defaultUiHost || defaultApiHost
|
|
121
|
+
...(defaultUiHost || defaultApiHost || defaultPortalTemplate
|
|
121
122
|
? {
|
|
122
123
|
init: {
|
|
123
124
|
...(defaultUiHost ? { defaultUiHost } : {}),
|
|
124
125
|
...(defaultApiHost ? { defaultApiHost } : {}),
|
|
126
|
+
...(defaultPortalTemplate ? { defaultPortalTemplate } : {}),
|
|
125
127
|
},
|
|
126
128
|
}
|
|
127
129
|
: {}),
|
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;
|
package/dist/lib/env-auth.js
CHANGED
|
@@ -32,12 +32,12 @@ function buildDeviceVerificationPathFromApiBaseUrl(apiBaseUrl) {
|
|
|
32
32
|
const subappMatch = url.pathname.match(/^(.*)\/api\/__app\/([^/]+)\/?$/);
|
|
33
33
|
if (subappMatch) {
|
|
34
34
|
const publicPath = (subappMatch[1] || '').replace(/\/+$/, '');
|
|
35
|
-
return `${publicPath}/apps/${subappMatch[2]}/idpOAuth/device`;
|
|
35
|
+
return `${publicPath}/settings/apps/${subappMatch[2]}/idpOAuth/device`;
|
|
36
36
|
}
|
|
37
37
|
const appMatch = url.pathname.match(/^(.*)\/api\/?$/);
|
|
38
38
|
if (appMatch) {
|
|
39
39
|
const publicPath = (appMatch[1] || '').replace(/\/+$/, '');
|
|
40
|
-
return `${publicPath}/idpOAuth/device`;
|
|
40
|
+
return `${publicPath}/settings/idpOAuth/device`;
|
|
41
41
|
}
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|