@nocobase/cli 2.1.0-beta.29 → 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.
Files changed (50) hide show
  1. package/README.md +14 -0
  2. package/README.zh-CN.md +14 -0
  3. package/bin/run.js +3 -0
  4. package/bin/session-env.js +27 -0
  5. package/dist/commands/app/down.js +47 -9
  6. package/dist/commands/app/logs.js +17 -0
  7. package/dist/commands/app/restart.js +23 -1
  8. package/dist/commands/app/start.js +17 -0
  9. package/dist/commands/app/stop.js +17 -0
  10. package/dist/commands/app/upgrade.js +22 -2
  11. package/dist/commands/db/check.js +6 -4
  12. package/dist/commands/db/ps.js +1 -1
  13. package/dist/commands/env/add.js +3 -2
  14. package/dist/commands/env/auth.js +1 -1
  15. package/dist/commands/env/current.js +21 -0
  16. package/dist/commands/env/info.js +4 -3
  17. package/dist/commands/env/list.js +8 -14
  18. package/dist/commands/env/remove.js +2 -2
  19. package/dist/commands/env/status.js +90 -0
  20. package/dist/commands/env/update.js +1 -1
  21. package/dist/commands/env/use.js +11 -1
  22. package/dist/commands/install.js +10 -4
  23. package/dist/commands/license/activate.js +20 -24
  24. package/dist/commands/license/id.js +17 -2
  25. package/dist/commands/license/plugins/clean.js +17 -2
  26. package/dist/commands/license/plugins/list.js +17 -2
  27. package/dist/commands/license/plugins/sync.js +22 -5
  28. package/dist/commands/license/shared.js +15 -6
  29. package/dist/commands/license/status.js +17 -2
  30. package/dist/commands/plugin/disable.js +25 -4
  31. package/dist/commands/plugin/enable.js +25 -4
  32. package/dist/commands/plugin/list.js +25 -4
  33. package/dist/commands/session/id.js +24 -0
  34. package/dist/commands/session/remove.js +57 -0
  35. package/dist/commands/session/setup.js +62 -0
  36. package/dist/commands/source/dev.js +19 -1
  37. package/dist/commands/source/download.js +10 -8
  38. package/dist/lib/app-managed-resources.js +5 -3
  39. package/dist/lib/app-runtime.js +1 -1
  40. package/dist/lib/auth-store.js +28 -11
  41. package/dist/lib/docker-image.js +37 -0
  42. package/dist/lib/env-guard.js +61 -0
  43. package/dist/lib/generated-command.js +16 -0
  44. package/dist/lib/plugin-storage.js +1 -64
  45. package/dist/lib/resource-command.js +15 -0
  46. package/dist/lib/runtime-generator.js +1 -1
  47. package/dist/lib/session-id.js +17 -0
  48. package/dist/lib/session-integration.js +703 -0
  49. package/dist/lib/session-store.js +118 -0
  50. package/package.json +3 -3
@@ -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 to inspect. Defaults to the current env when omitted; cannot be used with --all',
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
+ }
@@ -22,7 +22,7 @@ export default class EnvUpdate extends Command {
22
22
  ];
23
23
  static args = {
24
24
  name: Args.string({
25
- description: 'Environment name (omit to use the current env)',
25
+ description: 'Configured environment name to refresh. Defaults to the current env when omitted',
26
26
  required: false,
27
27
  }),
28
28
  };
@@ -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 = [
@@ -16,7 +18,7 @@ export default class EnvUse extends Command {
16
18
  ];
17
19
  static args = {
18
20
  name: Args.string({
19
- description: 'Configured environment name',
21
+ description: 'Configured environment name to switch to',
20
22
  required: true,
21
23
  }),
22
24
  };
@@ -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
  }
@@ -19,12 +19,13 @@ import { applyCliLocale, localeText, resolveCliLocale, translateCli, } from "../
19
19
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
20
20
  import { defaultDockerContainerPrefix, defaultDockerNetworkName, } from '../lib/app-runtime.js';
21
21
  import { resolveDockerContainerPrefix, resolveDockerNetworkName, } from '../lib/cli-config.js';
22
+ import { DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../lib/docker-image.js";
22
23
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
23
24
  import { validateExternalDbConfig } from "../lib/db-connection-check.js";
24
25
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
25
26
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
26
27
  import { startTask, stopTask, updateTask } from '../lib/ui.js';
27
- import { getEnv, upsertEnv } from '../lib/auth-store.js';
28
+ import { getEnv, setCurrentEnv, upsertEnv } from '../lib/auth-store.js';
28
29
  import { buildStoredEnvConfig } from '../lib/env-config.js';
29
30
  import Download, { defaultDockerRegistryForLang, } from './download.js';
30
31
  import EnvAdd from "./env/add.js";
@@ -1561,7 +1562,11 @@ export default class Install extends Command {
1561
1562
  static buildDockerAppPlan(params) {
1562
1563
  const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim()
1563
1564
  || defaultDockerRegistryForLang(process.env.NB_LOCALE);
1564
- const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || 'latest';
1565
+ const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
1566
+ const imageRef = resolveDockerImageRef(dockerRegistry, version, {
1567
+ defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
1568
+ defaultVersion: DEFAULT_DOCKER_VERSION,
1569
+ });
1565
1570
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1566
1571
  const storagePath = resolveConfiguredEnvPath(String(params.appResults.storagePath ?? '').trim()
1567
1572
  || defaultInstallStoragePath(params.envName))
@@ -1596,12 +1601,12 @@ export default class Install extends Command {
1596
1601
  for (const [key, value] of Object.entries(initEnvVars)) {
1597
1602
  args.push('-e', `${key}=${value}`);
1598
1603
  }
1599
- args.push('-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:/app/nocobase/storage`, `${dockerRegistry}:${version}`);
1604
+ args.push('-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:/app/nocobase/storage`, imageRef);
1600
1605
  return {
1601
1606
  source: 'docker',
1602
1607
  networkName: params.networkName,
1603
1608
  containerName,
1604
- imageRef: `${dockerRegistry}:${version}`,
1609
+ imageRef,
1605
1610
  appPort,
1606
1611
  storagePath,
1607
1612
  appKey,
@@ -1908,6 +1913,7 @@ export default class Install extends Command {
1908
1913
  }
1909
1914
  async saveInstalledEnv(params) {
1910
1915
  await upsertEnv(params.envName, Install.buildSavedEnvConfig(params), { scope: resolveDefaultConfigScope() });
1916
+ await setCurrentEnv(params.envName, { scope: resolveDefaultConfigScope() });
1911
1917
  }
1912
1918
  async syncInstalledEnvConnection(params) {
1913
1919
  if (!params.appReady) {
@@ -9,7 +9,8 @@
9
9
  import * as p from '@clack/prompts';
10
10
  import { Command, Flags } from '@oclif/core';
11
11
  import { readFile } from 'node:fs/promises';
12
- import { ensureInstanceId, licenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, redactLicenseKey, requireLicenseRuntime, resolveLicenseKeyFile, resolveLicenseServiceUrl, saveLicenseKey, sanitizeLicenseOutput, validateLicenseKey, } from './shared.js';
12
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
13
+ import { createLicenseEnvFlag, ensureInstanceId, licenseJsonFlag, licensePkgUrlFlag, licenseYesFlag, redactLicenseKey, requireLicenseRuntime, resolveLicenseKeyFile, resolveLicenseServiceUrl, saveLicenseKey, sanitizeLicenseOutput, validateLicenseKey, } from './shared.js';
13
14
  import { announceTargetEnv, isInteractiveTerminal } from '../../lib/ui.js';
14
15
  import { appUrl } from '../env/shared.js';
15
16
  function resolveOnlineInputValue(value) {
@@ -114,21 +115,10 @@ async function promptOnlineActivationInput(initial) {
114
115
  p.cancel('License activation cancelled.');
115
116
  return;
116
117
  }
117
- const confirmedAnswer = typeof initial.confirmed === 'boolean'
118
- ? initial.confirmed
119
- : await p.confirm({
120
- message: 'Confirm that the submitted license information is true and accurate?',
121
- initialValue: false,
122
- });
123
- if (p.isCancel(confirmedAnswer)) {
124
- p.cancel('License activation cancelled.');
125
- return;
126
- }
127
118
  return {
128
119
  account,
129
120
  password,
130
121
  appName,
131
- confirmed: Boolean(confirmedAnswer),
132
122
  serviceUrl: await resolveLicenseServiceUrl(initial.serviceUrl),
133
123
  };
134
124
  }
@@ -176,11 +166,12 @@ export default class LicenseActivate extends Command {
176
166
  '<%= config.bin %> <%= command.id %> --env app1 --key <licenseKey>',
177
167
  '<%= config.bin %> <%= command.id %> --env app1 --key-file ./license.txt',
178
168
  '<%= config.bin %> <%= command.id %> --env app1 --online',
169
+ '<%= config.bin %> <%= command.id %> --env app1 --online --account aa --password bb --desc test24',
179
170
  '<%= config.bin %> <%= command.id %> --env app1 --online --account aa --password bb --desc test24 --yes',
180
171
  '<%= config.bin %> <%= command.id %> --env app1 --json --key-file ./license.txt',
181
172
  ];
182
173
  static flags = {
183
- env: licenseEnvFlag,
174
+ env: createLicenseEnvFlag('CLI env name to activate a license for. Defaults to the current env when omitted'),
184
175
  json: licenseJsonFlag,
185
176
  key: Flags.string({
186
177
  description: 'Existing license key to activate',
@@ -202,13 +193,23 @@ export default class LicenseActivate extends Command {
202
193
  description: 'Application name for online activation',
203
194
  }),
204
195
  'pkg-url': licensePkgUrlFlag,
205
- yes: Flags.boolean({
206
- description: 'Confirm that the submitted application information is true and accurate',
207
- default: false,
208
- }),
196
+ yes: licenseYesFlag,
209
197
  };
210
198
  async run() {
211
199
  const { flags } = await this.parse(LicenseActivate);
200
+ const requestedEnv = flags.env?.trim() || undefined;
201
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv ?? []));
202
+ if (explicitEnvSelection) {
203
+ const confirmed = await ensureCrossEnvConfirmed({
204
+ command: this,
205
+ requestedEnv,
206
+ yes: flags.yes,
207
+ });
208
+ if (!confirmed) {
209
+ this.log('Canceled.');
210
+ return;
211
+ }
212
+ }
212
213
  const runtime = await requireLicenseRuntime(flags.env);
213
214
  if (!flags.json) {
214
215
  announceTargetEnv(runtime.envName);
@@ -246,16 +247,14 @@ export default class LicenseActivate extends Command {
246
247
  account: resolveOnlineInputValue(flags.account),
247
248
  password: resolveOnlineInputValue(flags.password),
248
249
  appName: resolveOnlineInputValue(flags.desc),
249
- confirmed: flags.yes ? true : undefined,
250
250
  serviceUrl: resolvedServiceUrl,
251
251
  };
252
252
  let onlineInput = initialOnline;
253
253
  if (!onlineInput.account
254
254
  || !onlineInput.password
255
- || !onlineInput.appName
256
- || !onlineInput.confirmed) {
255
+ || !onlineInput.appName) {
257
256
  if (!isInteractiveTerminal()) {
258
- this.error('Online activation requires --account, --password, --desc, and --yes when not using a TTY.');
257
+ this.error('Online activation requires --account, --password, and --desc when not using a TTY.');
259
258
  }
260
259
  const prompted = await promptOnlineActivationInput(initialOnline);
261
260
  if (!prompted) {
@@ -264,9 +263,6 @@ export default class LicenseActivate extends Command {
264
263
  }
265
264
  onlineInput = prompted;
266
265
  }
267
- if (!onlineInput.confirmed) {
268
- this.error('Online activation requires confirmation that the submitted application information is true and accurate.');
269
- }
270
266
  const instanceId = await ensureInstanceId(runtime);
271
267
  const resolvedAppUrl = resolveAppUrlOrThrow(runtime);
272
268
  const resolvedKey = await requestOnlineLicenseKey(onlineInput.serviceUrl, onlineInput.account, onlineInput.password, {
@@ -7,7 +7,8 @@
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 { generateAndSaveInstanceId, licenseEnvFlag, licenseJsonFlag, readSavedInstanceId, requireLicenseRuntime, resolveInstanceIdFile, } from './shared.js';
10
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
11
+ import { createLicenseEnvFlag, generateAndSaveInstanceId, licenseJsonFlag, licenseYesFlag, readSavedInstanceId, requireLicenseRuntime, resolveInstanceIdFile, } from './shared.js';
11
12
  import { announceTargetEnv } from '../../lib/ui.js';
12
13
  export default class LicenseId extends Command {
13
14
  static summary = 'Show the instance ID for the selected env';
@@ -19,8 +20,9 @@ export default class LicenseId extends Command {
19
20
  '<%= config.bin %> <%= command.id %> --env app1 --json',
20
21
  ];
21
22
  static flags = {
22
- env: licenseEnvFlag,
23
+ env: createLicenseEnvFlag('CLI env name to inspect. Defaults to the current env when omitted'),
23
24
  json: licenseJsonFlag,
25
+ yes: licenseYesFlag,
24
26
  force: Flags.boolean({
25
27
  description: 'Force regenerate the instance ID even if one is already saved',
26
28
  default: false,
@@ -28,6 +30,19 @@ export default class LicenseId extends Command {
28
30
  };
29
31
  async run() {
30
32
  const { flags } = await this.parse(LicenseId);
33
+ const requestedEnv = flags.env?.trim() || undefined;
34
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv ?? []));
35
+ if (explicitEnvSelection) {
36
+ const confirmed = await ensureCrossEnvConfirmed({
37
+ command: this,
38
+ requestedEnv,
39
+ yes: flags.yes,
40
+ });
41
+ if (!confirmed) {
42
+ this.log('Canceled.');
43
+ return;
44
+ }
45
+ }
31
46
  const runtime = await requireLicenseRuntime(flags.env);
32
47
  if (!flags.json) {
33
48
  announceTargetEnv(runtime.envName);
@@ -8,7 +8,8 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import pc from 'picocolors';
11
- import { licenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, requireLicenseRuntime } from '../shared.js';
11
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../../lib/env-guard.js';
12
+ import { createLicenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, licenseYesFlag, requireLicenseRuntime, } from '../shared.js';
12
13
  import { cleanLicensedPlugins } from './shared.js';
13
14
  import { resolvePluginStoragePath } from '../../../lib/plugin-storage.js';
14
15
  import { announceTargetEnv } from '../../../lib/ui.js';
@@ -31,7 +32,7 @@ export default class LicensePluginsClean extends Command {
31
32
  '<%= config.bin %> <%= command.id %> --env app1 --json',
32
33
  ];
33
34
  static flags = {
34
- env: licenseEnvFlag,
35
+ env: createLicenseEnvFlag('CLI env name to clean licensed plugins for. Defaults to the current env when omitted'),
35
36
  json: licenseJsonFlag,
36
37
  'pkg-url': licensePkgUrlFlag,
37
38
  'dry-run': Flags.boolean({
@@ -42,9 +43,23 @@ export default class LicensePluginsClean extends Command {
42
43
  description: 'Show detailed per-plugin clean logs',
43
44
  default: false,
44
45
  }),
46
+ yes: licenseYesFlag,
45
47
  };
46
48
  async run() {
47
49
  const { flags } = await this.parse(LicensePluginsClean);
50
+ const requestedEnv = flags.env?.trim() || undefined;
51
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv ?? []));
52
+ if (explicitEnvSelection) {
53
+ const confirmed = await ensureCrossEnvConfirmed({
54
+ command: this,
55
+ requestedEnv,
56
+ yes: flags.yes,
57
+ });
58
+ if (!confirmed) {
59
+ this.log('Canceled.');
60
+ return;
61
+ }
62
+ }
48
63
  const runtime = await requireLicenseRuntime(flags.env);
49
64
  if (!flags.json) {
50
65
  announceTargetEnv(runtime.envName);
@@ -7,7 +7,8 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command } from '@oclif/core';
10
- import { licenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, requireLicenseRuntime } from '../shared.js';
10
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../../lib/env-guard.js';
11
+ import { createLicenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, licenseYesFlag, requireLicenseRuntime, } from '../shared.js';
11
12
  import { fetchLicensedPluginPackages } from './shared.js';
12
13
  import { renderTable } from '../../../lib/ui.js';
13
14
  export default class LicensePluginsList extends Command {
@@ -19,12 +20,26 @@ export default class LicensePluginsList extends Command {
19
20
  '<%= config.bin %> <%= command.id %> --env app1 --json',
20
21
  ];
21
22
  static flags = {
22
- env: licenseEnvFlag,
23
+ env: createLicenseEnvFlag('CLI env name to inspect licensed plugins for. Defaults to the current env when omitted'),
23
24
  json: licenseJsonFlag,
24
25
  'pkg-url': licensePkgUrlFlag,
26
+ yes: licenseYesFlag,
25
27
  };
26
28
  async run() {
27
29
  const { flags } = await this.parse(LicensePluginsList);
30
+ const requestedEnv = flags.env?.trim() || undefined;
31
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv ?? []));
32
+ if (explicitEnvSelection) {
33
+ const confirmed = await ensureCrossEnvConfirmed({
34
+ command: this,
35
+ requestedEnv,
36
+ yes: flags.yes,
37
+ });
38
+ if (!confirmed) {
39
+ this.log('Canceled.');
40
+ return;
41
+ }
42
+ }
28
43
  const runtime = await requireLicenseRuntime(flags.env);
29
44
  const { commercialPlugins, licensedPlugins } = await fetchLicensedPluginPackages(runtime, {
30
45
  pkgUrl: flags['pkg-url'],
@@ -10,7 +10,9 @@ import { Command, Flags } from '@oclif/core';
10
10
  import pc from 'picocolors';
11
11
  import { readFile } from 'node:fs/promises';
12
12
  import path from 'node:path';
13
- import { licenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, requireLicenseRuntime } from '../shared.js';
13
+ import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../../../lib/docker-image.js";
14
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../../lib/env-guard.js';
15
+ import { createLicenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, licenseYesFlag, requireLicenseRuntime, } from '../shared.js';
14
16
  import { syncLicensedPlugins } from './shared.js';
15
17
  import { resolvePluginStoragePath } from '../../../lib/plugin-storage.js';
16
18
  import { commandOutput } from '../../../lib/run-npm.js';
@@ -18,8 +20,6 @@ import { announceTargetEnv, startTask, stopTask, succeedTask, updateTask } from
18
20
  const SYNC_LOADING_DELAY_MS = 1200;
19
21
  const SYNC_LOADING_UPDATE_MS = 5000;
20
22
  const LOCAL_APP_PACKAGE_JSON_PATH = 'node_modules/@nocobase/app/package.json';
21
- const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
22
- const DEFAULT_DOCKER_VERSION = 'alpha';
23
23
  function formatActionLabel(action) {
24
24
  switch (action) {
25
25
  case 'installed':
@@ -89,7 +89,10 @@ async function resolveLocalAppVersion(runtime) {
89
89
  }
90
90
  async function resolveDockerAppVersion(runtime) {
91
91
  const config = runtime.env.config ?? {};
92
- const imageRef = `${trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY}:${trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION}`;
92
+ const imageRef = resolveDockerImageRef(config.dockerRegistry, config.downloadVersion, {
93
+ defaultRegistry: DEFAULT_DOCKER_REGISTRY,
94
+ defaultVersion: DEFAULT_DOCKER_VERSION,
95
+ });
93
96
  const args = [
94
97
  'run',
95
98
  '--rm',
@@ -137,7 +140,7 @@ export default class LicensePluginsSync extends Command {
137
140
  '<%= config.bin %> <%= command.id %> --env app1 --json',
138
141
  ];
139
142
  static flags = {
140
- env: licenseEnvFlag,
143
+ env: createLicenseEnvFlag('CLI env name to sync licensed plugins for. Defaults to the current env when omitted'),
141
144
  json: licenseJsonFlag,
142
145
  'pkg-url': licensePkgUrlFlag,
143
146
  'dry-run': Flags.boolean({
@@ -151,9 +154,23 @@ export default class LicensePluginsSync extends Command {
151
154
  description: 'Show detailed per-plugin sync logs',
152
155
  default: false,
153
156
  }),
157
+ yes: licenseYesFlag,
154
158
  };
155
159
  async run() {
156
160
  const { flags } = await this.parse(LicensePluginsSync);
161
+ const requestedEnv = flags.env?.trim() || undefined;
162
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv ?? []));
163
+ if (explicitEnvSelection) {
164
+ const confirmed = await ensureCrossEnvConfirmed({
165
+ command: this,
166
+ requestedEnv,
167
+ yes: flags.yes,
168
+ });
169
+ if (!confirmed) {
170
+ this.log('Canceled.');
171
+ return;
172
+ }
173
+ }
157
174
  const runtime = await requireLicenseRuntime(flags.env);
158
175
  if (!flags.json) {
159
176
  announceTargetEnv(runtime.envName);
@@ -12,22 +12,28 @@ import path from 'node:path';
12
12
  import { getEnvAsync, getInstanceIdAsync, keyDecrypt } from '@nocobase/license-kit';
13
13
  import _ from 'lodash';
14
14
  import { checkExternalDbConnection, readExternalDbConnectionConfig, } from "../../lib/db-connection-check.js";
15
+ import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../../lib/docker-image.js";
15
16
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
16
17
  import { buildRuntimeEnvVars } from '../../lib/runtime-env-vars.js';
17
18
  import { resolveLicensePkgUrlFromConfig } from '../../lib/cli-config.js';
18
19
  import { commandOutput } from '../../lib/run-npm.js';
19
20
  import { appUrl } from '../env/shared.js';
20
- export const licenseEnvFlag = Flags.string({
21
- char: 'e',
22
- description: 'CLI env name (from `nb env` / `nb init`). Defaults to the current env when omitted',
21
+ export function createLicenseEnvFlag(description) {
22
+ return Flags.string({
23
+ char: 'e',
24
+ description,
25
+ });
26
+ }
27
+ export const licenseYesFlag = Flags.boolean({
28
+ char: 'y',
29
+ description: 'Confirm using --env when it targets a different env than the current env',
30
+ default: false,
23
31
  });
24
32
  export const licenseJsonFlag = Flags.boolean({
25
33
  description: 'Output the result as JSON',
26
34
  default: false,
27
35
  });
28
36
  const DEFAULT_LICENSE_PKG_URL = 'https://pkg.nocobase.com/';
29
- const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
30
- const DEFAULT_DOCKER_VERSION = 'alpha';
31
37
  export const licensePkgUrlFlag = Flags.string({
32
38
  description: 'Commercial package service base URL',
33
39
  hidden: true,
@@ -74,7 +80,10 @@ function normalizeDockerPlatform(value) {
74
80
  }
75
81
  function resolveDockerLicenseImageRef(runtime) {
76
82
  const config = runtime.env.config ?? {};
77
- return `${trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY}:${trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION}`;
83
+ return resolveDockerImageRef(config.dockerRegistry, config.downloadVersion, {
84
+ defaultRegistry: DEFAULT_DOCKER_REGISTRY,
85
+ defaultVersion: DEFAULT_DOCKER_VERSION,
86
+ });
78
87
  }
79
88
  function buildDockerLicenseDbFlagArgs(envVars) {
80
89
  return [
@@ -7,7 +7,8 @@
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 { ensureInstanceId, licenseEnvFlag, licenseJsonFlag, requireLicenseRuntime } from './shared.js';
10
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
11
+ import { createLicenseEnvFlag, ensureInstanceId, licenseJsonFlag, licenseYesFlag, requireLicenseRuntime } from './shared.js';
11
12
  export default class LicenseStatus extends Command {
12
13
  static summary = 'Show commercial license status for the selected env';
13
14
  static description = 'Inspect the selected env and show the current commercial licensing status. Use `--doctor` for extra diagnostic checks once the license backend wiring is implemented.';
@@ -18,8 +19,9 @@ export default class LicenseStatus extends Command {
18
19
  '<%= config.bin %> <%= command.id %> --env app1 --json',
19
20
  ];
20
21
  static flags = {
21
- env: licenseEnvFlag,
22
+ env: createLicenseEnvFlag('CLI env name to inspect. Defaults to the current env when omitted'),
22
23
  json: licenseJsonFlag,
24
+ yes: licenseYesFlag,
23
25
  doctor: Flags.boolean({
24
26
  description: 'Run extra diagnostic checks and suggestions',
25
27
  default: false,
@@ -27,6 +29,19 @@ export default class LicenseStatus extends Command {
27
29
  };
28
30
  async run() {
29
31
  const { flags } = await this.parse(LicenseStatus);
32
+ const requestedEnv = flags.env?.trim() || undefined;
33
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv ?? []));
34
+ if (explicitEnvSelection) {
35
+ const confirmed = await ensureCrossEnvConfirmed({
36
+ command: this,
37
+ requestedEnv,
38
+ yes: flags.yes,
39
+ });
40
+ if (!confirmed) {
41
+ this.log('Canceled.');
42
+ return;
43
+ }
44
+ }
30
45
  const runtime = await requireLicenseRuntime(flags.env);
31
46
  const payload = {
32
47
  ok: true,
@@ -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 PluginDisable extends Command {
13
14
  static hidden = false;
@@ -27,18 +28,36 @@ export default class PluginDisable extends Command {
27
28
  static flags = {
28
29
  env: Flags.string({
29
30
  char: 'e',
30
- description: 'CLI env name (from `nb env` / `nb init`). Defaults to the current env when omitted',
31
+ description: 'CLI env name to disable 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(PluginDisable);
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(flags.env);
58
+ const runtime = await resolveManagedAppRuntime(requestedEnv);
40
59
  if (!runtime) {
41
- this.error(formatMissingManagedAppEnvMessage(flags.env));
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 PluginDisable extends Command {
61
80
  }
62
81
  return;
63
82
  }
64
- await this.config.runCommand('api:pm:disable', ['--await-response', '--filter-by-tk', packages.join(',')]);
83
+ await this.config.runCommand('api:pm:disable', 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
  }