@nocobase/cli 2.2.0-alpha.1 → 2.2.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +1 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +4 -1
- package/bin/early-locale.js +89 -0
- package/bin/node-version.js +35 -0
- package/bin/run.js +9 -0
- package/bin/windows-admin.js +60 -0
- package/dist/commands/app/destroy.js +4 -3
- package/dist/commands/app/restart.js +38 -0
- package/dist/commands/app/shared.js +49 -3
- package/dist/commands/app/start.js +95 -0
- package/dist/commands/app/upgrade.js +11 -0
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/examples/prompts-stages.js +2 -2
- package/dist/commands/examples/prompts-test.js +2 -2
- package/dist/commands/init.js +152 -14
- package/dist/commands/install.js +256 -109
- package/dist/commands/license/activate.js +4 -1
- package/dist/commands/license/shared.js +24 -15
- package/dist/commands/portal/config.js +88 -0
- package/dist/commands/portal/create.js +104 -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/proxy/caddy/generate.js +93 -7
- package/dist/commands/proxy/nginx/generate.js +98 -7
- package/dist/commands/revision/create.js +1 -1
- package/dist/commands/self/check.js +1 -1
- package/dist/commands/self/update.js +4 -4
- package/dist/commands/skills/check.js +4 -5
- package/dist/commands/skills/install.js +18 -1
- package/dist/commands/skills/update.js +19 -4
- package/dist/commands/source/dev.js +10 -6
- package/dist/commands/source/download.js +85 -16
- package/dist/lib/api-command-compat.js +51 -8
- package/dist/lib/app-managed-resources.js +104 -5
- package/dist/lib/auth-store.js +105 -13
- package/dist/lib/cli-config.js +93 -2
- package/dist/lib/docker-image.js +94 -6
- package/dist/lib/env-auth.js +291 -45
- package/dist/lib/env-config.js +14 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +276 -61
- package/dist/lib/hook-script.js +160 -0
- package/dist/lib/managed-init-env.js +6 -1
- 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 +283 -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/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-validators.js +1 -1
- package/dist/lib/prompt-web-ui.js +20 -13
- package/dist/lib/proxy-caddy.js +77 -9
- package/dist/lib/proxy-nginx.js +71 -11
- package/dist/lib/run-npm.js +21 -16
- package/dist/lib/self-manager.js +254 -46
- package/dist/lib/skills-manager.js +116 -23
- package/dist/lib/source-publish.js +2 -2
- package/dist/lib/startup-update.js +1 -1
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +227 -43
- package/dist/locale/zh-CN.json +227 -43
- package/package.json +11 -2
- package/scripts/build.mjs +0 -34
- package/scripts/clean.mjs +0 -9
- package/tsconfig.json +0 -19
|
@@ -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
|
+
}
|
|
@@ -8,20 +8,51 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
|
|
11
|
-
import {
|
|
11
|
+
import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
|
|
12
|
+
import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
|
|
13
|
+
import { getCaddyProxyDriver, writeManualCaddyProxyBundle, writeCaddyProxyBundle, resolveCaddyProxyRuntimeContext, } from '../../../lib/proxy-caddy.js';
|
|
12
14
|
import { normalizeProxyListenPort } from '../../../lib/proxy-nginx.js';
|
|
13
15
|
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
|
|
14
16
|
export default class ProxyCaddyGenerate extends Command {
|
|
15
17
|
static summary = 'Generate caddy proxy files for one managed env';
|
|
16
18
|
static examples = [
|
|
19
|
+
'<%= config.bin %> proxy caddy generate --host app1.example.com',
|
|
17
20
|
'<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com',
|
|
18
21
|
'<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com --port 8080',
|
|
22
|
+
'<%= config.bin %> proxy caddy generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
|
|
19
23
|
];
|
|
20
24
|
static flags = {
|
|
21
25
|
env: Flags.string({
|
|
22
26
|
char: 'e',
|
|
23
|
-
description: 'CLI env name to generate proxy files for',
|
|
24
|
-
|
|
27
|
+
description: 'CLI env name to generate proxy files for. Defaults to the current env when omitted',
|
|
28
|
+
}),
|
|
29
|
+
manual: Flags.boolean({
|
|
30
|
+
description: 'Generate proxy files from explicit runtime flags instead of a saved env',
|
|
31
|
+
default: false,
|
|
32
|
+
}),
|
|
33
|
+
name: Flags.string({
|
|
34
|
+
description: 'Output bundle name used under .nocobase/proxy/caddy in manual mode',
|
|
35
|
+
}),
|
|
36
|
+
'storage-path': Flags.string({
|
|
37
|
+
description: 'Path to the NocoBase storage directory in manual mode',
|
|
38
|
+
}),
|
|
39
|
+
'dist-root-path': Flags.string({
|
|
40
|
+
description: 'Path to the dist-client root directory used to generate index-v1.html and index-v2.html in manual mode',
|
|
41
|
+
}),
|
|
42
|
+
'runtime-version': Flags.string({
|
|
43
|
+
description: 'Frontend runtime version under dist-root-path in manual mode',
|
|
44
|
+
}),
|
|
45
|
+
'app-public-path': Flags.string({
|
|
46
|
+
description: 'Public base path served by the proxied app in manual mode. Defaults to /',
|
|
47
|
+
}),
|
|
48
|
+
'upstream-host': Flags.string({
|
|
49
|
+
description: 'Upstream host used by caddy reverse_proxy in manual mode',
|
|
50
|
+
}),
|
|
51
|
+
'upstream-port': Flags.string({
|
|
52
|
+
description: 'Upstream port used by caddy reverse_proxy in manual mode',
|
|
53
|
+
}),
|
|
54
|
+
'cdn-base-url': Flags.string({
|
|
55
|
+
description: 'Client asset CDN base URL used when generating runtime HTML',
|
|
25
56
|
}),
|
|
26
57
|
host: Flags.string({
|
|
27
58
|
description: 'Host exposed by the caddy site block, such as example.com or localhost',
|
|
@@ -35,9 +66,56 @@ export default class ProxyCaddyGenerate extends Command {
|
|
|
35
66
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
36
67
|
const requestedPort = flags.port?.trim() || undefined;
|
|
37
68
|
const normalizedPort = normalizeProxyListenPort(requestedPort);
|
|
69
|
+
const manual = Boolean(flags.manual);
|
|
38
70
|
if (requestedPort && !normalizedPort) {
|
|
39
71
|
this.error(`Invalid proxy entry port "${requestedPort}". Use an integer between 1 and 65535.`);
|
|
40
72
|
}
|
|
73
|
+
if (manual && requestedEnv) {
|
|
74
|
+
this.error('`--manual` cannot be combined with `--env`.');
|
|
75
|
+
}
|
|
76
|
+
if (manual) {
|
|
77
|
+
const name = flags.name?.trim() || undefined;
|
|
78
|
+
const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
|
|
79
|
+
const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
|
|
80
|
+
const storagePath = flags['storage-path']?.trim() || undefined;
|
|
81
|
+
const distRootPath = flags['dist-root-path']?.trim() || undefined;
|
|
82
|
+
const runtimeVersion = flags['runtime-version']?.trim() || undefined;
|
|
83
|
+
if (requestedUpstreamPort && !upstreamPort) {
|
|
84
|
+
this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
|
|
85
|
+
}
|
|
86
|
+
if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
|
|
87
|
+
this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
|
|
88
|
+
}
|
|
89
|
+
const driver = await getCaddyProxyDriver();
|
|
90
|
+
const runtimeContext = await resolveCaddyProxyRuntimeContext();
|
|
91
|
+
announceTargetEnv(name);
|
|
92
|
+
startTask(`Generating caddy proxy config for env "${name}" with the ${driver} driver...`);
|
|
93
|
+
try {
|
|
94
|
+
const { bundle, status } = await writeManualCaddyProxyBundle({
|
|
95
|
+
name,
|
|
96
|
+
storagePath,
|
|
97
|
+
distRootPath,
|
|
98
|
+
runtimeVersion,
|
|
99
|
+
appPublicPath: flags['app-public-path']?.trim() || undefined,
|
|
100
|
+
upstreamHost: flags['upstream-host']?.trim() || undefined,
|
|
101
|
+
upstreamPort,
|
|
102
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
103
|
+
}, {
|
|
104
|
+
host: flags.host?.trim() || undefined,
|
|
105
|
+
port: normalizedPort,
|
|
106
|
+
}, runtimeContext, {
|
|
107
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
108
|
+
});
|
|
109
|
+
succeedTask(status === 'created'
|
|
110
|
+
? `Saved caddy proxy files for env "${name}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
|
|
111
|
+
: `Saved caddy proxy files for env "${name}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
failTask(`Failed to generate caddy proxy config for env "${name}".`);
|
|
115
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
41
119
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
42
120
|
if (!runtime) {
|
|
43
121
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
@@ -53,10 +131,18 @@ export default class ProxyCaddyGenerate extends Command {
|
|
|
53
131
|
announceTargetEnv(runtime.envName);
|
|
54
132
|
startTask(`Generating caddy proxy config for env "${runtime.envName}" with the ${driver} driver...`);
|
|
55
133
|
try {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
134
|
+
const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'caddy');
|
|
135
|
+
const appEntryOptions = {
|
|
136
|
+
host: flags.host?.trim() || savedAppEntryOptions?.host,
|
|
137
|
+
port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
|
|
138
|
+
};
|
|
139
|
+
const { bundle, status } = await writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext, {
|
|
140
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
141
|
+
});
|
|
142
|
+
await setEnvProxyEntry(runtime.envName, 'caddy', {
|
|
143
|
+
host: appEntryOptions.host,
|
|
144
|
+
port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
|
|
145
|
+
}, { scope: resolveDefaultConfigScope() });
|
|
60
146
|
succeedTask(status === 'created'
|
|
61
147
|
? `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
|
|
62
148
|
: `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
|
|
@@ -8,19 +8,54 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
|
|
11
|
-
import {
|
|
11
|
+
import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
|
|
12
|
+
import { getNginxProxyDriver, normalizeProxyListenPort, resolveNginxProxyRuntimeContext, writeManualNginxProxyBundle, writeNginxProxyBundle, } from '../../../lib/proxy-nginx.js';
|
|
13
|
+
import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
|
|
12
14
|
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
|
|
13
15
|
export default class ProxyNginxGenerate extends Command {
|
|
14
16
|
static summary = 'Generate nginx proxy files for one managed env';
|
|
15
17
|
static examples = [
|
|
18
|
+
'<%= config.bin %> proxy nginx generate --host app1.example.com',
|
|
16
19
|
'<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com',
|
|
17
20
|
'<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com --port 8080',
|
|
21
|
+
'<%= config.bin %> proxy nginx generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
|
|
18
22
|
];
|
|
19
23
|
static flags = {
|
|
20
24
|
env: Flags.string({
|
|
21
25
|
char: 'e',
|
|
22
|
-
description: 'CLI env name to generate proxy files for',
|
|
23
|
-
|
|
26
|
+
description: 'CLI env name to generate proxy files for. Defaults to the current env when omitted',
|
|
27
|
+
}),
|
|
28
|
+
manual: Flags.boolean({
|
|
29
|
+
description: 'Generate proxy files from explicit runtime flags instead of a saved env',
|
|
30
|
+
default: false,
|
|
31
|
+
}),
|
|
32
|
+
name: Flags.string({
|
|
33
|
+
description: 'Output bundle name used under .nocobase/proxy/nginx in manual mode',
|
|
34
|
+
}),
|
|
35
|
+
'storage-path': Flags.string({
|
|
36
|
+
description: 'Path to the NocoBase storage directory in manual mode',
|
|
37
|
+
}),
|
|
38
|
+
'dist-root-path': Flags.string({
|
|
39
|
+
description: 'Path to the dist-client root directory used to generate index-v1.html and index-v2.html in manual mode',
|
|
40
|
+
}),
|
|
41
|
+
'runtime-version': Flags.string({
|
|
42
|
+
description: 'Frontend runtime version under dist-root-path in manual mode',
|
|
43
|
+
}),
|
|
44
|
+
'app-public-path': Flags.string({
|
|
45
|
+
description: 'Public base path served by the proxied app in manual mode. Defaults to /',
|
|
46
|
+
}),
|
|
47
|
+
'upstream-host': Flags.string({
|
|
48
|
+
description: 'Upstream host used by nginx proxy_pass in manual mode',
|
|
49
|
+
}),
|
|
50
|
+
'upstream-port': Flags.string({
|
|
51
|
+
description: 'Upstream port used by nginx proxy_pass in manual mode',
|
|
52
|
+
}),
|
|
53
|
+
'cdn-base-url': Flags.string({
|
|
54
|
+
description: 'Client asset CDN base URL used when generating runtime HTML',
|
|
55
|
+
}),
|
|
56
|
+
force: Flags.boolean({
|
|
57
|
+
description: 'Overwrite existing app.conf even when the managed block is missing',
|
|
58
|
+
default: false,
|
|
24
59
|
}),
|
|
25
60
|
host: Flags.string({
|
|
26
61
|
description: 'Host exposed by the nginx entry config, such as example.com or localhost',
|
|
@@ -34,9 +69,56 @@ export default class ProxyNginxGenerate extends Command {
|
|
|
34
69
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
35
70
|
const requestedPort = flags.port?.trim() || undefined;
|
|
36
71
|
const normalizedPort = normalizeProxyListenPort(requestedPort);
|
|
72
|
+
const manual = Boolean(flags.manual);
|
|
37
73
|
if (requestedPort && !normalizedPort) {
|
|
38
74
|
this.error(`Invalid proxy entry port "${requestedPort}". Use an integer between 1 and 65535.`);
|
|
39
75
|
}
|
|
76
|
+
if (manual && requestedEnv) {
|
|
77
|
+
this.error('`--manual` cannot be combined with `--env`.');
|
|
78
|
+
}
|
|
79
|
+
if (manual) {
|
|
80
|
+
const name = flags.name?.trim() || undefined;
|
|
81
|
+
const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
|
|
82
|
+
const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
|
|
83
|
+
const storagePath = flags['storage-path']?.trim() || undefined;
|
|
84
|
+
const distRootPath = flags['dist-root-path']?.trim() || undefined;
|
|
85
|
+
const runtimeVersion = flags['runtime-version']?.trim() || undefined;
|
|
86
|
+
if (requestedUpstreamPort && !upstreamPort) {
|
|
87
|
+
this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
|
|
88
|
+
}
|
|
89
|
+
if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
|
|
90
|
+
this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
|
|
91
|
+
}
|
|
92
|
+
const driver = await getNginxProxyDriver();
|
|
93
|
+
const runtimeContext = await resolveNginxProxyRuntimeContext();
|
|
94
|
+
announceTargetEnv(name);
|
|
95
|
+
startTask(`Generating nginx proxy config for env "${name}" with the ${driver} driver...`);
|
|
96
|
+
try {
|
|
97
|
+
const { bundle, status } = await writeManualNginxProxyBundle({
|
|
98
|
+
name,
|
|
99
|
+
storagePath,
|
|
100
|
+
distRootPath,
|
|
101
|
+
runtimeVersion,
|
|
102
|
+
appPublicPath: flags['app-public-path']?.trim() || undefined,
|
|
103
|
+
upstreamHost: flags['upstream-host']?.trim() || undefined,
|
|
104
|
+
upstreamPort,
|
|
105
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
106
|
+
}, {
|
|
107
|
+
host: flags.host?.trim() || undefined,
|
|
108
|
+
port: normalizedPort,
|
|
109
|
+
}, runtimeContext, {
|
|
110
|
+
force: flags.force,
|
|
111
|
+
});
|
|
112
|
+
succeedTask(status === 'created'
|
|
113
|
+
? `Saved nginx proxy files for env "${name}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
|
|
114
|
+
: `Saved nginx proxy files for env "${name}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
failTask(`Failed to generate nginx proxy config for env "${name}".`);
|
|
118
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
40
122
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
41
123
|
if (!runtime) {
|
|
42
124
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
@@ -52,10 +134,19 @@ export default class ProxyNginxGenerate extends Command {
|
|
|
52
134
|
announceTargetEnv(runtime.envName);
|
|
53
135
|
startTask(`Generating nginx proxy config for env "${runtime.envName}" with the ${driver} driver...`);
|
|
54
136
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
137
|
+
const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'nginx');
|
|
138
|
+
const appEntryOptions = {
|
|
139
|
+
host: flags.host?.trim() || savedAppEntryOptions?.host,
|
|
140
|
+
port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
|
|
141
|
+
};
|
|
142
|
+
const { bundle, status } = await writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext, {
|
|
143
|
+
cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
|
|
144
|
+
force: flags.force,
|
|
145
|
+
});
|
|
146
|
+
await setEnvProxyEntry(runtime.envName, 'nginx', {
|
|
147
|
+
host: appEntryOptions.host,
|
|
148
|
+
port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
|
|
149
|
+
}, { scope: resolveDefaultConfigScope() });
|
|
59
150
|
succeedTask(status === 'created'
|
|
60
151
|
? `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
|
|
61
152
|
: `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
|
|
@@ -20,7 +20,7 @@ export default class SelfCheck extends Command {
|
|
|
20
20
|
static flags = {
|
|
21
21
|
channel: Flags.string({
|
|
22
22
|
description: 'Release channel to compare against. Defaults to the current CLI channel.',
|
|
23
|
-
options: ['auto', 'latest', 'beta', 'alpha'],
|
|
23
|
+
options: ['auto', 'latest', 'test', 'beta', 'alpha'],
|
|
24
24
|
default: 'auto',
|
|
25
25
|
}),
|
|
26
26
|
json: Flags.boolean({
|
|
@@ -26,17 +26,17 @@ function formatSkillsUpdateMessage(result, verbose) {
|
|
|
26
26
|
}
|
|
27
27
|
export default class SelfUpdate extends Command {
|
|
28
28
|
static summary = 'Update the globally installed NocoBase CLI';
|
|
29
|
-
static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm install.';
|
|
29
|
+
static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm, pnpm, or yarn install.';
|
|
30
30
|
static examples = [
|
|
31
31
|
'<%= config.bin %> <%= command.id %>',
|
|
32
32
|
'<%= config.bin %> <%= command.id %> --yes',
|
|
33
33
|
'<%= config.bin %> <%= command.id %> --skills',
|
|
34
|
-
'<%= config.bin %> <%= command.id %> --channel
|
|
34
|
+
'<%= config.bin %> <%= command.id %> --channel test --json',
|
|
35
35
|
];
|
|
36
36
|
static flags = {
|
|
37
37
|
channel: Flags.string({
|
|
38
38
|
description: 'Release channel to update to. Defaults to the current CLI channel.',
|
|
39
|
-
options: ['auto', 'latest', 'beta', 'alpha'],
|
|
39
|
+
options: ['auto', 'latest', 'test', 'beta', 'alpha'],
|
|
40
40
|
default: 'auto',
|
|
41
41
|
}),
|
|
42
42
|
yes: Flags.boolean({
|
|
@@ -77,7 +77,7 @@ export default class SelfUpdate extends Command {
|
|
|
77
77
|
message: flags.skills
|
|
78
78
|
? `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion} and refresh the globally installed NocoBase AI coding skills?`
|
|
79
79
|
: `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
|
|
80
|
-
default:
|
|
80
|
+
default: true,
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
catch {
|
|
@@ -12,10 +12,7 @@ import { printInfo, renderTable } from '../../lib/ui.js';
|
|
|
12
12
|
export default class SkillsCheck extends Command {
|
|
13
13
|
static summary = 'Check the globally installed NocoBase AI coding skills';
|
|
14
14
|
static description = 'Inspect the global NocoBase AI coding skills and report whether they are managed by the CLI and whether an update is available.';
|
|
15
|
-
static examples = [
|
|
16
|
-
'<%= config.bin %> <%= command.id %>',
|
|
17
|
-
'<%= config.bin %> <%= command.id %> --json',
|
|
18
|
-
];
|
|
15
|
+
static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --json'];
|
|
19
16
|
static flags = {
|
|
20
17
|
json: Flags.boolean({
|
|
21
18
|
description: 'Output the result as JSON',
|
|
@@ -25,6 +22,7 @@ export default class SkillsCheck extends Command {
|
|
|
25
22
|
async run() {
|
|
26
23
|
const { flags } = await this.parse(SkillsCheck);
|
|
27
24
|
const status = await inspectSkillsStatus();
|
|
25
|
+
const displaySkillNames = status.packageSkillNames.length ? status.packageSkillNames : status.installedSkillNames;
|
|
28
26
|
if (flags.json) {
|
|
29
27
|
this.log(JSON.stringify({
|
|
30
28
|
ok: true,
|
|
@@ -35,6 +33,7 @@ export default class SkillsCheck extends Command {
|
|
|
35
33
|
managedByNb: status.managedByNb,
|
|
36
34
|
sourcePackage: status.sourcePackage,
|
|
37
35
|
npmPackageName: status.npmPackageName,
|
|
36
|
+
packageSkillNames: status.packageSkillNames,
|
|
38
37
|
installedSkillNames: status.installedSkillNames,
|
|
39
38
|
installedVersion: status.installedVersion,
|
|
40
39
|
latestVersion: status.latestVersion,
|
|
@@ -50,7 +49,7 @@ export default class SkillsCheck extends Command {
|
|
|
50
49
|
['Skills home', status.globalRoot],
|
|
51
50
|
['Installed', status.installed ? 'yes' : 'no'],
|
|
52
51
|
['Managed by nb', status.managedByNb ? 'yes' : 'no'],
|
|
53
|
-
['Installed skills',
|
|
52
|
+
['Installed skills', displaySkillNames.length ? displaySkillNames.join(', ') : '(none)'],
|
|
54
53
|
['Installed version', status.installedVersion ?? '(unknown)'],
|
|
55
54
|
['Latest version', status.latestVersion ?? '(unknown)'],
|
|
56
55
|
['Update available', status.updateAvailable === null ? 'unknown' : status.updateAvailable ? 'yes' : 'no'],
|