@nocobase/cli 2.1.0-alpha.22 → 2.1.0-alpha.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -147,6 +147,8 @@ In non-interactive mode, pass these setup-only flags again because they are not
147
147
  | `nb env` | Manage saved CLI env connections. |
148
148
  | `nb api` | Call NocoBase API resources from the CLI. |
149
149
  | `nb pm` | Manage plugins for the selected NocoBase env. |
150
+ | `nb self` | Check or update the installed NocoBase CLI. |
151
+ | `nb skills` | Check, install, or update NocoBase AI coding skills for the current workspace. |
150
152
 
151
153
  Recommended style: use `--env` explicitly for app/runtime commands. `-e` is the short form:
152
154
 
@@ -166,6 +168,35 @@ nb upgrade -e app1
166
168
  nb db start -e app1
167
169
  ```
168
170
 
171
+ ## CLI And Skills Updates
172
+
173
+ Check whether the installed CLI itself is up to date:
174
+
175
+ ```bash
176
+ nb self check
177
+ nb self check --json
178
+ ```
179
+
180
+ Update the CLI when it is installed globally with npm:
181
+
182
+ ```bash
183
+ nb self update
184
+ ```
185
+
186
+ Check whether the current workspace already has the NocoBase AI coding skills:
187
+
188
+ ```bash
189
+ nb skills check
190
+ nb skills check --json
191
+ ```
192
+
193
+ Install the skills for the first time, or update an existing `nocobase/skills` install:
194
+
195
+ ```bash
196
+ nb skills install
197
+ nb skills update
198
+ ```
199
+
169
200
  ## Runtime Types
170
201
 
171
202
  ### Docker
package/bin/run.js CHANGED
@@ -38,18 +38,20 @@ function reexecWithTsx() {
38
38
  process.exit(1);
39
39
  }
40
40
 
41
- const result = spawnSync(
42
- process.execPath,
43
- ['--import', pathToFileURL(tsxEntry).href, '--disable-warning=ExperimentalWarning', ...process.argv.slice(1)],
44
- {
45
- stdio: 'inherit',
46
- env: {
47
- ...process.env,
48
- _NOCO_CLI_TSX_CHILD: '1',
49
- NODE_ENV: 'development',
50
- },
41
+ const reexecArgs = ['--import', pathToFileURL(tsxEntry).href];
42
+ const supportedFlags = Array.from(process.allowedNodeEnvironmentFlags);
43
+ if (supportedFlags.some((flag) => flag === '--disable-warning' || flag.startsWith('--disable-warning='))) {
44
+ reexecArgs.push('--disable-warning=ExperimentalWarning');
45
+ }
46
+
47
+ const result = spawnSync(process.execPath, [...reexecArgs, ...process.argv.slice(1)], {
48
+ stdio: 'inherit',
49
+ env: {
50
+ ...process.env,
51
+ _NOCO_CLI_TSX_CHILD: '1',
52
+ NODE_ENV: 'development',
51
53
  },
52
- );
54
+ });
53
55
  process.exit(result.status === null ? 1 : result.status);
54
56
  }
55
57
 
@@ -12,13 +12,15 @@ import * as p from '@clack/prompts';
12
12
  import path from 'node:path';
13
13
  import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
14
14
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
15
- import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, } from "../lib/cli-locale.js";
15
+ import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
16
16
  import { run } from "../lib/run-npm.js";
17
17
  import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../lib/ui.js';
18
18
  const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
19
19
  const DEFAULT_DOCKER_REGISTRY_ZH_CN = 'registry.cn-shanghai.aliyuncs.com/nocobase/nocobase';
20
20
  const DEFAULT_DOCKER_PLATFORM = 'auto';
21
+ const DEFAULT_DOWNLOAD_VERSION = 'beta';
21
22
  const downloadText = (key, values) => localeText(`commands.download.${key}`, values);
23
+ const downloadTranslatedText = (key, values, fallback) => translateCli(`commands.download.${key}`, values, { fallback });
22
24
  function defaultOutputDirForVersion(versionTag) {
23
25
  const safe = versionTag.replace(/[/\\]/g, '-');
24
26
  return `./nocobase-${safe}`;
@@ -33,10 +35,13 @@ async function pathExists(target) {
33
35
  }
34
36
  }
35
37
  export function defaultDockerRegistryForLang(lang) {
36
- return String(lang ?? '').trim() === 'zh-CN'
38
+ return resolveCliLocale(String(lang ?? '').trim() || undefined) === 'zh-CN'
37
39
  ? DEFAULT_DOCKER_REGISTRY_ZH_CN
38
40
  : DEFAULT_DOCKER_REGISTRY;
39
41
  }
42
+ function defaultDockerRegistryForPromptValues(_values) {
43
+ return defaultDockerRegistryForLang(process.env.NB_LOCALE);
44
+ }
40
45
  function argvHasToken(argv, tokens) {
41
46
  return tokens.some((t) => argv.includes(t));
42
47
  }
@@ -81,8 +86,64 @@ function dockerPlatformArg(value) {
81
86
  }
82
87
  return platform;
83
88
  }
89
+ function formatDownloadStageFailure(stage) {
90
+ return [
91
+ downloadTranslatedText(`failures.${stage}.title`),
92
+ downloadTranslatedText(`failures.${stage}.body`),
93
+ downloadTranslatedText(`failures.${stage}.hint`),
94
+ ].join('\n');
95
+ }
96
+ function formatDownloadFailure(message, verbose) {
97
+ if (verbose) {
98
+ return message;
99
+ }
100
+ const lower = message.toLowerCase();
101
+ if (lower.includes('yarn install')) {
102
+ return formatDownloadStageFailure('dependencyInstall');
103
+ }
104
+ if (lower.includes('git clone')) {
105
+ return formatDownloadStageFailure('gitClone');
106
+ }
107
+ if (lower.includes('docker pull')) {
108
+ return formatDownloadStageFailure('dockerPull');
109
+ }
110
+ if (lower.includes('docker save')) {
111
+ return formatDownloadStageFailure('dockerSave');
112
+ }
113
+ if (lower.includes('create-nocobase-app') || lower.includes('npx create-nocobase-app')) {
114
+ return formatDownloadStageFailure('scaffold');
115
+ }
116
+ if (lower.includes('nocobase command')) {
117
+ return formatDownloadStageFailure('build');
118
+ }
119
+ if (lower.includes('exited with code') || lower.includes('exited due to signal')) {
120
+ return formatDownloadStageFailure('generic');
121
+ }
122
+ return message;
123
+ }
84
124
  const EXTERNAL_COMMAND_LOADING_DELAY_MS = 8_000;
85
125
  const EXTERNAL_COMMAND_LOADING_UPDATE_MS = 15_000;
126
+ function normalizeVersionPreset(value) {
127
+ const text = String(value ?? '').trim();
128
+ if (text === 'latest' || text === 'beta' || text === 'alpha') {
129
+ return text;
130
+ }
131
+ return 'other';
132
+ }
133
+ function versionPresetForValue(value) {
134
+ return normalizeVersionPreset(value);
135
+ }
136
+ function otherVersionValue(value) {
137
+ const text = String(value ?? '').trim();
138
+ return normalizeVersionPreset(text) === 'other' ? text : '';
139
+ }
140
+ function resolveVersionFromResults(results, fallback) {
141
+ const preset = normalizeVersionPreset(results.version ?? fallback);
142
+ if (preset === 'other') {
143
+ return String(results.otherVersion ?? fallback ?? '').trim();
144
+ }
145
+ return preset;
146
+ }
86
147
  export default class Download extends Command {
87
148
  _flags;
88
149
  preparationTaskActive = false;
@@ -176,30 +237,72 @@ export default class Download extends Command {
176
237
  static prompts = {
177
238
  source: {
178
239
  type: 'select',
240
+ variant: 'radio',
179
241
  message: downloadText('prompts.source.message'),
180
242
  options: [
181
- { value: 'npm', label: downloadText('prompts.source.npmLabel') },
182
- { value: 'git', label: downloadText('prompts.source.gitLabel') },
183
- { value: 'docker', label: downloadText('prompts.source.dockerLabel') },
243
+ {
244
+ value: 'docker',
245
+ label: downloadText('prompts.source.dockerLabel'),
246
+ hint: downloadText('prompts.source.dockerHint'),
247
+ },
248
+ {
249
+ value: 'npm',
250
+ label: downloadText('prompts.source.npmLabel'),
251
+ hint: downloadText('prompts.source.npmHint'),
252
+ },
253
+ {
254
+ value: 'git',
255
+ label: downloadText('prompts.source.gitLabel'),
256
+ hint: downloadText('prompts.source.gitHint'),
257
+ },
184
258
  ],
185
259
  yesInitialValue: 'docker',
186
260
  initialValue: 'docker',
187
261
  required: true,
188
262
  },
189
263
  version: {
190
- type: 'text',
264
+ type: 'select',
265
+ variant: 'radio',
191
266
  message: downloadText('prompts.version.message'),
192
- placeholder: downloadText('prompts.version.placeholder'),
193
- initialValue: 'alpha',
194
- yesInitialValue: 'alpha',
267
+ options: [
268
+ {
269
+ value: 'latest',
270
+ label: downloadText('prompts.version.latestLabel'),
271
+ hint: downloadText('prompts.version.latestHint'),
272
+ disabled: true,
273
+ },
274
+ {
275
+ value: 'beta',
276
+ label: downloadText('prompts.version.betaLabel'),
277
+ hint: downloadText('prompts.version.betaHint'),
278
+ },
279
+ {
280
+ value: 'alpha',
281
+ label: downloadText('prompts.version.alphaLabel'),
282
+ hint: downloadText('prompts.version.alphaHint'),
283
+ },
284
+ {
285
+ value: 'other',
286
+ label: downloadText('prompts.version.otherLabel'),
287
+ hint: downloadText('prompts.version.otherHint'),
288
+ },
289
+ ],
290
+ initialValue: DEFAULT_DOWNLOAD_VERSION,
291
+ yesInitialValue: DEFAULT_DOWNLOAD_VERSION,
292
+ required: true,
293
+ },
294
+ otherVersion: {
295
+ type: 'text',
296
+ message: downloadText('prompts.otherVersion.message'),
297
+ placeholder: downloadText('prompts.otherVersion.placeholder'),
195
298
  required: true,
299
+ hidden: (values) => normalizeVersionPreset(values.version) !== 'other',
196
300
  },
197
301
  dockerRegistry: {
198
302
  type: 'text',
199
303
  message: downloadText('prompts.dockerRegistry.message'),
200
304
  placeholder: downloadText('prompts.dockerRegistry.placeholder'),
201
- initialValue: (values) => defaultDockerRegistryForLang(values.lang),
202
- yesInitialValue: DEFAULT_DOCKER_REGISTRY,
305
+ initialValue: defaultDockerRegistryForPromptValues,
203
306
  required: true,
204
307
  hidden: (values) => values.source !== 'docker',
205
308
  },
@@ -239,7 +342,10 @@ export default class Download extends Command {
239
342
  type: 'text',
240
343
  message: downloadText('prompts.outputDir.message'),
241
344
  placeholder: downloadText('prompts.outputDir.placeholder'),
242
- initialValue: (values) => defaultOutputDirForVersion(String(values.version ?? 'latest').trim() || 'latest'),
345
+ initialValue: (values) => {
346
+ const version = resolveVersionFromResults(values, DEFAULT_DOWNLOAD_VERSION) || DEFAULT_DOWNLOAD_VERSION;
347
+ return defaultOutputDirForVersion(version);
348
+ },
243
349
  required: true,
244
350
  hidden: (values) => {
245
351
  const s = values.source;
@@ -315,7 +421,7 @@ export default class Download extends Command {
315
421
  return outputAbs;
316
422
  }
317
423
  dockerTarPath(flags, outputAbs) {
318
- const image = flags['docker-registry'] ?? DEFAULT_DOCKER_REGISTRY;
424
+ const image = String(flags['docker-registry'] ?? '').trim() || defaultDockerRegistryForLang(process.env.NB_LOCALE);
319
425
  const tag = flags.version ?? 'latest';
320
426
  const safeBase = `${image.replace(/[/:]/g, '-')}-${tag.replace(/[/\\]/g, '-')}`;
321
427
  return path.join(outputAbs, `${safeBase}.tar`);
@@ -326,12 +432,15 @@ export default class Download extends Command {
326
432
  */
327
433
  buildInitialValuesFromParsed(flags, preset) {
328
434
  const initialValues = {};
435
+ const localeDefaultDockerRegistry = defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE);
329
436
  const source = flags.source?.trim();
330
437
  if (source) {
331
438
  initialValues.source = source;
332
439
  }
333
440
  if (flags.version !== undefined) {
334
- initialValues.version = flags.version.trim() || 'latest';
441
+ const version = flags.version.trim() || 'latest';
442
+ initialValues.version = versionPresetForValue(version);
443
+ initialValues.otherVersion = otherVersionValue(version);
335
444
  }
336
445
  initialValues.replace = flags.replace;
337
446
  initialValues.devDependencies = flags['dev-dependencies'];
@@ -346,6 +455,9 @@ export default class Download extends Command {
346
455
  if (flags['docker-registry'] !== undefined) {
347
456
  initialValues.dockerRegistry = String(flags['docker-registry'] ?? '').trim();
348
457
  }
458
+ else {
459
+ initialValues.dockerRegistry = localeDefaultDockerRegistry;
460
+ }
349
461
  initialValues.dockerPlatform = normalizeDockerPlatform(flags['docker-platform']);
350
462
  initialValues.dockerSave = flags['docker-save'];
351
463
  if (flags['npm-registry'] !== undefined) {
@@ -370,7 +482,11 @@ export default class Download extends Command {
370
482
  preset.source = String(flags.source).trim();
371
483
  }
372
484
  if (flags.version !== undefined) {
373
- preset.version = String(flags.version).trim() || 'latest';
485
+ const version = String(flags.version).trim() || 'latest';
486
+ preset.version = versionPresetForValue(version);
487
+ if (normalizeVersionPreset(version) === 'other') {
488
+ preset.otherVersion = version;
489
+ }
374
490
  }
375
491
  if (flags['docker-registry'] !== undefined) {
376
492
  const v = String(flags['docker-registry'] ?? '').trim();
@@ -424,7 +540,7 @@ export default class Download extends Command {
424
540
  }
425
541
  mapCatalogResultsToResolved(results, flags) {
426
542
  const source = String(results.source);
427
- const version = String(results.version ?? '').trim() || 'latest';
543
+ const version = resolveVersionFromResults(results, flags.version) || 'latest';
428
544
  const devDependencies = source === 'npm' ? Boolean(results.devDependencies) : undefined;
429
545
  const effectiveBuild = this.resolveEffectiveBuild(source, results, flags);
430
546
  const buildDtsWant = results.buildDts !== undefined ? Boolean(results.buildDts) : flags['build-dts'];
@@ -435,9 +551,11 @@ export default class Download extends Command {
435
551
  const gitUrl = results.gitUrl !== undefined
436
552
  ? String(results.gitUrl).trim() || undefined
437
553
  : flags['git-url']?.trim() || undefined;
438
- const dockerRegistry = results.dockerRegistry !== undefined
439
- ? String(results.dockerRegistry).trim() || undefined
440
- : flags['docker-registry']?.trim() || undefined;
554
+ const dockerRegistry = source === 'docker'
555
+ ? (results.dockerRegistry !== undefined
556
+ ? String(results.dockerRegistry).trim() || undefined
557
+ : flags['docker-registry']?.trim() || defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE))
558
+ : undefined;
441
559
  const dockerPlatform = source === 'docker'
442
560
  ? normalizeDockerPlatform(results.dockerPlatform !== undefined
443
561
  ? results.dockerPlatform
@@ -490,9 +608,13 @@ export default class Download extends Command {
490
608
  },
491
609
  });
492
610
  const source = String(results.source ?? '').trim();
611
+ const version = resolveVersionFromResults(results, flags.version);
493
612
  if (!source || !['docker', 'npm', 'git'].includes(source)) {
494
613
  this.error('Download source is required. Choose npm, git, or docker.');
495
614
  }
615
+ if (!version) {
616
+ this.error('Version is required. Choose alpha, beta, latest, or enter another version.');
617
+ }
496
618
  if (flags['docker-save'] && source !== 'docker') {
497
619
  this.error('--docker-save is only available when --source docker is selected.');
498
620
  }
@@ -586,7 +708,7 @@ export default class Download extends Command {
586
708
  return argv;
587
709
  }
588
710
  async downloadFromDocker(flags) {
589
- const image = flags['docker-registry'] ?? DEFAULT_DOCKER_REGISTRY;
711
+ const image = String(flags['docker-registry'] ?? '').trim() || defaultDockerRegistryForLang(process.env.NB_LOCALE);
590
712
  const tag = flags.version ?? 'latest';
591
713
  const imageRef = `${image}:${tag}`;
592
714
  const platform = dockerPlatformArg(flags['docker-platform']);
@@ -737,7 +859,7 @@ export default class Download extends Command {
737
859
  }
738
860
  catch (error) {
739
861
  const message = error instanceof Error ? error.message : String(error);
740
- this.error(message);
862
+ this.error(formatDownloadFailure(message, this.isVerbose()));
741
863
  }
742
864
  }
743
865
  }
@@ -17,7 +17,7 @@ import { runPromptCatalog, } from "../lib/prompt-catalog.js";
17
17
  import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
18
18
  import { runPromptCatalogWebUI, } from "../lib/prompt-web-ui.js";
19
19
  import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
20
- import { run } from "../lib/run-npm.js";
20
+ import { installNocoBaseSkills } from '../lib/skills-manager.js';
21
21
  import Download from "./download.js";
22
22
  import EnvAdd from "./env/add.js";
23
23
  import Install, { defaultDbPortForDialect } from "./install.js";
@@ -48,6 +48,13 @@ function downloadInNewInstallOnly(def) {
48
48
  function argvHasToken(argv, tokens) {
49
49
  return tokens.some((token) => argv.includes(token));
50
50
  }
51
+ function resolveInitDownloadVersion(results) {
52
+ const preset = String(results.version ?? '').trim();
53
+ if (preset === 'other') {
54
+ return String(results.otherVersion ?? '').trim();
55
+ }
56
+ return preset;
57
+ }
51
58
  function shouldAllowExistingInitEnv() {
52
59
  return argvHasToken(process.argv.slice(2), ['--force', '-f']);
53
60
  }
@@ -177,6 +184,7 @@ Prompt modes:
177
184
  fetchSource: newInstallOnly(Install.appPrompts.fetchSource),
178
185
  source: downloadInNewInstallOnly(Download.prompts.source),
179
186
  version: downloadInNewInstallOnly(Download.prompts.version),
187
+ otherVersion: downloadInNewInstallOnly(Download.prompts.otherVersion),
180
188
  dockerRegistry: downloadInNewInstallOnly(Download.prompts.dockerRegistry),
181
189
  dockerPlatform: downloadInNewInstallOnly(Download.prompts.dockerPlatform),
182
190
  dockerSave: downloadInNewInstallOnly(Download.prompts.dockerSave),
@@ -238,6 +246,10 @@ Prompt modes:
238
246
  description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
239
247
  default: false,
240
248
  }),
249
+ verbose: Flags.boolean({
250
+ description: 'Show detailed command output',
251
+ default: false,
252
+ }),
241
253
  'ui-host': Flags.string({
242
254
  description: 'Host for the local --ui setup server (default: 127.0.0.1)',
243
255
  }),
@@ -272,8 +284,8 @@ Prompt modes:
272
284
  p.intro(initTitle());
273
285
  if (Boolean(normalizedFlags['install-skills'])) {
274
286
  try {
275
- p.log.step('Installing NocoBase agent skills (npx -y skills add nocobase/skills)');
276
- await run('npx', ['-y', 'skills', 'add', 'nocobase/skills', '-y']);
287
+ p.log.step('Installing NocoBase agent skills (nb skills install)');
288
+ await installNocoBaseSkills();
277
289
  }
278
290
  catch (error) {
279
291
  const message = error instanceof Error ? error.message : String(error);
@@ -363,8 +375,8 @@ Prompt modes:
363
375
  }
364
376
  if (installSkills) {
365
377
  try {
366
- p.log.step('Installing NocoBase agent skills (npx -y skills add nocobase/skills)');
367
- await run('npx', ['-y', 'skills', 'add', 'nocobase/skills', '-y']);
378
+ p.log.step('Installing NocoBase agent skills (nb skills install)');
379
+ await installNocoBaseSkills();
368
380
  }
369
381
  catch (error) {
370
382
  const message = error instanceof Error ? error.message : String(error);
@@ -468,6 +480,7 @@ Prompt modes:
468
480
  catalog: {
469
481
  source: c.source,
470
482
  version: c.version,
483
+ otherVersion: c.otherVersion,
471
484
  dockerRegistry: c.dockerRegistry,
472
485
  dockerPlatform: c.dockerPlatform,
473
486
  dockerSave: c.dockerSave,
@@ -614,7 +627,7 @@ Prompt modes:
614
627
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
615
628
  const appPort = String(results.appPort ?? '').trim();
616
629
  const source = String(results.source ?? '').trim();
617
- const version = String(results.version ?? '').trim();
630
+ const version = resolveInitDownloadVersion(results);
618
631
  const dockerRegistry = String(results.dockerRegistry ?? '').trim();
619
632
  const dockerPlatform = String(results.dockerPlatform ?? '').trim();
620
633
  const gitUrl = String(results.gitUrl ?? '').trim();
@@ -675,6 +688,9 @@ Prompt modes:
675
688
  if (options?.resume) {
676
689
  argv.push('--resume');
677
690
  }
691
+ if (Boolean(flags.verbose)) {
692
+ argv.push('--verbose');
693
+ }
678
694
  const lang = String(results.lang ?? '').trim();
679
695
  if (lang) {
680
696
  argv.push('--lang', lang);
@@ -699,7 +715,7 @@ Prompt modes:
699
715
  if (source) {
700
716
  argv.push('--source', source);
701
717
  }
702
- const version = String(results.version ?? '').trim();
718
+ const version = resolveInitDownloadVersion(results);
703
719
  if (version) {
704
720
  argv.push('--version', version);
705
721
  }