@nocobase/cli 2.1.0-alpha.30 → 2.1.0-alpha.32
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/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 +17 -0
- package/dist/commands/env/add.js +2 -1
- package/dist/commands/env/current.js +21 -0
- package/dist/commands/env/list.js +8 -14
- package/dist/commands/env/remove.js +1 -1
- package/dist/commands/env/status.js +90 -0
- package/dist/commands/env/use.js +10 -0
- package/dist/commands/install.js +2 -1
- package/dist/commands/license/activate.js +18 -19
- package/dist/commands/license/id.js +18 -0
- package/dist/commands/license/plugins/clean.js +18 -0
- package/dist/commands/license/plugins/list.js +19 -1
- package/dist/commands/license/plugins/sync.js +18 -0
- package/dist/commands/license/status.js +18 -0
- package/dist/commands/plugin/disable.js +24 -3
- package/dist/commands/plugin/enable.js +24 -3
- package/dist/commands/plugin/list.js +24 -3
- 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/test.js +11 -1
- package/dist/lib/app-runtime.js +1 -1
- package/dist/lib/auth-store.js +28 -11
- package/dist/lib/env-guard.js +61 -0
- package/dist/lib/generated-command.js +16 -0
- 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 +719 -0
- package/dist/lib/session-store.js +118 -0
- package/package.json +3 -3
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,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'],
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Flags } from '@oclif/core';
|
|
10
|
+
import { ensureCrossEnvConfirmed } from './env-guard.js';
|
|
10
11
|
import { executeResourceRequest } from './resource-request.js';
|
|
11
12
|
import { setVerboseMode } from './ui.js';
|
|
12
13
|
function parseJson(value, flagName) {
|
|
@@ -95,6 +96,11 @@ export const resourceBaseFlags = {
|
|
|
95
96
|
description: 'Show detailed progress output',
|
|
96
97
|
default: false,
|
|
97
98
|
}),
|
|
99
|
+
yes: Flags.boolean({
|
|
100
|
+
char: 'y',
|
|
101
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
102
|
+
default: false,
|
|
103
|
+
}),
|
|
98
104
|
env: Flags.string({
|
|
99
105
|
char: 'e',
|
|
100
106
|
description: 'Environment name',
|
|
@@ -331,6 +337,15 @@ export function buildQueryArgs(flags) {
|
|
|
331
337
|
}
|
|
332
338
|
export async function runResourceCommand(command, action, flags, args) {
|
|
333
339
|
setVerboseMode(Boolean(flags.verbose));
|
|
340
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
341
|
+
command,
|
|
342
|
+
requestedEnv: flags.env,
|
|
343
|
+
yes: flags.yes,
|
|
344
|
+
});
|
|
345
|
+
if (!confirmed) {
|
|
346
|
+
command.log('Canceled.');
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
334
349
|
const response = await executeResourceRequest({
|
|
335
350
|
envName: flags.env,
|
|
336
351
|
baseUrl: flags['api-base-url'],
|
|
@@ -18,7 +18,7 @@ import { createHash } from 'node:crypto';
|
|
|
18
18
|
import { loadBuildConfig } from './build-config.js';
|
|
19
19
|
import { toKebabCase, toLogicalActionName, toLogicalResourceName, toResourceSegments } from './naming.js';
|
|
20
20
|
import { collectOperations } from './openapi.js';
|
|
21
|
-
const RESERVED_FLAG_NAMES = new Set(['api-base-url', 'base-url', 'env', 'token', 'json-output', 'body', 'body-file']);
|
|
21
|
+
const RESERVED_FLAG_NAMES = new Set(['api-base-url', 'base-url', 'env', 'token', 'json-output', 'body', 'body-file', 'yes']);
|
|
22
22
|
function matchesPattern(value, pattern) {
|
|
23
23
|
if (!value) {
|
|
24
24
|
return false;
|
|
@@ -0,0 +1,17 @@
|
|
|
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 function resolveSessionIdentity() {
|
|
10
|
+
const sessionId = String(process.env.NB_SESSION_ID ?? '').trim();
|
|
11
|
+
if (sessionId) {
|
|
12
|
+
return {
|
|
13
|
+
id: sessionId,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|