@nocobase/cli 2.2.0-alpha.1 → 2.2.0-alpha.10
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 +266 -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
|
@@ -143,11 +143,14 @@ export default class LicenseActivate extends Command {
|
|
|
143
143
|
const validation = await validateLicenseKey(runtime, resolvedKey);
|
|
144
144
|
const ok = !validation.keyStatus && validation.envMatch && validation.domainMatch && validation.licenseStatus === 'active';
|
|
145
145
|
const licenseKeyPath = ok ? await saveLicenseKey(runtime, resolvedKey) : resolveLicenseKeyFile(runtime);
|
|
146
|
+
const shouldResolveInstanceId = Boolean(interactiveKeyFlowInstanceId || ok || (flags.json && !validation.keyStatus));
|
|
146
147
|
const payload = {
|
|
147
148
|
ok,
|
|
148
149
|
env: runtime.envName,
|
|
149
150
|
kind: runtime.kind,
|
|
150
|
-
instanceId:
|
|
151
|
+
instanceId: shouldResolveInstanceId
|
|
152
|
+
? interactiveKeyFlowInstanceId ?? (await ensureInstanceId(runtime))
|
|
153
|
+
: undefined,
|
|
151
154
|
mode: 'key',
|
|
152
155
|
key: redactLicenseKey(resolvedKey),
|
|
153
156
|
keyFile: keyFile || undefined,
|
|
@@ -10,8 +10,8 @@ import { Flags } from '@oclif/core';
|
|
|
10
10
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { getEnvAsync, getInstanceIdAsync, keyDecrypt } from '@nocobase/license-kit';
|
|
13
|
-
import { checkExternalDbConnection, readExternalDbConnectionConfig
|
|
14
|
-
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef
|
|
13
|
+
import { checkExternalDbConnection, readExternalDbConnectionConfig } from "../../lib/db-connection-check.js";
|
|
14
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../../lib/docker-image.js";
|
|
15
15
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
|
|
16
16
|
import { buildRuntimeEnvVars } from '../../lib/runtime-env-vars.js';
|
|
17
17
|
import { resolveLicensePkgUrlFromConfig } from '../../lib/cli-config.js';
|
|
@@ -102,12 +102,7 @@ function buildDockerLicenseDbFlagArgs(envVars) {
|
|
|
102
102
|
];
|
|
103
103
|
}
|
|
104
104
|
async function runDockerLicenseJsonCommand(runtime, commandArgs) {
|
|
105
|
-
const args = [
|
|
106
|
-
'run',
|
|
107
|
-
'--rm',
|
|
108
|
-
'--network',
|
|
109
|
-
runtime.dockerNetworkName || runtime.workspaceName,
|
|
110
|
-
];
|
|
105
|
+
const args = ['run', '--rm', '--network', runtime.dockerNetworkName || runtime.workspaceName];
|
|
111
106
|
const dockerPlatform = normalizeDockerPlatform(runtime.env.config?.dockerPlatform);
|
|
112
107
|
if (dockerPlatform) {
|
|
113
108
|
args.push('--platform', dockerPlatform);
|
|
@@ -193,11 +188,11 @@ export async function generateValidatedInstanceIdFromEnvVars(envVars) {
|
|
|
193
188
|
}
|
|
194
189
|
async function generateInstanceIdForDockerRuntime(runtime) {
|
|
195
190
|
const envVars = await buildRuntimeEnvVars(runtime);
|
|
196
|
-
const payload = await runDockerLicenseJsonCommand(runtime, [
|
|
191
|
+
const payload = (await runDockerLicenseJsonCommand(runtime, [
|
|
197
192
|
'license',
|
|
198
193
|
'generate-id',
|
|
199
194
|
...buildDockerLicenseDbFlagArgs(envVars),
|
|
200
|
-
]);
|
|
195
|
+
]));
|
|
201
196
|
const instanceId = trimValue(payload.instanceId);
|
|
202
197
|
if (!instanceId) {
|
|
203
198
|
throw new Error('Docker instance ID generation did not return an instance ID.');
|
|
@@ -337,7 +332,7 @@ export async function getLicenseStatus(keyData) {
|
|
|
337
332
|
}),
|
|
338
333
|
signal: controller.signal,
|
|
339
334
|
});
|
|
340
|
-
const payload = await response.json();
|
|
335
|
+
const payload = (await response.json());
|
|
341
336
|
return payload?.data?.status === 'active' ? 'active' : 'invalid';
|
|
342
337
|
}
|
|
343
338
|
catch {
|
|
@@ -356,6 +351,22 @@ export async function validateLicenseKey(runtime, key) {
|
|
|
356
351
|
catch {
|
|
357
352
|
keyStatus = 'invalid';
|
|
358
353
|
}
|
|
354
|
+
if (keyStatus) {
|
|
355
|
+
const currentDomain = appUrl(runtime);
|
|
356
|
+
return {
|
|
357
|
+
current: {
|
|
358
|
+
env: undefined,
|
|
359
|
+
domain: currentDomain ? new URL(currentDomain).host : '',
|
|
360
|
+
},
|
|
361
|
+
keyData,
|
|
362
|
+
keyStatus,
|
|
363
|
+
dbMatch: false,
|
|
364
|
+
sysMatch: false,
|
|
365
|
+
envMatch: false,
|
|
366
|
+
domainMatch: false,
|
|
367
|
+
licenseStatus: 'invalid',
|
|
368
|
+
};
|
|
369
|
+
}
|
|
359
370
|
const currentEnv = await getCurrentLicenseEnv(runtime);
|
|
360
371
|
const currentDomain = appUrl(runtime);
|
|
361
372
|
const dbMatch = isDbMatch(currentEnv, keyData);
|
|
@@ -391,7 +402,7 @@ export async function resolveLicenseServiceUrl(value) {
|
|
|
391
402
|
return (await resolveLicensePkgUrl(value)).replace(/\/+$/, '');
|
|
392
403
|
}
|
|
393
404
|
export async function resolveLicensePkgUrl(value) {
|
|
394
|
-
const normalized = String(value ?? '').trim() || await resolveLicensePkgUrlFromConfig();
|
|
405
|
+
const normalized = String(value ?? '').trim() || (await resolveLicensePkgUrlFromConfig());
|
|
395
406
|
return normalized.replace(/\/+$/, '') + '/';
|
|
396
407
|
}
|
|
397
408
|
function shouldRedactOutputKey(key) {
|
|
@@ -414,9 +425,7 @@ export function sanitizeLicenseOutput(value) {
|
|
|
414
425
|
if (value && typeof value === 'object') {
|
|
415
426
|
return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [
|
|
416
427
|
key,
|
|
417
|
-
shouldRedactOutputKey(key)
|
|
418
|
-
? redactOutputValue(String(nestedValue ?? ''))
|
|
419
|
-
: sanitizeLicenseOutput(nestedValue),
|
|
428
|
+
shouldRedactOutputKey(key) ? redactOutputValue(String(nestedValue ?? '')) : sanitizeLicenseOutput(nestedValue),
|
|
420
429
|
]));
|
|
421
430
|
}
|
|
422
431
|
return value;
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { configurePortalWorkspace } from '../../lib/portal-configure.js';
|
|
15
|
+
import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
16
|
+
const portalConfigureText = (key, values, fallback) => translateCli(`commands.portalConfigure.${key}`, values, { fallback });
|
|
17
|
+
export default class PortalConfig extends Command {
|
|
18
|
+
static summary = 'Update portal source configuration';
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> customer --source-storage nocobase',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer --source-storage git --git-repo git@github.com:nocobase/customer-portal.git',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --git-branch main --git-path portals/customer',
|
|
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
|
+
'source-storage': Flags.string({
|
|
41
|
+
description: 'Where portal source code is managed',
|
|
42
|
+
options: ['nocobase', 'git'],
|
|
43
|
+
}),
|
|
44
|
+
'git-repo': Flags.string({
|
|
45
|
+
description: 'Git repository URL used when --source-storage=git',
|
|
46
|
+
}),
|
|
47
|
+
'git-branch': Flags.string({
|
|
48
|
+
description: 'Git branch used when --source-storage=git',
|
|
49
|
+
}),
|
|
50
|
+
'git-path': Flags.string({
|
|
51
|
+
description: 'Directory inside the Git repository for this portal; defaults to the repository root',
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
async run() {
|
|
55
|
+
const { args, flags } = await this.parse(PortalConfig);
|
|
56
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
57
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
58
|
+
command: this,
|
|
59
|
+
requestedEnv,
|
|
60
|
+
yes: flags.yes,
|
|
61
|
+
});
|
|
62
|
+
if (!confirmed) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const scope = resolveDefaultConfigScope();
|
|
66
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
67
|
+
const env = await getEnv(envName, { scope });
|
|
68
|
+
if (!env) {
|
|
69
|
+
this.error(portalConfigureText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
70
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
71
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
72
|
+
}
|
|
73
|
+
const result = await configurePortalWorkspace({
|
|
74
|
+
portal: args.portal,
|
|
75
|
+
env,
|
|
76
|
+
envName,
|
|
77
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
78
|
+
sourceStorage: flags['source-storage'],
|
|
79
|
+
gitRepo: flags['git-repo'],
|
|
80
|
+
gitBranch: flags['git-branch'],
|
|
81
|
+
gitPath: flags['git-path'],
|
|
82
|
+
});
|
|
83
|
+
printSuccess(portalConfigureText('messages.updated', { portal: result.portal, portalDir: result.portalDir }, `Portal "${result.portal}" configuration updated at ${result.portalDir}/portal.config.json.`));
|
|
84
|
+
printInfo(result.remoteSynced
|
|
85
|
+
? portalConfigureText('messages.remoteSynced', undefined, 'Remote portal record: synced')
|
|
86
|
+
: portalConfigureText('messages.remoteSkipped', undefined, 'Remote portal record: not found; local config only'));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -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 { 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 { createPortalWorkspace } from '../../lib/portal-create.js';
|
|
15
|
+
import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
16
|
+
const DEFAULT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
17
|
+
const portalCreateText = (key, values, fallback) => translateCli(`commands.portalCreate.${key}`, values, { fallback });
|
|
18
|
+
export default class PortalCreate extends Command {
|
|
19
|
+
static summary = 'Create a local portal from a template';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --template @nocobase/portal-template-default',
|
|
23
|
+
'<%= 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
|
+
];
|
|
26
|
+
static args = {
|
|
27
|
+
portal: Args.string({
|
|
28
|
+
required: true,
|
|
29
|
+
description: 'Portal name',
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
static flags = {
|
|
33
|
+
template: Flags.string({
|
|
34
|
+
description: 'Template package, local path, or file:// URL',
|
|
35
|
+
default: DEFAULT_PORTAL_TEMPLATE,
|
|
36
|
+
}),
|
|
37
|
+
env: Flags.string({
|
|
38
|
+
char: 'e',
|
|
39
|
+
description: 'CLI env name; omitted uses the current env',
|
|
40
|
+
}),
|
|
41
|
+
yes: Flags.boolean({
|
|
42
|
+
char: 'y',
|
|
43
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
44
|
+
default: false,
|
|
45
|
+
}),
|
|
46
|
+
title: Flags.string({
|
|
47
|
+
description: 'Portal display title; defaults to a title generated from the portal slug',
|
|
48
|
+
}),
|
|
49
|
+
force: Flags.boolean({
|
|
50
|
+
description: 'Delete the existing portal and recreate it',
|
|
51
|
+
default: false,
|
|
52
|
+
}),
|
|
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
|
+
};
|
|
68
|
+
async run() {
|
|
69
|
+
const { args, flags } = await this.parse(PortalCreate);
|
|
70
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
71
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
72
|
+
command: this,
|
|
73
|
+
requestedEnv,
|
|
74
|
+
yes: flags.yes,
|
|
75
|
+
});
|
|
76
|
+
if (!confirmed) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const env = await getEnv(flags.env, { scope: resolveDefaultConfigScope() });
|
|
80
|
+
if (!env) {
|
|
81
|
+
this.error(flags.env
|
|
82
|
+
? portalCreateText('errors.envNotConfigured', { envName: flags.env }, `Env "${flags.env}" is not configured. Run \`nb env add ${flags.env} --api-base-url <url>\` first.`)
|
|
83
|
+
: portalCreateText('errors.noEnvConfigured', undefined, 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
84
|
+
}
|
|
85
|
+
const result = await createPortalWorkspace({
|
|
86
|
+
portal: args.portal,
|
|
87
|
+
title: flags.title,
|
|
88
|
+
template: flags.template,
|
|
89
|
+
env,
|
|
90
|
+
envName: flags.env,
|
|
91
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
92
|
+
force: flags.force,
|
|
93
|
+
sourceStorage: flags['source-storage'],
|
|
94
|
+
gitRepo: flags['git-repo'],
|
|
95
|
+
gitBranch: flags['git-branch'],
|
|
96
|
+
gitPath: flags['git-path'],
|
|
97
|
+
onSkipInstall: (message) => printInfo(message),
|
|
98
|
+
});
|
|
99
|
+
printSuccess(portalCreateText('messages.created', { portal: result.portal, portalDir: result.portalDir }, `Portal "${result.portal}" created at ${result.portalDir}.`));
|
|
100
|
+
printInfo(portalCreateText('messages.app', { app: result.app }, `App: ${result.app}`));
|
|
101
|
+
printInfo(portalCreateText('messages.base', { base: result.portalBase }, `Base: ${result.portalBase}`));
|
|
102
|
+
printInfo(portalCreateText('messages.sourceStorage', { sourceStorage: result.sourceStorage }, `Source storage: ${result.sourceStorage}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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 { deployPortalWorkspace } from '../../lib/portal-deploy.js';
|
|
16
|
+
import { listPortalWorkspaces } from '../../lib/portal-list.js';
|
|
17
|
+
import { printSuccess } from '../../lib/ui.js';
|
|
18
|
+
const portalDeployText = (key, values, fallback) => translateCli(`commands.portalDeploy.${key}`, values, { fallback });
|
|
19
|
+
export default class PortalDeploy extends Command {
|
|
20
|
+
static summary = 'Build and deploy a portal';
|
|
21
|
+
static examples = [
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> customer --env dev --yes',
|
|
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
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const { args, flags } = await this.parse(PortalDeploy);
|
|
44
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
45
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
46
|
+
command: this,
|
|
47
|
+
requestedEnv,
|
|
48
|
+
yes: flags.yes,
|
|
49
|
+
});
|
|
50
|
+
if (!confirmed) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const scope = resolveDefaultConfigScope();
|
|
54
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
55
|
+
const env = await getEnv(envName, { scope });
|
|
56
|
+
if (!env) {
|
|
57
|
+
this.error(portalDeployText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
58
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
59
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
60
|
+
}
|
|
61
|
+
const result = await deployPortalWorkspace({
|
|
62
|
+
portal: args.portal,
|
|
63
|
+
env,
|
|
64
|
+
envName,
|
|
65
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
66
|
+
});
|
|
67
|
+
printSuccess(portalDeployText('messages.deployed', { portal: result.portal }, `Portal "${result.portal}" deployed.`));
|
|
68
|
+
const info = await listPortalWorkspaces({
|
|
69
|
+
env,
|
|
70
|
+
envName,
|
|
71
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
72
|
+
});
|
|
73
|
+
const portal = findPortalListItem(info.items, result.portal);
|
|
74
|
+
if (!portal) {
|
|
75
|
+
this.error(translateCli(`commands.portalInfo.errors.notFound`, { portal: result.portal }, {
|
|
76
|
+
fallback: `Portal "${result.portal}" was not found.`,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
this.log(formatPortalInfo(portal));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -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
|
+
}
|