@nocobase/cli 2.1.0-beta.27 → 2.1.0-beta.30
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/README.md +14 -0
- package/README.zh-CN.md +14 -0
- package/bin/run.js +3 -0
- package/bin/session-env.js +27 -0
- package/dist/commands/app/down.js +47 -9
- package/dist/commands/app/logs.js +17 -0
- package/dist/commands/app/restart.js +23 -1
- package/dist/commands/app/start.js +17 -0
- package/dist/commands/app/stop.js +17 -0
- package/dist/commands/app/upgrade.js +22 -2
- package/dist/commands/db/check.js +6 -4
- package/dist/commands/db/ps.js +1 -1
- package/dist/commands/env/add.js +3 -2
- package/dist/commands/env/auth.js +1 -1
- package/dist/commands/env/current.js +21 -0
- package/dist/commands/env/info.js +4 -3
- package/dist/commands/env/list.js +8 -14
- package/dist/commands/env/remove.js +2 -2
- package/dist/commands/env/status.js +90 -0
- package/dist/commands/env/update.js +1 -1
- package/dist/commands/env/use.js +11 -1
- package/dist/commands/install.js +10 -4
- package/dist/commands/license/activate.js +20 -24
- package/dist/commands/license/id.js +17 -2
- package/dist/commands/license/plugins/clean.js +17 -2
- package/dist/commands/license/plugins/list.js +17 -2
- package/dist/commands/license/plugins/sync.js +22 -5
- package/dist/commands/license/shared.js +15 -6
- package/dist/commands/license/status.js +17 -2
- package/dist/commands/plugin/disable.js +25 -4
- package/dist/commands/plugin/enable.js +25 -4
- package/dist/commands/plugin/list.js +25 -4
- package/dist/commands/session/id.js +24 -0
- package/dist/commands/session/remove.js +57 -0
- package/dist/commands/session/setup.js +62 -0
- package/dist/commands/source/dev.js +19 -1
- package/dist/commands/source/download.js +10 -8
- package/dist/lib/app-managed-resources.js +5 -3
- package/dist/lib/app-runtime.js +1 -1
- package/dist/lib/auth-store.js +28 -11
- package/dist/lib/docker-image.js +37 -0
- package/dist/lib/env-guard.js +61 -0
- package/dist/lib/generated-command.js +16 -0
- package/dist/lib/plugin-storage.js +1 -64
- package/dist/lib/resource-command.js +15 -0
- package/dist/lib/runtime-generator.js +1 -1
- package/dist/lib/session-id.js +17 -0
- package/dist/lib/session-integration.js +703 -0
- package/dist/lib/session-store.js +118 -0
- package/package.json +3 -3
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Args, Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runDockerNocoBaseCommand, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
11
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
12
|
import { announceTargetEnv } from '../../lib/ui.js';
|
|
12
13
|
export default class PluginEnable extends Command {
|
|
13
14
|
static hidden = false;
|
|
@@ -27,18 +28,36 @@ export default class PluginEnable extends Command {
|
|
|
27
28
|
static flags = {
|
|
28
29
|
env: Flags.string({
|
|
29
30
|
char: 'e',
|
|
30
|
-
description: 'CLI env name
|
|
31
|
+
description: 'CLI env name to enable plugins for. Defaults to the current env when omitted',
|
|
32
|
+
}),
|
|
33
|
+
yes: Flags.boolean({
|
|
34
|
+
char: 'y',
|
|
35
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
36
|
+
default: false,
|
|
31
37
|
}),
|
|
32
38
|
};
|
|
33
39
|
async run() {
|
|
34
40
|
const { args, flags } = await this.parse(PluginEnable);
|
|
41
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
42
|
+
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
43
|
+
if (explicitEnvSelection) {
|
|
44
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
45
|
+
command: this,
|
|
46
|
+
requestedEnv,
|
|
47
|
+
yes: flags.yes,
|
|
48
|
+
});
|
|
49
|
+
if (!confirmed) {
|
|
50
|
+
this.log('Canceled.');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
35
54
|
const packages = args.packages;
|
|
36
55
|
if (!Array.isArray(packages) || packages.length === 0) {
|
|
37
56
|
this.error('Pass at least one plugin package name.');
|
|
38
57
|
}
|
|
39
|
-
const runtime = await resolveManagedAppRuntime(
|
|
58
|
+
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
40
59
|
if (!runtime) {
|
|
41
|
-
this.error(formatMissingManagedAppEnvMessage(
|
|
60
|
+
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
42
61
|
}
|
|
43
62
|
announceTargetEnv(runtime.envName);
|
|
44
63
|
if (runtime.kind === 'local') {
|
|
@@ -61,6 +80,8 @@ export default class PluginEnable extends Command {
|
|
|
61
80
|
}
|
|
62
81
|
return;
|
|
63
82
|
}
|
|
64
|
-
await this.config.runCommand('api:pm:enable',
|
|
83
|
+
await this.config.runCommand('api:pm:enable', explicitEnvSelection
|
|
84
|
+
? ['--await-response', '--filter-by-tk', packages.join(','), '--env', runtime.envName, '--yes']
|
|
85
|
+
: ['--await-response', '--filter-by-tk', packages.join(',')]);
|
|
65
86
|
}
|
|
66
87
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runDockerNocoBaseCommand, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
11
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
12
|
export default class PluginList extends Command {
|
|
12
13
|
static hidden = false;
|
|
13
14
|
static args = {};
|
|
@@ -21,14 +22,32 @@ export default class PluginList extends Command {
|
|
|
21
22
|
static flags = {
|
|
22
23
|
env: Flags.string({
|
|
23
24
|
char: 'e',
|
|
24
|
-
description: 'CLI env name
|
|
25
|
+
description: 'CLI env name to inspect plugins for. Defaults to the current env when omitted',
|
|
26
|
+
}),
|
|
27
|
+
yes: Flags.boolean({
|
|
28
|
+
char: 'y',
|
|
29
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
30
|
+
default: false,
|
|
25
31
|
}),
|
|
26
32
|
};
|
|
27
33
|
async run() {
|
|
28
34
|
const { flags } = await this.parse(PluginList);
|
|
29
|
-
const
|
|
35
|
+
const requestedEnv = flags.env?.trim() || undefined;
|
|
36
|
+
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
37
|
+
if (explicitEnvSelection) {
|
|
38
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
39
|
+
command: this,
|
|
40
|
+
requestedEnv,
|
|
41
|
+
yes: flags.yes,
|
|
42
|
+
});
|
|
43
|
+
if (!confirmed) {
|
|
44
|
+
this.log('Canceled.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
30
49
|
if (!runtime) {
|
|
31
|
-
this.error(formatMissingManagedAppEnvMessage(
|
|
50
|
+
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
32
51
|
}
|
|
33
52
|
if (runtime.kind === 'local') {
|
|
34
53
|
try {
|
|
@@ -57,6 +76,8 @@ export default class PluginList extends Command {
|
|
|
57
76
|
'Use a local, Docker, or HTTP env for plugin inspection right now.',
|
|
58
77
|
].join('\n'));
|
|
59
78
|
}
|
|
60
|
-
await this.config.runCommand('api:pm:list',
|
|
79
|
+
await this.config.runCommand('api:pm:list', explicitEnvSelection
|
|
80
|
+
? ['--mode=summary', '--env', runtime.envName, '--yes']
|
|
81
|
+
: ['--mode=summary']);
|
|
61
82
|
}
|
|
62
83
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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 } from '@oclif/core';
|
|
10
|
+
import { resolveSessionIdentity } from '../../lib/session-id.js';
|
|
11
|
+
export default class SessionId extends Command {
|
|
12
|
+
static summary = 'Show the current effective session id';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> <%= command.id %>',
|
|
15
|
+
];
|
|
16
|
+
async run() {
|
|
17
|
+
await this.parse(SessionId);
|
|
18
|
+
const identity = resolveSessionIdentity();
|
|
19
|
+
if (!identity) {
|
|
20
|
+
this.error('No effective session id is available. Run `nb session setup`, then open a new shell session or runtime.');
|
|
21
|
+
}
|
|
22
|
+
this.log(identity.id);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* This file is part of the NocoBase (R) project.
|
|
11
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
12
|
+
* Authors: NocoBase Team.
|
|
13
|
+
*
|
|
14
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
15
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
16
|
+
*/
|
|
17
|
+
import { Command, Flags } from '@oclif/core';
|
|
18
|
+
import { detectSessionShell, removeSessionIntegration } from '../../lib/session-integration.js';
|
|
19
|
+
export default class SessionRemove extends Command {
|
|
20
|
+
static summary = 'Remove shell session integration for NB_SESSION_ID';
|
|
21
|
+
static examples = [
|
|
22
|
+
'<%= config.bin %> <%= command.id %>',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> --shell zsh',
|
|
24
|
+
];
|
|
25
|
+
static flags = {
|
|
26
|
+
shell: Flags.string({
|
|
27
|
+
description: 'Target shell to remove configuration from',
|
|
28
|
+
options: ['bash', 'zsh', 'fish', 'powershell', 'cmd'],
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
async run() {
|
|
32
|
+
const { flags } = await this.parse(SessionRemove);
|
|
33
|
+
const shell = flags.shell ?? detectSessionShell();
|
|
34
|
+
if (!shell) {
|
|
35
|
+
this.error('Could not detect the current shell. Re-run with `--shell bash|zsh|fish|powershell|cmd`.');
|
|
36
|
+
}
|
|
37
|
+
const result = await removeSessionIntegration(shell);
|
|
38
|
+
this.log(`Session integration removed for ${result.shell}.`);
|
|
39
|
+
if (result.profileUpdated) {
|
|
40
|
+
for (const profileFile of result.profileFiles) {
|
|
41
|
+
this.log(`Profile updated: ${profileFile}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (result.managedFileRemoved) {
|
|
45
|
+
this.log(`Managed file removed: ${result.managedFile}`);
|
|
46
|
+
}
|
|
47
|
+
if (result.cmdAutoRunRemoved) {
|
|
48
|
+
this.log(`cmd AutoRun updated: ${result.cmdAutoRunLocation}`);
|
|
49
|
+
}
|
|
50
|
+
if (result.agentConfigUpdated) {
|
|
51
|
+
this.log(`Opencode config updated: ${result.agentConfigFile}`);
|
|
52
|
+
}
|
|
53
|
+
if (result.agentPluginRemoved) {
|
|
54
|
+
this.log(`Opencode agent plugin removed: ${result.agentPluginFile}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* This file is part of the NocoBase (R) project.
|
|
11
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
12
|
+
* Authors: NocoBase Team.
|
|
13
|
+
*
|
|
14
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
15
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
16
|
+
*/
|
|
17
|
+
import { Command, Flags } from '@oclif/core';
|
|
18
|
+
import { detectSessionShell, setupSessionIntegration } from '../../lib/session-integration.js';
|
|
19
|
+
export default class SessionSetup extends Command {
|
|
20
|
+
static summary = 'Set up shell session integration for NB_SESSION_ID';
|
|
21
|
+
static examples = [
|
|
22
|
+
'<%= config.bin %> <%= command.id %>',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> --shell zsh',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> --shell powershell',
|
|
25
|
+
];
|
|
26
|
+
static flags = {
|
|
27
|
+
shell: Flags.string({
|
|
28
|
+
description: 'Target shell to configure',
|
|
29
|
+
options: ['bash', 'zsh', 'fish', 'powershell', 'cmd'],
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { flags } = await this.parse(SessionSetup);
|
|
34
|
+
const shell = flags.shell ?? detectSessionShell();
|
|
35
|
+
if (!shell) {
|
|
36
|
+
this.error('Could not detect the current shell. Re-run with `--shell bash|zsh|fish|powershell|cmd`.');
|
|
37
|
+
}
|
|
38
|
+
const result = await setupSessionIntegration(shell);
|
|
39
|
+
this.log(`Session integration configured for ${result.shell}.`);
|
|
40
|
+
this.log(`Managed file: ${result.managedFile}`);
|
|
41
|
+
if (result.cmdAutoRunConfigured) {
|
|
42
|
+
this.log(`cmd AutoRun updated: ${result.cmdAutoRunLocation}`);
|
|
43
|
+
this.log('Open a new cmd session to initialize NB_SESSION_ID automatically.');
|
|
44
|
+
}
|
|
45
|
+
if (result.agentConfigured) {
|
|
46
|
+
this.log(`Opencode agent plugin installed: ${result.agentPluginFile}`);
|
|
47
|
+
this.log(`Opencode config updated: ${result.agentConfigFile}`);
|
|
48
|
+
}
|
|
49
|
+
else if (result.agentSkippedReason === 'opencode_dir_not_found') {
|
|
50
|
+
this.log('Opencode config directory not found. Skipped agent session integration.');
|
|
51
|
+
}
|
|
52
|
+
if (result.profileFiles.length > 0) {
|
|
53
|
+
for (const profileFile of result.profileFiles) {
|
|
54
|
+
this.log(`Profile updated: ${profileFile}`);
|
|
55
|
+
}
|
|
56
|
+
this.log('Open a new shell session or reload your profile to initialize NB_SESSION_ID automatically.');
|
|
57
|
+
}
|
|
58
|
+
if (result.manualStep) {
|
|
59
|
+
this.log(result.manualStep);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
10
11
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
11
12
|
import { announceTargetEnv, printInfo } from '../../lib/ui.js';
|
|
12
13
|
function formatUnsupportedRuntimeMessage(kind, envName) {
|
|
@@ -72,7 +73,13 @@ export default class SourceDev extends Command {
|
|
|
72
73
|
static flags = {
|
|
73
74
|
env: Flags.string({
|
|
74
75
|
char: 'e',
|
|
75
|
-
description: 'CLI env name
|
|
76
|
+
description: 'CLI env name to run dev mode for. Defaults to the current env when omitted',
|
|
77
|
+
required: false,
|
|
78
|
+
}),
|
|
79
|
+
yes: Flags.boolean({
|
|
80
|
+
char: 'y',
|
|
81
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
82
|
+
default: false,
|
|
76
83
|
required: false,
|
|
77
84
|
}),
|
|
78
85
|
'db-sync': Flags.boolean({
|
|
@@ -103,6 +110,17 @@ export default class SourceDev extends Command {
|
|
|
103
110
|
async run() {
|
|
104
111
|
const { flags } = await this.parse(SourceDev);
|
|
105
112
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
113
|
+
if (requestedEnv && hasExplicitEnvSelection(this.argv)) {
|
|
114
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
115
|
+
command: this,
|
|
116
|
+
requestedEnv,
|
|
117
|
+
yes: flags.yes,
|
|
118
|
+
});
|
|
119
|
+
if (!confirmed) {
|
|
120
|
+
this.log('Canceled.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
106
124
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
107
125
|
if (!runtime) {
|
|
108
126
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
@@ -13,10 +13,9 @@ import path from 'node:path';
|
|
|
13
13
|
import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
14
14
|
import { runPromptCatalog, } from "../../lib/prompt-catalog.js";
|
|
15
15
|
import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, resolveCliLocale, translateCli, } from "../../lib/cli-locale.js";
|
|
16
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
16
17
|
import { run } from "../../lib/run-npm.js";
|
|
17
18
|
import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
|
|
18
|
-
const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
|
|
19
|
-
const DEFAULT_DOCKER_REGISTRY_ZH_CN = 'registry.cn-shanghai.aliyuncs.com/nocobase/nocobase';
|
|
20
19
|
const DEFAULT_DOCKER_PLATFORM = 'auto';
|
|
21
20
|
const DEFAULT_DOWNLOAD_VERSION = 'beta';
|
|
22
21
|
const downloadText = (key, values) => localeText(`commands.download.${key}`, values);
|
|
@@ -422,9 +421,11 @@ export default class SourceDownload extends Command {
|
|
|
422
421
|
return outputAbs;
|
|
423
422
|
}
|
|
424
423
|
dockerTarPath(flags, outputAbs) {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
424
|
+
const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
|
|
425
|
+
defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
|
|
426
|
+
defaultVersion: 'latest',
|
|
427
|
+
});
|
|
428
|
+
const safeBase = imageRef.replace(/[\\/:]/g, '-');
|
|
428
429
|
return path.join(outputAbs, `${safeBase}.tar`);
|
|
429
430
|
}
|
|
430
431
|
/**
|
|
@@ -709,9 +710,10 @@ export default class SourceDownload extends Command {
|
|
|
709
710
|
return argv;
|
|
710
711
|
}
|
|
711
712
|
async downloadFromDocker(flags) {
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
713
|
+
const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
|
|
714
|
+
defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
|
|
715
|
+
defaultVersion: 'latest',
|
|
716
|
+
});
|
|
715
717
|
const platform = dockerPlatformArg(flags['docker-platform']);
|
|
716
718
|
const pullArgs = ['pull'];
|
|
717
719
|
if (platform) {
|
|
@@ -10,10 +10,9 @@ import { mkdir, readdir } from 'node:fs/promises';
|
|
|
10
10
|
import { dockerContainerExists, startDockerContainer } from './app-runtime.js';
|
|
11
11
|
import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin-db.js';
|
|
12
12
|
import { resolveConfiguredEnvPath } from './cli-home.js';
|
|
13
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
|
|
13
14
|
import { commandSucceeds, run } from './run-npm.js';
|
|
14
15
|
import Install from '../commands/install.js';
|
|
15
|
-
const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
|
|
16
|
-
const DEFAULT_DOCKER_VERSION = 'alpha';
|
|
17
16
|
const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
|
|
18
17
|
function commandStdio(verbose) {
|
|
19
18
|
return verbose ? 'inherit' : 'ignore';
|
|
@@ -102,7 +101,10 @@ export function buildSavedDockerRunArgs(runtime) {
|
|
|
102
101
|
const dbPassword = trimValue(config.dbPassword);
|
|
103
102
|
const dockerRegistry = trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY;
|
|
104
103
|
const version = trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION;
|
|
105
|
-
const imageRef =
|
|
104
|
+
const imageRef = resolveDockerImageRef(dockerRegistry, version, {
|
|
105
|
+
defaultRegistry: DEFAULT_DOCKER_REGISTRY,
|
|
106
|
+
defaultVersion: DEFAULT_DOCKER_VERSION,
|
|
107
|
+
});
|
|
106
108
|
const missing = [];
|
|
107
109
|
if (!storagePath) {
|
|
108
110
|
missing.push('storagePath');
|
package/dist/lib/app-runtime.js
CHANGED
|
@@ -61,7 +61,7 @@ export async function resolveManagedAppRuntime(envName) {
|
|
|
61
61
|
if (!env) {
|
|
62
62
|
return undefined;
|
|
63
63
|
}
|
|
64
|
-
const resolvedName = env.name || envName?.trim() || config.
|
|
64
|
+
const resolvedName = env.name || envName?.trim() || config.lastEnv || 'default';
|
|
65
65
|
const source = normalizeEnvSource(env);
|
|
66
66
|
const dockerNetworkName = sanitizeDockerResourceName(getEffectiveCliConfigValue(config, 'docker.network') || defaultDockerNetworkName());
|
|
67
67
|
const dockerContainerPrefix = sanitizeDockerResourceName(getEffectiveCliConfigValue(config, 'docker.container-prefix') || defaultDockerContainerPrefix());
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { promises as fs } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath, } from './cli-home.js';
|
|
12
|
+
import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
|
|
12
13
|
function normalizeStoredEnvKind(value) {
|
|
13
14
|
const kind = String(value ?? '').trim();
|
|
14
15
|
if (kind === 'remote') {
|
|
@@ -84,7 +85,7 @@ function normalizeAuthConfig(config) {
|
|
|
84
85
|
}
|
|
85
86
|
: {}),
|
|
86
87
|
},
|
|
87
|
-
|
|
88
|
+
lastEnv: config.lastEnv || config.currentEnv || 'default',
|
|
88
89
|
envs: Object.fromEntries(Object.entries(config.envs || {}).map(([envName, entry]) => [envName, normalizeEnvConfigEntry(entry) ?? {}])),
|
|
89
90
|
};
|
|
90
91
|
}
|
|
@@ -93,7 +94,7 @@ function getConfigFile(options = {}) {
|
|
|
93
94
|
}
|
|
94
95
|
function createDefaultConfig() {
|
|
95
96
|
return {
|
|
96
|
-
|
|
97
|
+
lastEnv: 'default',
|
|
97
98
|
envs: {},
|
|
98
99
|
};
|
|
99
100
|
}
|
|
@@ -121,20 +122,24 @@ export async function saveAuthConfig(config, options = {}) {
|
|
|
121
122
|
export async function listEnvs(options = {}) {
|
|
122
123
|
const config = await loadAuthConfig(options);
|
|
123
124
|
return {
|
|
124
|
-
|
|
125
|
+
lastEnv: config.lastEnv || 'default',
|
|
125
126
|
envs: config.envs,
|
|
126
127
|
};
|
|
127
128
|
}
|
|
128
129
|
export async function getCurrentEnvName(options = {}) {
|
|
129
130
|
const config = await loadAuthConfig(options);
|
|
130
|
-
return config.
|
|
131
|
+
return await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
|
|
132
|
+
scope: options.scope,
|
|
133
|
+
lastEnv: config.lastEnv,
|
|
134
|
+
});
|
|
131
135
|
}
|
|
132
136
|
export async function setCurrentEnv(envName, options = {}) {
|
|
133
137
|
const config = await loadExactAuthConfig(options);
|
|
134
138
|
if (!config.envs[envName]) {
|
|
135
139
|
throw new Error(`Env "${envName}" is not configured`);
|
|
136
140
|
}
|
|
137
|
-
config.
|
|
141
|
+
config.lastEnv = envName;
|
|
142
|
+
await setSessionCurrentEnv(envName, options.scope);
|
|
138
143
|
await saveAuthConfig(config, options);
|
|
139
144
|
}
|
|
140
145
|
export class Env {
|
|
@@ -211,7 +216,10 @@ export class Env {
|
|
|
211
216
|
export async function getEnv(envName, options = {}) {
|
|
212
217
|
const { config: snapshot, ...loadOptions } = options;
|
|
213
218
|
const config = snapshot ?? (await loadAuthConfig(loadOptions));
|
|
214
|
-
const resolved = envName?.trim() || config.
|
|
219
|
+
const resolved = envName?.trim() || (await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
|
|
220
|
+
scope: loadOptions.scope,
|
|
221
|
+
lastEnv: config.lastEnv,
|
|
222
|
+
}));
|
|
215
223
|
const envConfig = config.envs[resolved];
|
|
216
224
|
if (!envConfig) {
|
|
217
225
|
return undefined;
|
|
@@ -243,7 +251,6 @@ async function writeEnv(envName, updater, options = {}) {
|
|
|
243
251
|
const config = await loadExactAuthConfig(options);
|
|
244
252
|
const previous = config.envs[envName];
|
|
245
253
|
config.envs[envName] = updater(previous);
|
|
246
|
-
config.currentEnv = envName;
|
|
247
254
|
await saveAuthConfig(config, options);
|
|
248
255
|
}
|
|
249
256
|
export async function upsertEnv(envName, config, options = {}) {
|
|
@@ -306,7 +313,6 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
|
|
|
306
313
|
...current,
|
|
307
314
|
runtime,
|
|
308
315
|
};
|
|
309
|
-
config.currentEnv = envName;
|
|
310
316
|
await saveAuthConfig(config, options);
|
|
311
317
|
}
|
|
312
318
|
export async function removeEnv(envName, options = {}) {
|
|
@@ -315,14 +321,25 @@ export async function removeEnv(envName, options = {}) {
|
|
|
315
321
|
throw new Error(`Env "${envName}" is not configured`);
|
|
316
322
|
}
|
|
317
323
|
delete config.envs[envName];
|
|
318
|
-
if (config.
|
|
324
|
+
if (config.lastEnv === envName) {
|
|
319
325
|
const nextEnv = Object.keys(config.envs).sort()[0];
|
|
320
|
-
config.
|
|
326
|
+
config.lastEnv = nextEnv ?? 'default';
|
|
321
327
|
}
|
|
322
328
|
await saveAuthConfig(config, options);
|
|
329
|
+
const remainingEnvNames = Object.keys(config.envs).sort();
|
|
330
|
+
const fallbackEnv = remainingEnvNames.length
|
|
331
|
+
? await resolveEffectiveCurrentEnv(remainingEnvNames, {
|
|
332
|
+
scope: options.scope,
|
|
333
|
+
lastEnv: config.lastEnv,
|
|
334
|
+
})
|
|
335
|
+
: undefined;
|
|
336
|
+
await cleanupCurrentSessionAfterEnvRemoval(envName, {
|
|
337
|
+
scope: options.scope,
|
|
338
|
+
fallbackEnv,
|
|
339
|
+
});
|
|
323
340
|
return {
|
|
324
341
|
removed: envName,
|
|
325
|
-
|
|
342
|
+
lastEnv: config.lastEnv || 'default',
|
|
326
343
|
hasEnvs: Object.keys(config.envs).length > 0,
|
|
327
344
|
};
|
|
328
345
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
|
|
10
|
+
export const DEFAULT_DOCKER_REGISTRY_ZH_CN = 'registry.cn-shanghai.aliyuncs.com/nocobase/nocobase';
|
|
11
|
+
export const DEFAULT_DOCKER_VERSION = 'alpha';
|
|
12
|
+
export const DOCKER_IMAGE_FULL_SUFFIX = '-full';
|
|
13
|
+
const OFFICIAL_FULL_IMAGE_REGISTRIES = new Set([
|
|
14
|
+
DEFAULT_DOCKER_REGISTRY,
|
|
15
|
+
DEFAULT_DOCKER_REGISTRY_ZH_CN,
|
|
16
|
+
]);
|
|
17
|
+
function trimValue(value) {
|
|
18
|
+
return String(value ?? '').trim();
|
|
19
|
+
}
|
|
20
|
+
export function shouldUseFullDockerImageTag(registry) {
|
|
21
|
+
return OFFICIAL_FULL_IMAGE_REGISTRIES.has(trimValue(registry));
|
|
22
|
+
}
|
|
23
|
+
export function normalizeDockerImageTag(registry, version) {
|
|
24
|
+
const tag = trimValue(version) || DEFAULT_DOCKER_VERSION;
|
|
25
|
+
if (!shouldUseFullDockerImageTag(registry)) {
|
|
26
|
+
return tag;
|
|
27
|
+
}
|
|
28
|
+
return tag.endsWith(DOCKER_IMAGE_FULL_SUFFIX)
|
|
29
|
+
? tag
|
|
30
|
+
: `${tag}${DOCKER_IMAGE_FULL_SUFFIX}`;
|
|
31
|
+
}
|
|
32
|
+
export function resolveDockerImageRef(registry, version, options) {
|
|
33
|
+
const resolvedRegistry = trimValue(registry) || options?.defaultRegistry || DEFAULT_DOCKER_REGISTRY;
|
|
34
|
+
const rawVersion = trimValue(version) || options?.defaultVersion || DEFAULT_DOCKER_VERSION;
|
|
35
|
+
const normalizedTag = normalizeDockerImageTag(resolvedRegistry, rawVersion);
|
|
36
|
+
return `${resolvedRegistry}:${normalizedTag}`;
|
|
37
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 * as p from '@clack/prompts';
|
|
10
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
11
|
+
import { getCurrentEnvName } from './auth-store.js';
|
|
12
|
+
function normalizeEnvName(value) {
|
|
13
|
+
const text = String(value ?? '').trim();
|
|
14
|
+
return text || undefined;
|
|
15
|
+
}
|
|
16
|
+
export function hasExplicitEnvSelection(argv) {
|
|
17
|
+
return argv.some((token, index) => (token === '--env'
|
|
18
|
+
|| token === '-e'
|
|
19
|
+
|| token.startsWith('--env=')
|
|
20
|
+
|| (token.startsWith('-e') && token.length > 2 && index >= 0)));
|
|
21
|
+
}
|
|
22
|
+
function isInteractiveTerminal() {
|
|
23
|
+
return Boolean(input.isTTY && output.isTTY);
|
|
24
|
+
}
|
|
25
|
+
function formatCrossEnvPromptMessage(currentEnv, requestedEnv) {
|
|
26
|
+
return `Current env is "${currentEnv}", but this command targets "${requestedEnv}" via --env. Continue without switching the current env?`;
|
|
27
|
+
}
|
|
28
|
+
export function formatCrossEnvRefusalMessage(currentEnv, requestedEnv) {
|
|
29
|
+
return [
|
|
30
|
+
`Refusing to run against env "${requestedEnv}" because the current env is "${currentEnv}" and interactive confirmation is unavailable in the current agent session.`,
|
|
31
|
+
'',
|
|
32
|
+
'For safety, the agent will not switch envs automatically and will not add --yes on your behalf.',
|
|
33
|
+
'',
|
|
34
|
+
'To continue:',
|
|
35
|
+
`- run \`nb env use ${requestedEnv}\` yourself and then re-run the command, or`,
|
|
36
|
+
`- re-run the same command with \`--env ${requestedEnv} --yes\` to confirm this one-off cross-env operation.`,
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
39
|
+
export async function ensureCrossEnvConfirmed(options) {
|
|
40
|
+
const requestedEnv = normalizeEnvName(options.requestedEnv);
|
|
41
|
+
if (!requestedEnv) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const currentEnv = normalizeEnvName(await getCurrentEnvName());
|
|
45
|
+
const interactiveTerminal = isInteractiveTerminal();
|
|
46
|
+
const bypassInteractivePrompt = interactiveTerminal && Boolean(options.yes);
|
|
47
|
+
if (!currentEnv || currentEnv === requestedEnv || bypassInteractivePrompt) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (!interactiveTerminal) {
|
|
51
|
+
options.command.error(formatCrossEnvRefusalMessage(currentEnv, requestedEnv));
|
|
52
|
+
}
|
|
53
|
+
const answer = await p.confirm({
|
|
54
|
+
message: formatCrossEnvPromptMessage(currentEnv, requestedEnv),
|
|
55
|
+
initialValue: false,
|
|
56
|
+
});
|
|
57
|
+
if (p.isCancel(answer)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return Boolean(answer);
|
|
61
|
+
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { Command, Flags } from '@oclif/core';
|
|
18
18
|
import { executeApiRequest } from './api-client.js';
|
|
19
|
+
import { ensureCrossEnvConfirmed } from './env-guard.js';
|
|
19
20
|
import { applyPostProcessor } from './post-processors.js';
|
|
20
21
|
import { registerPostProcessors } from '../post-processors/index.js';
|
|
21
22
|
function buildParameterFlag(parameter, options) {
|
|
@@ -110,6 +111,12 @@ export function createGeneratedFlags(operation) {
|
|
|
110
111
|
default: false,
|
|
111
112
|
helpGroup: 'Global',
|
|
112
113
|
});
|
|
114
|
+
flags.yes = Flags.boolean({
|
|
115
|
+
char: 'y',
|
|
116
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
117
|
+
default: false,
|
|
118
|
+
helpGroup: 'Global',
|
|
119
|
+
});
|
|
113
120
|
flags.env = Flags.string({
|
|
114
121
|
char: 'e',
|
|
115
122
|
description: 'Environment name',
|
|
@@ -139,6 +146,15 @@ export class GeneratedApiCommand extends Command {
|
|
|
139
146
|
registerPostProcessors();
|
|
140
147
|
const ctor = this.constructor;
|
|
141
148
|
const { flags } = await this.parse(ctor);
|
|
149
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
150
|
+
command: this,
|
|
151
|
+
requestedEnv: flags.env,
|
|
152
|
+
yes: flags.yes,
|
|
153
|
+
});
|
|
154
|
+
if (!confirmed) {
|
|
155
|
+
this.log('Canceled.');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
142
158
|
const response = await executeApiRequest({
|
|
143
159
|
envName: flags.env,
|
|
144
160
|
baseUrl: flags['api-base-url'],
|