@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.
Files changed (38) hide show
  1. package/README.md +14 -0
  2. package/README.zh-CN.md +14 -0
  3. package/dist/commands/app/down.js +47 -9
  4. package/dist/commands/app/logs.js +17 -0
  5. package/dist/commands/app/restart.js +23 -1
  6. package/dist/commands/app/start.js +17 -0
  7. package/dist/commands/app/stop.js +17 -0
  8. package/dist/commands/app/upgrade.js +17 -0
  9. package/dist/commands/env/add.js +2 -1
  10. package/dist/commands/env/current.js +21 -0
  11. package/dist/commands/env/list.js +8 -14
  12. package/dist/commands/env/remove.js +1 -1
  13. package/dist/commands/env/status.js +90 -0
  14. package/dist/commands/env/use.js +10 -0
  15. package/dist/commands/install.js +2 -1
  16. package/dist/commands/license/activate.js +18 -19
  17. package/dist/commands/license/id.js +18 -0
  18. package/dist/commands/license/plugins/clean.js +18 -0
  19. package/dist/commands/license/plugins/list.js +19 -1
  20. package/dist/commands/license/plugins/sync.js +18 -0
  21. package/dist/commands/license/status.js +18 -0
  22. package/dist/commands/plugin/disable.js +24 -3
  23. package/dist/commands/plugin/enable.js +24 -3
  24. package/dist/commands/plugin/list.js +24 -3
  25. package/dist/commands/session/id.js +24 -0
  26. package/dist/commands/session/remove.js +57 -0
  27. package/dist/commands/session/setup.js +62 -0
  28. package/dist/commands/source/test.js +11 -1
  29. package/dist/lib/app-runtime.js +1 -1
  30. package/dist/lib/auth-store.js +28 -11
  31. package/dist/lib/env-guard.js +61 -0
  32. package/dist/lib/generated-command.js +16 -0
  33. package/dist/lib/resource-command.js +15 -0
  34. package/dist/lib/runtime-generator.js +1 -1
  35. package/dist/lib/session-id.js +17 -0
  36. package/dist/lib/session-integration.js +719 -0
  37. package/dist/lib/session-store.js +118 -0
  38. package/package.json +3 -3
package/README.md CHANGED
@@ -296,6 +296,20 @@ Show the current env:
296
296
  nb env
297
297
  ```
298
298
 
299
+ Show only the current env name:
300
+
301
+ ```bash
302
+ nb env current
303
+ ```
304
+
305
+ Set up shell session integration for `NB_SESSION_ID`:
306
+
307
+ ```bash
308
+ nb session setup
309
+ nb session id
310
+ nb session remove
311
+ ```
312
+
299
313
  List configured envs with token-verified API status:
300
314
 
301
315
  ```bash
package/README.zh-CN.md CHANGED
@@ -256,6 +256,20 @@ nb app down --env app1 --all --yes
256
256
  nb env
257
257
  ```
258
258
 
259
+ 只查看当前 env 名称:
260
+
261
+ ```bash
262
+ nb env current
263
+ ```
264
+
265
+ 配置 `NB_SESSION_ID` 的 shell session 集成:
266
+
267
+ ```bash
268
+ nb session setup
269
+ nb session id
270
+ nb session remove
271
+ ```
272
+
259
273
  查看已配置的 env 及 Token 验证后的 API 状态:
260
274
 
261
275
  ```bash
@@ -12,8 +12,9 @@ import fsp from 'node:fs/promises';
12
12
  import os from 'node:os';
13
13
  import path from 'node:path';
14
14
  import { buildDockerDbContainerName, formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
15
- import { removeEnv } from '../../lib/auth-store.js';
15
+ import { getCurrentEnvName, removeEnv } from '../../lib/auth-store.js';
16
16
  import { resolveConfiguredEnvPath } from '../../lib/cli-home.js';
17
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
17
18
  import { commandOutput, commandSucceeds, run } from '../../lib/run-npm.js';
18
19
  import { failTask, isInteractiveTerminal, printInfo, startTask, succeedTask, } from '../../lib/ui.js';
19
20
  function resolveConfiguredPath(value) {
@@ -97,16 +98,16 @@ function builtinDbContainerName(runtime) {
97
98
  function managedDockerNetworkName(runtime) {
98
99
  return runtime.dockerNetworkName?.trim() || runtime.workspaceName?.trim() || undefined;
99
100
  }
100
- async function confirmDownAll(envName, yes, options) {
101
- if (yes) {
101
+ async function confirmDownAll(envName, force, options) {
102
+ if (force) {
102
103
  return true;
103
104
  }
104
105
  const usedCurrentEnv = options?.explicitEnv === false;
105
106
  if (!isInteractiveTerminal()) {
106
107
  if (usedCurrentEnv) {
107
- throw new Error(`\`nb app down --all\` is using the current env "${envName}". Re-run with --env ${envName} --yes to delete everything for that env in non-interactive mode.`);
108
+ throw new Error(`\`nb app down --all\` is using the current env "${envName}". Re-run with --env ${envName} --force to delete everything for that env in non-interactive mode.`);
108
109
  }
109
- throw new Error(`\`nb app down --all\` needs confirmation. Re-run with --yes to delete everything for "${envName}" in non-interactive mode.`);
110
+ throw new Error(`\`nb app down --all\` needs confirmation. Re-run with --force to delete everything for "${envName}" in non-interactive mode.`);
110
111
  }
111
112
  const answer = await p.confirm({
112
113
  message: usedCurrentEnv
@@ -122,6 +123,17 @@ async function confirmDownAll(envName, yes, options) {
122
123
  }
123
124
  return answer;
124
125
  }
126
+ function formatDownCrossEnvForceRequiredMessage(currentEnv, requestedEnv) {
127
+ return [
128
+ `Refusing to run against env "${requestedEnv}" because the current env is "${currentEnv}" and interactive confirmation is unavailable in the current agent session.`,
129
+ '',
130
+ 'For safety, the agent will not switch envs automatically and will not add --force on your behalf.',
131
+ '',
132
+ 'To continue:',
133
+ `- run \`nb env use ${requestedEnv}\` yourself and then re-run the command, or`,
134
+ `- re-run the same command with \`--env ${requestedEnv} --force\` to confirm this one-off cross-env operation.`,
135
+ ].join('\n');
136
+ }
125
137
  function formatDownFailure(envName, message) {
126
138
  return [
127
139
  `Couldn't bring down NocoBase for "${envName}".`,
@@ -134,7 +146,8 @@ export default class AppDown extends Command {
134
146
  static description = 'Bring down the selected env by removing runtime containers and the saved local app files. Storage data and env config are kept unless explicitly requested.';
135
147
  static examples = [
136
148
  '<%= config.bin %> <%= command.id %> --env app1',
137
- '<%= config.bin %> <%= command.id %> --env app1 --all --yes',
149
+ '<%= config.bin %> <%= command.id %> --env app1 --all --force',
150
+ '<%= config.bin %> <%= command.id %> --env app1 --force',
138
151
  ];
139
152
  static flags = {
140
153
  env: Flags.string({
@@ -147,7 +160,12 @@ export default class AppDown extends Command {
147
160
  }),
148
161
  yes: Flags.boolean({
149
162
  char: 'y',
150
- description: 'Confirm destructive actions without prompting',
163
+ description: 'Skip the interactive cross-env confirmation prompt',
164
+ default: false,
165
+ }),
166
+ force: Flags.boolean({
167
+ char: 'f',
168
+ description: 'Force a one-off cross-env operation when --env targets a different env in non-interactive mode',
151
169
  default: false,
152
170
  }),
153
171
  verbose: Flags.boolean({
@@ -158,6 +176,26 @@ export default class AppDown extends Command {
158
176
  async run() {
159
177
  const { flags } = await this.parse(AppDown);
160
178
  const requestedEnv = flags.env?.trim() || undefined;
179
+ if (requestedEnv && hasExplicitEnvSelection(this.argv)) {
180
+ if (!isInteractiveTerminal()) {
181
+ const currentEnv = await getCurrentEnvName();
182
+ const normalizedCurrentEnv = String(currentEnv ?? '').trim() || undefined;
183
+ if (normalizedCurrentEnv && normalizedCurrentEnv !== requestedEnv && !flags.force) {
184
+ this.error(formatDownCrossEnvForceRequiredMessage(normalizedCurrentEnv, requestedEnv));
185
+ }
186
+ }
187
+ else {
188
+ const confirmed = await ensureCrossEnvConfirmed({
189
+ command: this,
190
+ requestedEnv,
191
+ yes: flags.yes,
192
+ });
193
+ if (!confirmed) {
194
+ this.log('Canceled.');
195
+ return;
196
+ }
197
+ }
198
+ }
161
199
  const explicitEnv = Boolean(requestedEnv);
162
200
  const removeData = Boolean(flags.all);
163
201
  const removeEnvConfig = Boolean(flags.all);
@@ -182,7 +220,7 @@ export default class AppDown extends Command {
182
220
  if (flags.all) {
183
221
  let confirmed = false;
184
222
  try {
185
- confirmed = await confirmDownAll(runtime.envName, flags.yes, { explicitEnv });
223
+ confirmed = await confirmDownAll(runtime.envName, flags.force, { explicitEnv });
186
224
  }
187
225
  catch (error) {
188
226
  this.error(error instanceof Error ? error.message : String(error));
@@ -255,7 +293,7 @@ export default class AppDown extends Command {
255
293
  if (removeEnvConfig) {
256
294
  startTask(`Removing saved CLI env config for "${runtime.envName}"...`);
257
295
  const result = await removeEnv(runtime.envName);
258
- succeedTask(`Saved CLI env config removed for "${runtime.envName}"${result.currentEnv ? ` (current env: ${result.currentEnv})` : ''}.`);
296
+ succeedTask(`Saved CLI env config removed for "${runtime.envName}"${result.lastEnv ? ` (last env: ${result.lastEnv})` : ''}.`);
259
297
  }
260
298
  }
261
299
  catch (error) {
@@ -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 { run } from '../../lib/run-npm.js';
12
13
  import { printInfo } from '../../lib/ui.js';
@@ -31,6 +32,11 @@ export default class AppLogs extends Command {
31
32
  char: 'e',
32
33
  description: 'CLI env name to inspect logs for. Defaults to the current env when omitted',
33
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
+ }),
34
40
  tail: Flags.integer({
35
41
  description: 'Number of recent log lines to show before following',
36
42
  default: 100,
@@ -46,6 +52,17 @@ export default class AppLogs extends Command {
46
52
  async run() {
47
53
  const { flags } = await this.parse(AppLogs);
48
54
  const requestedEnv = flags.env?.trim() || undefined;
55
+ if (requestedEnv && hasExplicitEnvSelection(this.argv)) {
56
+ const confirmed = await ensureCrossEnvConfirmed({
57
+ command: this,
58
+ requestedEnv,
59
+ yes: flags.yes,
60
+ });
61
+ if (!confirmed) {
62
+ this.log('Canceled.');
63
+ return;
64
+ }
65
+ }
49
66
  const runtime = await resolveManagedAppRuntime(requestedEnv);
50
67
  if (!runtime) {
51
68
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
@@ -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
  function argvHasToken(argv, tokens) {
11
12
  return tokens.some((token) => argv.includes(token));
12
13
  }
@@ -35,6 +36,11 @@ export default class AppRestart extends Command {
35
36
  char: 'e',
36
37
  description: 'CLI env name to restart. Defaults to the current env when omitted',
37
38
  }),
39
+ yes: Flags.boolean({
40
+ char: 'y',
41
+ description: 'Confirm using --env when it targets a different env than the current env',
42
+ default: false,
43
+ }),
38
44
  quickstart: Flags.boolean({ description: 'Quickstart the application after stopping it', required: false }),
39
45
  port: Flags.string({ description: 'Port (overrides appPort from env config when set)', char: 'p', required: false }),
40
46
  daemon: Flags.boolean({
@@ -53,9 +59,25 @@ export default class AppRestart extends Command {
53
59
  };
54
60
  async run() {
55
61
  const { flags } = await this.parse(AppRestart);
62
+ const requestedEnv = flags.env?.trim() || undefined;
63
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
64
+ if (explicitEnvSelection) {
65
+ const confirmed = await ensureCrossEnvConfirmed({
66
+ command: this,
67
+ requestedEnv,
68
+ yes: flags.yes,
69
+ });
70
+ if (!confirmed) {
71
+ this.log('Canceled.');
72
+ return;
73
+ }
74
+ }
56
75
  const stopArgv = [];
57
76
  const daemonFlagWasProvided = argvHasToken(this.argv, ['--daemon', '--no-daemon']);
58
- pushFlag(stopArgv, '--env', flags.env?.trim() || undefined);
77
+ pushFlag(stopArgv, '--env', requestedEnv);
78
+ if (flags.yes || explicitEnvSelection) {
79
+ stopArgv.push('--yes');
80
+ }
59
81
  if (flags.verbose) {
60
82
  stopArgv.push('--verbose');
61
83
  }
@@ -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, startDockerContainer, } from '../../lib/app-runtime.js';
11
12
  import { AppHealthCheckError, formatAppUrl, isAppReady, resolveManagedAppApiBaseUrl, waitForAppReady, } from '../../lib/app-health.js';
12
13
  import { ensureBuiltinDbReady, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
@@ -68,6 +69,11 @@ export default class AppStart extends Command {
68
69
  char: 'e',
69
70
  description: 'CLI env name to start. Defaults to the current env when omitted',
70
71
  }),
72
+ yes: Flags.boolean({
73
+ char: 'y',
74
+ description: 'Confirm using --env when it targets a different env than the current env',
75
+ default: false,
76
+ }),
71
77
  quickstart: Flags.boolean({ description: 'Quickstart the application', required: false }),
72
78
  port: Flags.string({ description: 'Port (overrides appPort from env config when set)', char: 'p', required: false }),
73
79
  daemon: Flags.boolean({
@@ -87,6 +93,17 @@ export default class AppStart extends Command {
87
93
  async run() {
88
94
  const { flags } = await this.parse(AppStart);
89
95
  const requestedEnv = flags.env?.trim() || undefined;
96
+ if (requestedEnv && hasExplicitEnvSelection(this.argv)) {
97
+ const confirmed = await ensureCrossEnvConfirmed({
98
+ command: this,
99
+ requestedEnv,
100
+ yes: flags.yes,
101
+ });
102
+ if (!confirmed) {
103
+ this.log('Canceled.');
104
+ return;
105
+ }
106
+ }
90
107
  const daemonFlagWasProvided = argvHasToken(this.argv, ['--daemon', '--no-daemon']);
91
108
  const runtime = await resolveManagedAppRuntime(requestedEnv);
92
109
  const commandStdio = flags.verbose ? 'inherit' : 'ignore';
@@ -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, stopDockerContainer, } from '../../lib/app-runtime.js';
11
12
  import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
12
13
  function formatStopFailure(envName, message) {
@@ -38,6 +39,11 @@ export default class AppStop extends Command {
38
39
  char: 'e',
39
40
  description: 'CLI env name to stop. Defaults to the current env when omitted',
40
41
  }),
42
+ yes: Flags.boolean({
43
+ char: 'y',
44
+ description: 'Confirm using --env when it targets a different env than the current env',
45
+ default: false,
46
+ }),
41
47
  verbose: Flags.boolean({
42
48
  description: 'Show raw shutdown output from the underlying local or Docker command',
43
49
  default: false,
@@ -46,6 +52,17 @@ export default class AppStop extends Command {
46
52
  async run() {
47
53
  const { flags } = await this.parse(AppStop);
48
54
  const requestedEnv = flags.env?.trim() || undefined;
55
+ if (requestedEnv && hasExplicitEnvSelection(this.argv)) {
56
+ const confirmed = await ensureCrossEnvConfirmed({
57
+ command: this,
58
+ requestedEnv,
59
+ yes: flags.yes,
60
+ });
61
+ if (!confirmed) {
62
+ this.log('Canceled.');
63
+ return;
64
+ }
65
+ }
49
66
  const runtime = await resolveManagedAppRuntime(requestedEnv);
50
67
  const commandStdio = flags.verbose ? 'inherit' : 'ignore';
51
68
  if (!runtime) {
@@ -11,6 +11,7 @@ import { upsertEnv } from '../../lib/auth-store.js';
11
11
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, startDockerContainer, stopDockerContainer, } from '../../lib/app-runtime.js';
12
12
  import { resolveConfiguredEnvPath } from '../../lib/cli-home.js';
13
13
  import { deriveBuiltinDbConnection } from '../../lib/builtin-db.js';
14
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
14
15
  import { commandSucceeds, run } from '../../lib/run-npm.js';
15
16
  import { announceTargetEnv, failTask, printInfo, startTask, stopTask, succeedTask, updateTask } from '../../lib/ui.js';
16
17
  const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
@@ -211,6 +212,11 @@ export default class AppUpgrade extends Command {
211
212
  char: 'e',
212
213
  description: 'CLI env name to upgrade. Defaults to the current env when omitted',
213
214
  }),
215
+ yes: Flags.boolean({
216
+ char: 'y',
217
+ description: 'Confirm using --env when it targets a different env than the current env',
218
+ default: false,
219
+ }),
214
220
  'skip-code-update': Flags.boolean({
215
221
  char: 's',
216
222
  description: 'Restart with the saved local code or Docker image without downloading updates first',
@@ -544,6 +550,17 @@ export default class AppUpgrade extends Command {
544
550
  const { flags } = await this.parse(AppUpgrade);
545
551
  const parsed = flags;
546
552
  const requestedEnv = parsed.env?.trim() || undefined;
553
+ if (requestedEnv && hasExplicitEnvSelection(this.argv)) {
554
+ const confirmed = await ensureCrossEnvConfirmed({
555
+ command: this,
556
+ requestedEnv,
557
+ yes: parsed.yes,
558
+ });
559
+ if (!confirmed) {
560
+ this.log('Canceled.');
561
+ return;
562
+ }
563
+ }
547
564
  const commandStdio = parsed.verbose ? 'inherit' : 'ignore';
548
565
  const runtime = await resolveManagedAppRuntime(requestedEnv);
549
566
  if (!runtime) {
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Args, Command, Flags } from '@oclif/core';
10
- import { upsertEnv } from '../../lib/auth-store.js';
10
+ import { setCurrentEnv, upsertEnv } from '../../lib/auth-store.js';
11
11
  import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
12
12
  import { buildStoredEnvConfig, } from '../../lib/env-config.js';
13
13
  import { runPromptCatalog, } from '../../lib/prompt-catalog.js';
@@ -303,6 +303,7 @@ export default class EnvAdd extends Command {
303
303
  const envConfig = this.buildEnvConfig(results, parsedFlags);
304
304
  printVerbose(`Saving env "${envName}" globally.`);
305
305
  await upsertEnv(envName, envConfig, { scope: resolveDefaultConfigScope() });
306
+ await setCurrentEnv(envName, { scope: resolveDefaultConfigScope() });
306
307
  if (results.authType === 'oauth') {
307
308
  await this.config.runCommand('env:auth', [envName]);
308
309
  }
@@ -0,0 +1,21 @@
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 { getCurrentEnvName } from '../../lib/auth-store.js';
11
+ import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
12
+ export default class EnvCurrent extends Command {
13
+ static summary = 'Show the current environment name';
14
+ static examples = [
15
+ '<%= config.bin %> <%= command.id %>',
16
+ ];
17
+ async run() {
18
+ await this.parse(EnvCurrent);
19
+ this.log(await getCurrentEnvName({ scope: resolveDefaultConfigScope() }));
20
+ }
21
+ }
@@ -7,20 +7,20 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command } from '@oclif/core';
10
- import { resolveManagedAppRuntime } from '../../lib/app-runtime.js';
11
- import { listEnvs } from '../../lib/auth-store.js';
10
+ import { getCurrentEnvName, listEnvs } from '../../lib/auth-store.js';
12
11
  import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
13
12
  import { renderTable } from '../../lib/ui.js';
14
- import { apiStatus, appUrl, resolveApiBaseUrl } from './shared.js';
13
+ import { resolveApiBaseUrl } from './shared.js';
15
14
  export default class EnvList extends Command {
16
- static summary = 'List configured environments and API auth status';
15
+ static summary = 'List configured environments';
17
16
  static examples = [
18
17
  '<%= config.bin %> <%= command.id %>',
19
18
  ];
20
19
  async run() {
21
20
  await this.parse(EnvList);
22
21
  const scope = resolveDefaultConfigScope();
23
- const { currentEnv, envs } = await listEnvs({ scope });
22
+ const { envs } = await listEnvs({ scope });
23
+ const currentEnv = await getCurrentEnvName({ scope });
24
24
  const names = Object.keys(envs).sort();
25
25
  if (!names.length) {
26
26
  this.log('No envs configured.');
@@ -30,21 +30,15 @@ export default class EnvList extends Command {
30
30
  const rows = [];
31
31
  for (const name of names) {
32
32
  const env = envs[name];
33
- const runtime = await resolveManagedAppRuntime(name);
34
- const statusConfig = {
35
- ...env,
36
- ...(runtime?.env.config ?? {}),
37
- };
38
33
  rows.push([
39
34
  name === currentEnv ? '*' : '',
40
35
  name,
41
- runtime?.kind ?? env.kind ?? '-',
42
- await apiStatus(name, statusConfig, { scope }),
43
- runtime ? appUrl(runtime) : resolveApiBaseUrl(env),
36
+ env.kind ?? '-',
37
+ resolveApiBaseUrl(env),
44
38
  env.auth?.type ?? '',
45
39
  env.runtime?.version ?? '',
46
40
  ]);
47
41
  }
48
- this.log(renderTable(['Current', 'Name', 'Kind', 'App Status', 'URL', 'Auth', 'Runtime'], rows));
42
+ this.log(renderTable(['Current', 'Name', 'Kind', 'API Base URL', 'Auth', 'Runtime'], rows));
49
43
  }
50
44
  }
@@ -51,7 +51,7 @@ export default class EnvRemove extends Command {
51
51
  const result = await removeEnv(args.name, { scope: resolveDefaultConfigScope() });
52
52
  this.log(`Removed env "${result.removed}".`);
53
53
  if (result.hasEnvs) {
54
- this.log(`Current env: ${result.currentEnv}`);
54
+ this.log(`Current env: ${await getCurrentEnvName({ scope: resolveDefaultConfigScope() })}`);
55
55
  return;
56
56
  }
57
57
  this.log('No envs configured.');
@@ -0,0 +1,90 @@
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 { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
11
+ import { getCurrentEnvName, listEnvs } from '../../lib/auth-store.js';
12
+ import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
13
+ import { renderTable } from '../../lib/ui.js';
14
+ import { apiStatus, runtimeStatus } from './shared.js';
15
+ export default class EnvStatus extends Command {
16
+ static summary = 'Show application runtime status for the current env, one env, or all envs';
17
+ static examples = [
18
+ '<%= config.bin %> <%= command.id %>',
19
+ '<%= config.bin %> <%= command.id %> app1',
20
+ '<%= config.bin %> <%= command.id %> --all',
21
+ '<%= config.bin %> <%= command.id %> --all --json-output',
22
+ ];
23
+ static args = {
24
+ name: Args.string({
25
+ description: 'Configured environment name. Defaults to the current env when omitted',
26
+ required: false,
27
+ }),
28
+ };
29
+ static flags = {
30
+ all: Flags.boolean({
31
+ description: 'Show status for all configured envs',
32
+ default: false,
33
+ }),
34
+ 'json-output': Flags.boolean({
35
+ description: 'Output the result as JSON',
36
+ default: false,
37
+ }),
38
+ };
39
+ async run() {
40
+ const { args, flags } = await this.parse(EnvStatus);
41
+ const scope = resolveDefaultConfigScope();
42
+ const requestedEnv = args.name?.trim() || undefined;
43
+ if (requestedEnv && flags.all) {
44
+ this.error('`nb env status <name>` and `nb env status --all` cannot be used together.');
45
+ }
46
+ const { envs } = await listEnvs({ scope });
47
+ const configuredEnvNames = Object.keys(envs).sort();
48
+ if (!configuredEnvNames.length) {
49
+ this.log('No envs configured.');
50
+ this.log('Run `nb env add <name> --api-base-url <url>` to add one.');
51
+ return;
52
+ }
53
+ const envNames = flags.all
54
+ ? configuredEnvNames
55
+ : [requestedEnv || (await getCurrentEnvName({ scope }))];
56
+ const rows = [];
57
+ for (const envName of envNames) {
58
+ const runtime = await resolveManagedAppRuntime(envName);
59
+ if (!runtime) {
60
+ if (!flags.all) {
61
+ this.error(formatMissingManagedAppEnvMessage(envName));
62
+ }
63
+ rows.push({
64
+ env: envName,
65
+ status: 'missing',
66
+ apiBaseUrl: '',
67
+ });
68
+ continue;
69
+ }
70
+ const status = runtime.kind === 'http' || runtime.kind === 'ssh'
71
+ ? await apiStatus(envName, {
72
+ ...envs[envName],
73
+ ...(runtime.env.config ?? {}),
74
+ }, { scope })
75
+ : await runtimeStatus(runtime);
76
+ rows.push({
77
+ env: runtime.envName,
78
+ status,
79
+ apiBaseUrl: runtime.env.apiBaseUrl
80
+ || String(runtime.env.config.apiBaseUrl ?? runtime.env.config.baseUrl ?? envs[envName]?.apiBaseUrl ?? envs[envName]?.baseUrl ?? '').trim(),
81
+ });
82
+ }
83
+ if (flags['json-output']) {
84
+ this.log(JSON.stringify(flags.all ? rows : rows[0], null, 2));
85
+ return;
86
+ }
87
+ const tableRows = rows.map((row) => [row.env, row.status, row.apiBaseUrl]);
88
+ this.log(renderTable(['Env', 'Status', 'API Base URL'], tableRows));
89
+ }
90
+ }
@@ -9,6 +9,8 @@
9
9
  import { Args, Command } from '@oclif/core';
10
10
  import { setCurrentEnv } from '../../lib/auth-store.js';
11
11
  import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
12
+ import { resolveSessionIdentity } from '../../lib/session-id.js';
13
+ import { isInteractiveTerminal, printInfo } from '../../lib/ui.js';
12
14
  export default class EnvUse extends Command {
13
15
  static summary = 'Switch the current environment';
14
16
  static examples = [
@@ -24,5 +26,13 @@ export default class EnvUse extends Command {
24
26
  const { args } = await this.parse(EnvUse);
25
27
  await setCurrentEnv(args.name, { scope: resolveDefaultConfigScope() });
26
28
  this.log(`Current env: ${args.name}`);
29
+ const identity = resolveSessionIdentity();
30
+ if (identity || !isInteractiveTerminal()) {
31
+ return;
32
+ }
33
+ this.log('Session mode is not enabled for the current shell or runtime.');
34
+ this.log('Without session mode, switching the current env here can affect other sessions running in parallel.');
35
+ this.log('');
36
+ printInfo('Run `nb session setup` to enable session mode for this shell or runtime.');
27
37
  }
28
38
  }
@@ -24,7 +24,7 @@ import { validateExternalDbConfig } from "../lib/db-connection-check.js";
24
24
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
25
25
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
26
26
  import { startTask, stopTask, updateTask } from '../lib/ui.js';
27
- import { getEnv, upsertEnv } from '../lib/auth-store.js';
27
+ import { getEnv, setCurrentEnv, upsertEnv } from '../lib/auth-store.js';
28
28
  import { buildStoredEnvConfig } from '../lib/env-config.js';
29
29
  import Download, { defaultDockerRegistryForLang, } from './download.js';
30
30
  import EnvAdd from "./env/add.js";
@@ -1908,6 +1908,7 @@ export default class Install extends Command {
1908
1908
  }
1909
1909
  async saveInstalledEnv(params) {
1910
1910
  await upsertEnv(params.envName, Install.buildSavedEnvConfig(params), { scope: resolveDefaultConfigScope() });
1911
+ await setCurrentEnv(params.envName, { scope: resolveDefaultConfigScope() });
1911
1912
  }
1912
1913
  async syncInstalledEnvConnection(params) {
1913
1914
  if (!params.appReady) {