@nocobase/cli 2.1.0-alpha.40 → 2.1.0-alpha.46

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 (56) hide show
  1. package/README.md +20 -8
  2. package/README.zh-CN.md +20 -8
  3. package/bin/run.js +9 -37
  4. package/dist/commands/app/destroy.js +225 -0
  5. package/dist/commands/app/down.js +24 -254
  6. package/dist/commands/app/restart.js +12 -5
  7. package/dist/commands/app/shared.js +122 -0
  8. package/dist/commands/app/start.js +41 -59
  9. package/dist/commands/app/stop.js +72 -26
  10. package/dist/commands/app/upgrade.js +312 -429
  11. package/dist/commands/config/delete.js +8 -1
  12. package/dist/commands/config/get.js +5 -0
  13. package/dist/commands/config/set.js +10 -1
  14. package/dist/commands/db/ps.js +5 -18
  15. package/dist/commands/db/start.js +23 -8
  16. package/dist/commands/env/add.js +66 -6
  17. package/dist/commands/env/auth.js +86 -27
  18. package/dist/commands/env/info.js +52 -8
  19. package/dist/commands/env/list.js +6 -7
  20. package/dist/commands/env/shared.js +41 -3
  21. package/dist/commands/env/status.js +10 -7
  22. package/dist/commands/init.js +214 -136
  23. package/dist/commands/install.js +364 -294
  24. package/dist/commands/license/plugins/shared.js +9 -3
  25. package/dist/commands/license/plugins/sync.js +54 -25
  26. package/dist/commands/self/update.js +58 -1
  27. package/dist/commands/source/dev.js +5 -5
  28. package/dist/commands/source/download.js +29 -25
  29. package/dist/generated/command-registry.js +3 -2
  30. package/dist/lib/api-client.js +6 -0
  31. package/dist/lib/api-command-compat.js +641 -0
  32. package/dist/lib/app-health.js +31 -18
  33. package/dist/lib/app-managed-resources.js +1 -1
  34. package/dist/lib/app-runtime.js +2 -2
  35. package/dist/lib/auth-store.js +56 -19
  36. package/dist/lib/backup.js +3 -3
  37. package/dist/lib/bootstrap.js +14 -8
  38. package/dist/lib/cli-config.js +137 -4
  39. package/dist/lib/cli-entry-error.js +44 -0
  40. package/dist/lib/cli-locale.js +19 -7
  41. package/dist/lib/db-connection-check.js +61 -0
  42. package/dist/lib/docker-log-stream.js +45 -0
  43. package/dist/lib/env-auth.js +98 -11
  44. package/dist/lib/env-config.js +3 -2
  45. package/dist/lib/env-guard.js +1 -1
  46. package/dist/lib/generated-command.js +17 -0
  47. package/dist/lib/prompt-validators.js +23 -5
  48. package/dist/lib/prompt-web-ui.js +143 -19
  49. package/dist/lib/run-npm.js +133 -23
  50. package/dist/lib/skills-manager.js +80 -4
  51. package/dist/lib/source-publish.js +4 -1
  52. package/dist/lib/startup-update.js +74 -50
  53. package/dist/lib/ui.js +4 -1
  54. package/dist/locale/en-US.json +92 -5
  55. package/dist/locale/zh-CN.json +92 -5
  56. package/package.json +34 -2
@@ -11,8 +11,14 @@ import { access, mkdir, rm } from 'node:fs/promises';
11
11
  import { Readable } from 'node:stream';
12
12
  import { createGunzip } from 'node:zlib';
13
13
  import * as tar from 'tar';
14
- import { removeStoragePluginSymlink, resolvePluginStoragePath, } from '../../../lib/plugin-storage.js';
15
- import { parseLicenseKey, readSavedLicenseKey, resolveLicensePkgUrl, } from '../shared.js';
14
+ import { removeStoragePluginSymlink, resolvePluginStoragePath } from '../../../lib/plugin-storage.js';
15
+ import { parseLicenseKey, readSavedLicenseKey, resolveLicensePkgUrl } from '../shared.js';
16
+ export class MissingSavedLicenseKeyError extends Error {
17
+ constructor(envName) {
18
+ super(`No saved license key was found for env "${envName}". Run \`nb license activate\` first.`);
19
+ this.name = 'MissingSavedLicenseKeyError';
20
+ }
21
+ }
16
22
  async function resolvePkgBaseUrl(pkgUrl) {
17
23
  return await resolveLicensePkgUrl(pkgUrl);
18
24
  }
@@ -54,7 +60,7 @@ async function loginPkg(baseURL, keyData) {
54
60
  export async function loadSavedLicenseKeyData(runtime) {
55
61
  const licenseKey = await readSavedLicenseKey(runtime);
56
62
  if (!licenseKey) {
57
- throw new Error(`No saved license key was found for env "${runtime.envName}". Run \`nb license activate\` first.`);
63
+ throw new MissingSavedLicenseKeyError(runtime.envName);
58
64
  }
59
65
  return parseLicenseKey(licenseKey);
60
66
  }
@@ -10,10 +10,10 @@ 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 { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../../../lib/docker-image.js";
13
+ import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../../../lib/docker-image.js";
14
14
  import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../../lib/env-guard.js';
15
- import { createLicenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, licenseYesFlag, requireLicenseRuntime, } from '../shared.js';
16
- import { syncLicensedPlugins } from './shared.js';
15
+ import { createLicenseEnvFlag, licenseJsonFlag, licensePkgUrlFlag, licenseYesFlag, readSavedLicenseKey, requireLicenseRuntime, } from '../shared.js';
16
+ import { MissingSavedLicenseKeyError, syncLicensedPlugins } from './shared.js';
17
17
  import { resolvePluginStoragePath } from '../../../lib/plugin-storage.js';
18
18
  import { commandOutput } from '../../../lib/run-npm.js';
19
19
  import { announceTargetEnv, startTask, stopTask, succeedTask, updateTask } from '../../../lib/ui.js';
@@ -93,12 +93,7 @@ async function resolveDockerAppVersion(runtime) {
93
93
  defaultRegistry: DEFAULT_DOCKER_REGISTRY,
94
94
  defaultVersion: DEFAULT_DOCKER_VERSION,
95
95
  });
96
- const args = [
97
- 'run',
98
- '--rm',
99
- '--network',
100
- runtime.workspaceName,
101
- ];
96
+ const args = ['run', '--rm', '--network', runtime.workspaceName];
102
97
  const dockerPlatform = normalizeDockerPlatform(config.dockerPlatform);
103
98
  if (dockerPlatform) {
104
99
  args.push('--platform', dockerPlatform);
@@ -130,6 +125,16 @@ async function resolveManagedAppVersion(runtime) {
130
125
  }
131
126
  throw new Error(`Env "${runtime.envName}" does not support automatic app version detection.`);
132
127
  }
128
+ function buildNoLicenseSkipPayload(runtime, dryRun) {
129
+ return {
130
+ ok: true,
131
+ env: runtime.envName,
132
+ kind: runtime.kind,
133
+ dryRun,
134
+ status: 'skipped',
135
+ skipReason: 'no-license-key',
136
+ };
137
+ }
133
138
  export default class LicensePluginsSync extends Command {
134
139
  static summary = 'Synchronize commercial plugins for the selected env';
135
140
  static description = 'Synchronize the commercial plugins allowed by the current saved license key.';
@@ -150,6 +155,10 @@ export default class LicensePluginsSync extends Command {
150
155
  version: Flags.string({
151
156
  description: 'Registry version or dist-tag to synchronize. Defaults to the current workspace version.',
152
157
  }),
158
+ 'skip-if-no-license': Flags.boolean({
159
+ description: 'Skip without error when this env does not have a saved license key',
160
+ default: false,
161
+ }),
153
162
  verbose: Flags.boolean({
154
163
  description: 'Show detailed per-plugin sync logs',
155
164
  default: false,
@@ -174,7 +183,17 @@ export default class LicensePluginsSync extends Command {
174
183
  if (!flags.json) {
175
184
  announceTargetEnv(runtime.envName);
176
185
  }
177
- const version = trimValue(flags.version) || await resolveManagedAppVersion(runtime);
186
+ if (flags['skip-if-no-license']) {
187
+ const savedLicenseKey = await readSavedLicenseKey(runtime);
188
+ if (!savedLicenseKey) {
189
+ const payload = buildNoLicenseSkipPayload(runtime, Boolean(flags['dry-run']));
190
+ if (flags.json) {
191
+ this.log(JSON.stringify(payload, null, 2));
192
+ }
193
+ return;
194
+ }
195
+ }
196
+ const version = trimValue(flags.version) || (await resolveManagedAppVersion(runtime));
178
197
  const registryVersion = normalizePluginRegistryVersion(version);
179
198
  const shouldStreamLogs = !flags.json && Boolean(flags.verbose);
180
199
  const pluginStoragePath = resolvePluginStoragePath(runtime.env.storagePath);
@@ -210,20 +229,32 @@ export default class LicensePluginsSync extends Command {
210
229
  }
211
230
  let result;
212
231
  try {
213
- result = await syncLicensedPlugins(runtime, {
214
- pkgUrl: flags['pkg-url'],
215
- version: registryVersion,
216
- dryRun: Boolean(flags['dry-run']),
217
- onProgress: shouldStreamLogs
218
- ? async (detail) => {
219
- this.log(`${formatActionLabel(detail.action)} ${pc.bold(detail.packageName)}`);
220
- this.log(pc.dim(` output: ${detail.outputDir}`));
221
- if (detail.warning) {
222
- this.log(pc.yellow(` warning: ${detail.warning}`));
232
+ try {
233
+ result = await syncLicensedPlugins(runtime, {
234
+ pkgUrl: flags['pkg-url'],
235
+ version: registryVersion,
236
+ dryRun: Boolean(flags['dry-run']),
237
+ onProgress: shouldStreamLogs
238
+ ? async (detail) => {
239
+ this.log(`${formatActionLabel(detail.action)} ${pc.bold(detail.packageName)}`);
240
+ this.log(pc.dim(` output: ${detail.outputDir}`));
241
+ if (detail.warning) {
242
+ this.log(pc.yellow(` warning: ${detail.warning}`));
243
+ }
223
244
  }
245
+ : undefined,
246
+ });
247
+ }
248
+ catch (error) {
249
+ if (flags['skip-if-no-license'] && error instanceof MissingSavedLicenseKeyError) {
250
+ const payload = buildNoLicenseSkipPayload(runtime, Boolean(flags['dry-run']));
251
+ if (flags.json) {
252
+ this.log(JSON.stringify(payload, null, 2));
224
253
  }
225
- : undefined,
226
- });
254
+ return;
255
+ }
256
+ throw error;
257
+ }
227
258
  }
228
259
  finally {
229
260
  if (loadingTimer) {
@@ -250,9 +281,7 @@ export default class LicensePluginsSync extends Command {
250
281
  return;
251
282
  }
252
283
  if (loadingStarted) {
253
- succeedTask(flags['dry-run']
254
- ? 'Commercial plugin sync preview is ready.'
255
- : 'Commercial plugin sync completed.');
284
+ succeedTask(flags['dry-run'] ? 'Commercial plugin sync preview is ready.' : 'Commercial plugin sync completed.');
256
285
  }
257
286
  if (!flags.verbose) {
258
287
  const changes = [];
@@ -8,14 +8,29 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { confirm } from "../../lib/inquirer.js";
11
+ import { updateNocoBaseSkills } from '../../lib/skills-manager.js';
11
12
  import { setVerboseMode } from '../../lib/ui.js';
12
13
  import { formatSelfUpdateUnavailableMessage, formatUnsupportedSelfUpdateMessage, inspectSelfStatus, updateSelf, } from '../../lib/self-manager.js';
14
+ function formatSkillsUpdateMessage(result, verbose) {
15
+ if (result.action === 'noop') {
16
+ if (result.reason === 'not-installed') {
17
+ return verbose
18
+ ? 'NocoBase AI coding skills are not installed globally. Run `nb skills install` first.'
19
+ : 'Skipped skills update because NocoBase AI coding skills are not installed.';
20
+ }
21
+ return verbose
22
+ ? 'NocoBase AI coding skills are already up to date globally.'
23
+ : 'NocoBase AI coding skills are up to date.';
24
+ }
25
+ return verbose ? 'Updated the global NocoBase AI coding skills.' : 'Updated NocoBase AI coding skills globally.';
26
+ }
13
27
  export default class SelfUpdate extends Command {
14
28
  static summary = 'Update the globally installed NocoBase CLI';
15
29
  static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm install.';
16
30
  static examples = [
17
31
  '<%= config.bin %> <%= command.id %>',
18
32
  '<%= config.bin %> <%= command.id %> --yes',
33
+ '<%= config.bin %> <%= command.id %> --skills',
19
34
  '<%= config.bin %> <%= command.id %> --channel alpha --json',
20
35
  ];
21
36
  static flags = {
@@ -33,6 +48,10 @@ export default class SelfUpdate extends Command {
33
48
  description: 'Output the result as JSON',
34
49
  default: false,
35
50
  }),
51
+ skills: Flags.boolean({
52
+ description: 'Also update the globally installed NocoBase AI coding skills',
53
+ default: false,
54
+ }),
36
55
  verbose: Flags.boolean({
37
56
  description: 'Show detailed update output',
38
57
  default: false,
@@ -50,11 +69,14 @@ export default class SelfUpdate extends Command {
50
69
  if (!status.latestVersion && status.registryError) {
51
70
  this.error(formatSelfUpdateUnavailableMessage(status));
52
71
  }
72
+ let shouldUpdateSkills = flags.skills;
53
73
  if (!flags.yes && status.updateAvailable) {
54
74
  let confirmed = false;
55
75
  try {
56
76
  confirmed = await confirm({
57
- message: `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
77
+ message: flags.skills
78
+ ? `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion} and refresh the globally installed NocoBase AI coding skills?`
79
+ : `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
58
80
  default: false,
59
81
  });
60
82
  }
@@ -69,6 +91,24 @@ export default class SelfUpdate extends Command {
69
91
  channel: flags.channel,
70
92
  verbose: flags.verbose,
71
93
  });
94
+ if (flags.skills && !flags.yes && !status.updateAvailable) {
95
+ let confirmed = false;
96
+ try {
97
+ confirmed = await confirm({
98
+ message: 'Update the globally installed NocoBase AI coding skills?',
99
+ default: true,
100
+ });
101
+ }
102
+ catch {
103
+ return;
104
+ }
105
+ shouldUpdateSkills = confirmed;
106
+ }
107
+ const skillsResult = shouldUpdateSkills
108
+ ? await updateNocoBaseSkills({
109
+ verbose: flags.verbose,
110
+ })
111
+ : undefined;
72
112
  if (flags.json) {
73
113
  this.log(JSON.stringify({
74
114
  ok: true,
@@ -79,6 +119,17 @@ export default class SelfUpdate extends Command {
79
119
  channel: result.status.channel,
80
120
  fromVersion: result.status.currentVersion,
81
121
  toVersion: result.targetVersion,
122
+ skills: skillsResult
123
+ ? {
124
+ action: skillsResult.action,
125
+ reason: skillsResult.action === 'noop' ? skillsResult.reason : undefined,
126
+ globalRoot: skillsResult.status.globalRoot,
127
+ workspaceRoot: skillsResult.status.workspaceRoot,
128
+ installedSkillNames: skillsResult.status.installedSkillNames,
129
+ installedVersion: skillsResult.status.installedVersion,
130
+ installedRef: skillsResult.status.installedRef,
131
+ }
132
+ : undefined,
82
133
  }, null, 2));
83
134
  return;
84
135
  }
@@ -86,10 +137,16 @@ export default class SelfUpdate extends Command {
86
137
  this.log(flags.verbose
87
138
  ? `NocoBase CLI is already up to date at ${result.status.currentVersion}.`
88
139
  : `NocoBase CLI is up to date: ${result.status.currentVersion}.`);
140
+ if (skillsResult) {
141
+ this.log(formatSkillsUpdateMessage(skillsResult, flags.verbose));
142
+ }
89
143
  return;
90
144
  }
91
145
  this.log(flags.verbose
92
146
  ? `Updated NocoBase CLI from ${result.status.currentVersion} using ${result.packageSpec}${result.targetVersion ? ` (latest ${result.status.channel} resolves to ${result.targetVersion})` : ''}.`
93
147
  : `Updated NocoBase CLI: ${result.status.currentVersion} -> ${result.targetVersion}.`);
148
+ if (skillsResult) {
149
+ this.log(formatSkillsUpdateMessage(skillsResult, flags.verbose));
150
+ }
94
151
  }
95
152
  }
@@ -16,20 +16,20 @@ function formatUnsupportedRuntimeMessage(kind, envName) {
16
16
  return [
17
17
  `Can't run dev mode for "${envName}".`,
18
18
  'This env is managed by Docker, but `nb source dev` requires a local npm or Git source directory.',
19
- `Use \`nb app logs --env ${envName}\` to inspect the Docker app, or create a source-based env with \`nb init --env ${envName} --source git\`.`,
19
+ `Use \`nb app logs --env ${envName}\` to inspect the Docker app, or create a source-based env with \`nb init --ui --env ${envName} --source git\`.`,
20
20
  ].join('\n');
21
21
  }
22
22
  if (kind === 'ssh') {
23
23
  return [
24
24
  `Can't run dev mode for "${envName}" yet.`,
25
25
  'SSH env support is reserved but not implemented yet.',
26
- `Create a source-based env with \`nb init --env ${envName} --source git\` if you want local development mode right now.`,
26
+ `Create a source-based env with \`nb init --ui --env ${envName} --source git\` if you want local development mode right now.`,
27
27
  ].join('\n');
28
28
  }
29
29
  return [
30
30
  `Can't run dev mode for "${envName}".`,
31
31
  'This env only has an API connection, but `nb source dev` requires a local npm or Git source directory.',
32
- `Create a source-based env with \`nb init --env ${envName} --source git\` if you want local development mode.`,
32
+ `Create a source-based env with \`nb init --ui --env ${envName} --source git\` if you want local development mode.`,
33
33
  ].join('\n');
34
34
  }
35
35
  function appUrlForPort(port) {
@@ -129,8 +129,8 @@ export default class SourceDev extends Command {
129
129
  this.error(formatUnsupportedRuntimeMessage(runtime.kind, runtime.envName));
130
130
  }
131
131
  announceTargetEnv(runtime.envName);
132
- const devPort = flags.port
133
- || (runtime.env.appPort !== undefined && runtime.env.appPort !== null
132
+ const devPort = flags.port ||
133
+ (runtime.env.appPort !== undefined && runtime.env.appPort !== null
134
134
  ? String(runtime.env.appPort).trim()
135
135
  : undefined);
136
136
  const appUrl = appUrlForPort(devPort);
@@ -70,6 +70,9 @@ function downloadSourceLabel(source) {
70
70
  return source;
71
71
  }
72
72
  }
73
+ function downloadActionLabel(flags) {
74
+ return flags.replace ? 'Source refresh' : 'Download';
75
+ }
73
76
  function normalizeDockerPlatform(value) {
74
77
  const text = String(value ?? '').trim();
75
78
  if (text === 'linux/amd64' || text === 'linux/arm64') {
@@ -228,7 +231,7 @@ export default class SourceDownload extends Command {
228
231
  'npm-registry': Flags.string({
229
232
  description: 'npm registry for npm/git downloads and dependency installation.',
230
233
  }),
231
- 'build': Flags.boolean({
234
+ build: Flags.boolean({
232
235
  allowNo: true,
233
236
  description: 'Build npm/git source after dependencies are installed.',
234
237
  default: true,
@@ -347,7 +350,8 @@ export default class SourceDownload extends Command {
347
350
  message: downloadText('prompts.outputDir.message'),
348
351
  placeholder: downloadText('prompts.outputDir.placeholder'),
349
352
  initialValue: (values) => {
350
- const version = resolveVersionFromResults(values, DEFAULT_DOWNLOAD_VERSION) || DEFAULT_DOWNLOAD_VERSION;
353
+ const version = resolveVersionFromResults(values, DEFAULT_DOWNLOAD_VERSION) ||
354
+ DEFAULT_DOWNLOAD_VERSION;
351
355
  return defaultOutputDirForVersion(version);
352
356
  },
353
357
  required: true,
@@ -467,8 +471,7 @@ export default class SourceDownload extends Command {
467
471
  initialValues.dockerPlatform = normalizeDockerPlatform(flags['docker-platform']);
468
472
  initialValues.dockerSave = flags['docker-save'];
469
473
  if (flags['npm-registry'] !== undefined) {
470
- initialValues.npmRegistry =
471
- typeof flags['npm-registry'] === 'string' ? flags['npm-registry'] : '';
474
+ initialValues.npmRegistry = typeof flags['npm-registry'] === 'string' ? flags['npm-registry'] : '';
472
475
  }
473
476
  for (const key of Object.keys(preset)) {
474
477
  if (Object.prototype.hasOwnProperty.call(initialValues, key)) {
@@ -536,7 +539,7 @@ export default class SourceDownload extends Command {
536
539
  return preset;
537
540
  }
538
541
  resolveEffectiveBuild(source, results, flags) {
539
- if (source === 'npm' && !Boolean(results.devDependencies)) {
542
+ if (source === 'npm' && !results.devDependencies) {
540
543
  return false;
541
544
  }
542
545
  if (source === 'npm' || source === 'git') {
@@ -554,34 +557,29 @@ export default class SourceDownload extends Command {
554
557
  ? String(results.outputDir).trim() || undefined
555
558
  : flags['output-dir']?.trim() || undefined;
556
559
  const replace = results.replace !== undefined ? Boolean(results.replace) : flags.replace;
557
- const gitUrl = results.gitUrl !== undefined
558
- ? String(results.gitUrl).trim() || undefined
559
- : flags['git-url']?.trim() || undefined;
560
+ const gitUrl = results.gitUrl !== undefined ? String(results.gitUrl).trim() || undefined : flags['git-url']?.trim() || undefined;
560
561
  const dockerRegistry = source === 'docker'
561
- ? (results.dockerRegistry !== undefined
562
+ ? results.dockerRegistry !== undefined
562
563
  ? String(results.dockerRegistry).trim() || undefined
563
- : flags['docker-registry']?.trim() || defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE))
564
+ : flags['docker-registry']?.trim() || defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE)
564
565
  : undefined;
565
566
  const dockerPlatform = source === 'docker'
566
- ? normalizeDockerPlatform(results.dockerPlatform !== undefined
567
- ? results.dockerPlatform
568
- : flags['docker-platform'])
567
+ ? normalizeDockerPlatform(results.dockerPlatform !== undefined ? results.dockerPlatform : flags['docker-platform'])
569
568
  : undefined;
570
569
  const dockerSave = source === 'docker'
571
570
  ? results.dockerSave !== undefined
572
571
  ? Boolean(results.dockerSave)
573
572
  : flags['docker-save']
574
573
  : false;
575
- const npmRegistryRaw = results.npmRegistry !== undefined
576
- ? String(results.npmRegistry)
577
- : flags['npm-registry'] ?? '';
574
+ const npmRegistryRaw = results.npmRegistry !== undefined ? String(results.npmRegistry) : flags['npm-registry'] ?? '';
578
575
  const npmRegistry = npmRegistryRaw.trim() || undefined;
576
+ const npmDevDependencies = devDependencies ?? false;
579
577
  return {
580
578
  source,
581
579
  version,
582
580
  replace,
583
- ...(source === 'npm' ? { 'dev-dependencies': devDependencies } : {}),
584
- 'build': effectiveBuild,
581
+ ...(source === 'npm' ? { 'dev-dependencies': npmDevDependencies } : {}),
582
+ build: effectiveBuild,
585
583
  'build-dts': normalizeBuildDts(source, effectiveBuild, buildDtsWant),
586
584
  'output-dir': outputDir,
587
585
  'git-url': gitUrl,
@@ -772,7 +770,9 @@ export default class SourceDownload extends Command {
772
770
  await fsp.mkdir(parentDir, { recursive: true });
773
771
  const registryEnv = this.npmRegistryEnv(flags);
774
772
  this.finishPreparationTask();
775
- this.log(`Creating NocoBase app "${appName}" from npm`);
773
+ this.log(flags.replace
774
+ ? `Refreshing NocoBase source in ${projectRoot} from npm`
775
+ : `Creating NocoBase app "${appName}" from npm`);
776
776
  await this.runExternalCommand('npx', npxArgs, {
777
777
  ...this.runOptionsWithCwd(parentDir, registryEnv),
778
778
  errorName: 'npx create-nocobase-app',
@@ -795,7 +795,7 @@ export default class SourceDownload extends Command {
795
795
  ...(this.isVerbose() ? ['--verbose'] : []),
796
796
  ]);
797
797
  }
798
- this.log(`NocoBase app is ready at ${projectRoot}`);
798
+ this.log(`${flags.replace ? 'NocoBase source' : 'NocoBase app'} is ready at ${projectRoot}`);
799
799
  return projectRoot;
800
800
  }
801
801
  async downloadFromGit(flags) {
@@ -808,9 +808,13 @@ export default class SourceDownload extends Command {
808
808
  gitArgs.push('--branch', branch);
809
809
  gitArgs.push('--depth', '1', repoUrl, outputDir);
810
810
  this.finishPreparationTask();
811
- this.log(branch === versionSpec
812
- ? `Cloning NocoBase from ${repoUrl} (${branch})`
813
- : `Cloning NocoBase from ${repoUrl} (${branch}, resolved from ${versionSpec})`);
811
+ this.log(flags.replace
812
+ ? branch === versionSpec
813
+ ? `Refreshing NocoBase source from ${repoUrl} (${branch})`
814
+ : `Refreshing NocoBase source from ${repoUrl} (${branch}, resolved from ${versionSpec})`
815
+ : branch === versionSpec
816
+ ? `Cloning NocoBase from ${repoUrl} (${branch})`
817
+ : `Cloning NocoBase from ${repoUrl} (${branch}, resolved from ${versionSpec})`);
814
818
  await this.runExternalCommand('git', gitArgs, {
815
819
  errorName: 'git clone',
816
820
  loadingMessage: 'Cloning the repository',
@@ -830,7 +834,7 @@ export default class SourceDownload extends Command {
830
834
  ...(this.isVerbose() ? ['--verbose'] : []),
831
835
  ]);
832
836
  }
833
- this.log(`NocoBase app is ready at ${projectRoot}`);
837
+ this.log(`${flags.replace ? 'NocoBase source' : 'NocoBase app'} is ready at ${projectRoot}`);
834
838
  return projectRoot;
835
839
  }
836
840
  async download() {
@@ -869,7 +873,7 @@ export default class SourceDownload extends Command {
869
873
  async run() {
870
874
  try {
871
875
  const result = await this.download();
872
- this.logProgress(`Download completed via ${downloadSourceLabel(result.resolved.source)}.`);
876
+ this.logProgress(`${downloadActionLabel(result.resolved)} completed via ${downloadSourceLabel(result.resolved.source)}.`);
873
877
  return result;
874
878
  }
875
879
  catch (error) {
@@ -50,13 +50,14 @@ function readEnvName(argv) {
50
50
  }
51
51
  return undefined;
52
52
  }
53
- function createRuntimeCommand(operation) {
53
+ function createRuntimeCommand(operation, runtimeVersion) {
54
54
  return class RuntimeCommand extends GeneratedApiCommand {
55
55
  static summary = operation.summary;
56
56
  static description = operation.description;
57
57
  static examples = operation.examples;
58
58
  static flags = createGeneratedFlags(operation);
59
59
  static operation = operation;
60
+ static runtimeVersion = runtimeVersion;
60
61
  };
61
62
  }
62
63
  function createRuntimeIndexCommand(commandId, metadata) {
@@ -122,7 +123,7 @@ const runtime = loadRuntimeSync(env?.runtime?.version);
122
123
  for (const operation of runtime?.commands ?? []) {
123
124
  const commandSegments = operation.commandId.split(' ');
124
125
  const commandKey = commandSegments.join(':');
125
- registry[`api:${commandKey}`] = createRuntimeCommand(operation);
126
+ registry[`api:${commandKey}`] = createRuntimeCommand(operation, runtime?.version);
126
127
  for (const [topicCommandId, metadata] of getRuntimeTopicEntries(operation)) {
127
128
  const topicKey = `api:${topicCommandId.split(' ').join(':')}`;
128
129
  if (!registry[topicKey]) {
@@ -23,6 +23,8 @@ import { resolveServerRequestTarget } from './env-auth.js';
23
23
  import { fetchWithPreservedAuthRedirect } from './http-request.js';
24
24
  const CLI_REQUEST_SOURCE_HEADER = 'x-request-source';
25
25
  const CLI_REQUEST_SOURCE_VALUE = 'cli';
26
+ const CLI_VERSION_HEADER = 'x-nb-cli-version';
27
+ const SKILLS_VERSION_HEADER = 'x-nb-skills-version';
26
28
  function stripUtf8Bom(text) {
27
29
  return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
28
30
  }
@@ -209,6 +211,10 @@ export async function executeApiRequest(options) {
209
211
  const { baseUrl, token } = await resolveServerRequestTarget(options);
210
212
  const headers = new Headers();
211
213
  headers.set(CLI_REQUEST_SOURCE_HEADER, CLI_REQUEST_SOURCE_VALUE);
214
+ headers.set(CLI_VERSION_HEADER, options.cliVersion);
215
+ if (options.skillsVersion) {
216
+ headers.set(SKILLS_VERSION_HEADER, options.skillsVersion);
217
+ }
212
218
  if (token) {
213
219
  headers.set('authorization', `Bearer ${token}`);
214
220
  }