@nocobase/cli 2.1.0-beta.37 → 2.1.0-beta.38

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.
@@ -7,8 +7,9 @@
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 { formatMissingManagedAppEnvMessage, startDockerContainer, } from '../../lib/app-runtime.js';
11
- import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
10
+ import { formatMissingManagedAppEnvMessage, startDockerContainer } from '../../lib/app-runtime.js';
11
+ import { ensureBuiltinDbReady } from '../../lib/app-managed-resources.js';
12
+ import { announceTargetEnv, failTask, startTask, succeedTask, updateTask } from '../../lib/ui.js';
12
13
  import { formatUnmanagedDbMessage, resolveDbRuntime } from './shared.js';
13
14
  function formatDbStartFailure(envName, message) {
14
15
  if (/does not exist/i.test(message)) {
@@ -45,6 +46,7 @@ export default class DbStart extends Command {
45
46
  async run() {
46
47
  const { flags } = await this.parse(DbStart);
47
48
  const requestedEnv = flags.env?.trim() || undefined;
49
+ const verbose = Boolean(flags.verbose);
48
50
  const runtime = await resolveDbRuntime(requestedEnv);
49
51
  if (!runtime) {
50
52
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
@@ -55,12 +57,25 @@ export default class DbStart extends Command {
55
57
  announceTargetEnv(runtime.envName);
56
58
  startTask(`Starting the built-in database for "${runtime.envName}"...`);
57
59
  try {
58
- const state = await startDockerContainer(runtime.containerName, {
59
- stdio: flags.verbose ? 'inherit' : 'ignore',
60
- });
61
- succeedTask(state === 'already-running'
62
- ? `The built-in database is already running for "${runtime.envName}"${runtime.address ? ` at ${runtime.address}` : ''}.`
63
- : `The built-in database is running for "${runtime.envName}"${runtime.address ? ` at ${runtime.address}` : ''}.`);
60
+ try {
61
+ const state = await startDockerContainer(runtime.containerName, {
62
+ stdio: verbose ? 'inherit' : 'ignore',
63
+ });
64
+ succeedTask(state === 'already-running'
65
+ ? `The built-in database is already running for "${runtime.envName}"${runtime.address ? ` at ${runtime.address}` : ''}.`
66
+ : `The built-in database is running for "${runtime.envName}"${runtime.address ? ` at ${runtime.address}` : ''}.`);
67
+ }
68
+ catch (error) {
69
+ const message = error instanceof Error ? error.message : String(error);
70
+ if (!/does not exist/i.test(message)) {
71
+ throw error;
72
+ }
73
+ updateTask(`Restoring the built-in database for "${runtime.envName}"...`);
74
+ await ensureBuiltinDbReady(runtime.appRuntime, {
75
+ verbose,
76
+ });
77
+ succeedTask(`The built-in database is running for "${runtime.envName}"${runtime.address ? ` at ${runtime.address}` : ''}.`);
78
+ }
64
79
  }
65
80
  catch (error) {
66
81
  const message = error instanceof Error ? error.message : String(error);
@@ -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 = [];
@@ -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
  }