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

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 (43) hide show
  1. package/README.md +8 -4
  2. package/README.zh-CN.md +6 -2
  3. package/bin/run.js +15 -0
  4. package/dist/commands/db/shared.js +19 -5
  5. package/dist/commands/dev.js +8 -1
  6. package/dist/commands/down.js +10 -6
  7. package/dist/commands/env/add.js +14 -34
  8. package/dist/commands/env/auth.js +6 -13
  9. package/dist/commands/env/list.js +10 -15
  10. package/dist/commands/env/remove.js +4 -10
  11. package/dist/commands/env/update.js +7 -13
  12. package/dist/commands/env/use.js +5 -13
  13. package/dist/commands/init.js +190 -62
  14. package/dist/commands/install.js +65 -26
  15. package/dist/commands/logs.js +8 -1
  16. package/dist/commands/pm/list.js +8 -1
  17. package/dist/commands/ps.js +18 -15
  18. package/dist/commands/restart.js +74 -0
  19. package/dist/commands/self/check.js +1 -1
  20. package/dist/commands/self/update.js +13 -3
  21. package/dist/commands/skills/check.js +11 -5
  22. package/dist/commands/skills/index.js +1 -1
  23. package/dist/commands/skills/install.js +20 -7
  24. package/dist/commands/skills/update.js +20 -7
  25. package/dist/commands/start.js +8 -1
  26. package/dist/commands/stop.js +8 -1
  27. package/dist/commands/upgrade.js +12 -1
  28. package/dist/lib/api-client.js +3 -2
  29. package/dist/lib/app-runtime.js +16 -5
  30. package/dist/lib/auth-store.js +159 -43
  31. package/dist/lib/bootstrap.js +13 -12
  32. package/dist/lib/cli-home.js +33 -2
  33. package/dist/lib/env-auth.js +3 -3
  34. package/dist/lib/generated-command.js +10 -2
  35. package/dist/lib/http-request.js +49 -0
  36. package/dist/lib/resource-command.js +10 -2
  37. package/dist/lib/runtime-generator.js +1 -1
  38. package/dist/lib/self-manager.js +1 -1
  39. package/dist/lib/skills-manager.js +140 -73
  40. package/dist/lib/startup-update.js +203 -0
  41. package/dist/locale/en-US.json +4 -1
  42. package/dist/locale/zh-CN.json +4 -1
  43. package/package.json +2 -2
@@ -15,9 +15,10 @@ import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
15
15
  import { getEnv, upsertEnv } from "../lib/auth-store.js";
16
16
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
17
17
  import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
18
+ import { resolveDefaultConfigScope } from '../lib/cli-home.js';
18
19
  import { runPromptCatalogWebUI, } from "../lib/prompt-web-ui.js";
19
20
  import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
20
- import { installNocoBaseSkills } from '../lib/skills-manager.js';
21
+ import { inspectSkillsStatus, installNocoBaseSkills, updateNocoBaseSkills, } from '../lib/skills-manager.js';
21
22
  import Download from "./download.js";
22
23
  import EnvAdd from "./env/add.js";
23
24
  import Install, { defaultDbPortForDialect } from "./install.js";
@@ -25,7 +26,14 @@ import _ from 'lodash';
25
26
  const DEFAULT_INIT_API_BASE_URL = 'http://localhost:13000/api';
26
27
  const DEFAULT_INIT_APP_NAME = 'local';
27
28
  const DOWNLOAD_OUTPUT_DIR_PROMPT = Download.prompts.outputDir;
28
- const CONFIG_SCOPE = 'project';
29
+ const INIT_ENV_ADD_FLAG_NAMES = [
30
+ 'locale',
31
+ 'default-api-base-url',
32
+ 'api-base-url',
33
+ 'auth-type',
34
+ 'access-token',
35
+ 'token',
36
+ ];
29
37
  const initText = (key, values) => localeText(`commands.init.${key}`, values);
30
38
  function withExtraHidden(def, extraHidden) {
31
39
  if (def.type === 'run') {
@@ -55,6 +63,27 @@ function resolveInitDownloadVersion(results) {
55
63
  }
56
64
  return preset;
57
65
  }
66
+ function initVersionPromptValue(version) {
67
+ return version === 'latest' || version === 'beta' || version === 'alpha'
68
+ ? version
69
+ : 'other';
70
+ }
71
+ function yesInitialValue(def, fallback) {
72
+ if ('yesInitialValue' in def && def.yesInitialValue !== undefined) {
73
+ return String(def.yesInitialValue);
74
+ }
75
+ return fallback;
76
+ }
77
+ function hasDownloadOverride(flags) {
78
+ return Boolean(String(flags.source ?? '').trim()
79
+ || String(flags.version ?? '').trim());
80
+ }
81
+ function explicitApiBaseUrlFlag(flags) {
82
+ return String(flags['api-base-url'] ?? '').trim();
83
+ }
84
+ function explicitDbHostFlag(flags) {
85
+ return String(flags['db-host'] ?? '').trim();
86
+ }
58
87
  function shouldAllowExistingInitEnv() {
59
88
  return argvHasToken(process.argv.slice(2), ['--force', '-f']);
60
89
  }
@@ -67,7 +96,7 @@ async function validateInitAppName(value) {
67
96
  if (!envName) {
68
97
  return undefined;
69
98
  }
70
- const existingEnv = await getEnv(envName, { scope: 'project' });
99
+ const existingEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
71
100
  if (existingEnv) {
72
101
  if (shouldAllowExistingInitEnv()) {
73
102
  return undefined;
@@ -94,6 +123,11 @@ function formatSkippedAppNameRequiredMessage() {
94
123
  translateCli('commands.init.messages.appNameEnvHelp'),
95
124
  ].join('\n');
96
125
  }
126
+ function shellQuoteArg(value) {
127
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)
128
+ ? value
129
+ : `'${value.replace(/'/g, `'\\''`)}'`;
130
+ }
97
131
  function initTitle() {
98
132
  return translateCli('commands.init.messages.title');
99
133
  }
@@ -162,12 +196,6 @@ Prompt modes:
162
196
  yesInitialValue: 'no',
163
197
  required: true,
164
198
  },
165
- installSkills: {
166
- type: 'boolean',
167
- message: initText('prompts.installSkills.message'),
168
- initialValue: true,
169
- yesInitialValue: true,
170
- },
171
199
  apiBaseUrl: existingAppOnly({
172
200
  type: 'text',
173
201
  message: initText('prompts.apiBaseUrl.message'),
@@ -238,10 +266,6 @@ Prompt modes:
238
266
  char: 'e',
239
267
  description: 'Env name for this setup. Required with --yes and --resume',
240
268
  }),
241
- 'install-skills': Flags.boolean({
242
- description: 'Install NocoBase AI coding skills (`nocobase/skills`) for this workspace',
243
- default: false,
244
- }),
245
269
  ui: Flags.boolean({
246
270
  description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
247
271
  default: false,
@@ -258,6 +282,7 @@ Prompt modes:
258
282
  min: 0,
259
283
  max: 65535,
260
284
  }),
285
+ ..._.pick(EnvAdd.flags, INIT_ENV_ADD_FLAG_NAMES),
261
286
  ..._.omit(Install.flags, ['yes', 'env']),
262
287
  };
263
288
  async run() {
@@ -282,24 +307,12 @@ Prompt modes:
282
307
  this.exit(1);
283
308
  }
284
309
  p.intro(initTitle());
285
- if (Boolean(normalizedFlags['install-skills'])) {
286
- try {
287
- p.log.step('Installing NocoBase agent skills (nb skills install)');
288
- await installNocoBaseSkills();
289
- }
290
- catch (error) {
291
- const message = error instanceof Error ? error.message : String(error);
292
- p.outro(pc.red(`Skills install failed: ${message}`));
293
- this.error(message);
294
- }
295
- }
310
+ await this.syncNocoBaseSkills();
296
311
  try {
297
- p.log.step(`Resuming setup for env "${envName}" from the saved workspace config`);
298
312
  await this.config.runCommand('install', this.buildResumeInstallArgv(normalizedFlags));
299
313
  }
300
314
  catch (error) {
301
315
  const message = error instanceof Error ? error.message : String(error);
302
- p.outro(pc.red(message));
303
316
  this.error(message);
304
317
  }
305
318
  p.outro('Workspace init finished.');
@@ -365,28 +378,15 @@ Prompt modes:
365
378
  },
366
379
  command: this,
367
380
  });
368
- const installSkills = Boolean(results.installSkills);
369
381
  const hasNocobase = results.hasNocobase === 'yes';
370
382
  const existingEnv = !hasNocobase
371
- ? await getEnv(String(results.appName ?? '').trim(), { scope: 'project' })
383
+ ? await getEnv(String(results.appName ?? '').trim(), { scope: resolveDefaultConfigScope() })
372
384
  : undefined;
373
385
  if (existingEnv && Boolean(normalizedFlags.force)) {
374
- p.log.warn(`Reconfiguring existing env ${pc.cyan(pc.bold(`"${existingEnv.name}"`))} in this workspace because ${pc.bold('--force')} was set. The env config will be updated before install starts, then refreshed again after install succeeds.`);
375
- }
376
- if (installSkills) {
377
- try {
378
- p.log.step('Installing NocoBase agent skills (nb skills install)');
379
- await installNocoBaseSkills();
380
- }
381
- catch (error) {
382
- const message = error instanceof Error ? error.message : String(error);
383
- p.outro(pc.red(`Skills install failed: ${message}`));
384
- this.error(message);
385
- }
386
- }
387
- else {
388
- p.log.info('Skipped NocoBase agent skills install.');
386
+ p.log.warn(`Reconfiguring existing env ${pc.cyan(pc.bold(`"${existingEnv.name}"`))} from the global config because ${pc.bold('--force')} was set. The env config will be updated before install starts, then refreshed again after install succeeds.`);
389
387
  }
388
+ await this.syncNocoBaseSkills();
389
+ let managedInstallResults;
390
390
  try {
391
391
  // oclif explicit registry keys use `:` (e.g. `env:add`); users still type `nb env add`.
392
392
  if (hasNocobase) {
@@ -395,15 +395,19 @@ Prompt modes:
395
395
  }
396
396
  else {
397
397
  p.log.step('Saving the local env config');
398
- await this.persistManagedEnvConfig(results);
398
+ await this.persistManagedEnvConfig(results, normalizedFlags);
399
+ managedInstallResults = results;
399
400
  p.log.step('Running nb install');
400
401
  await this.config.runCommand('install', this.buildInstallArgv(results, normalizedFlags));
401
402
  }
402
403
  }
403
404
  catch (error) {
404
405
  const message = error instanceof Error ? error.message : String(error);
405
- p.outro(pc.red(message));
406
- this.error(message);
406
+ const formatted = managedInstallResults
407
+ ? this.formatManagedInstallFailureMessage(message, managedInstallResults, normalizedFlags)
408
+ : message;
409
+ p.outro(pc.red(formatted));
410
+ this.exit(1);
407
411
  }
408
412
  p.outro('Workspace init finished.');
409
413
  }
@@ -451,7 +455,6 @@ Prompt modes:
451
455
  catalog: {
452
456
  appName: c.appName,
453
457
  hasNocobase: c.hasNocobase,
454
- installSkills: c.installSkills,
455
458
  },
456
459
  },
457
460
  {
@@ -525,6 +528,22 @@ Prompt modes:
525
528
  if (flags.env !== undefined && String(flags.env).trim() !== '') {
526
529
  preset.appName = String(flags.env).trim();
527
530
  }
531
+ const apiBaseUrl = explicitApiBaseUrlFlag(flags);
532
+ if (apiBaseUrl) {
533
+ preset.hasNocobase = 'yes';
534
+ preset.apiBaseUrl = apiBaseUrl;
535
+ }
536
+ else if (flags['default-api-base-url'] !== undefined
537
+ && String(flags['default-api-base-url']).trim() !== '') {
538
+ preset.apiBaseUrl = String(flags['default-api-base-url']).trim();
539
+ }
540
+ if (flags['auth-type'] !== undefined && String(flags['auth-type']).trim() !== '') {
541
+ preset.authType = String(flags['auth-type']).trim();
542
+ }
543
+ const accessToken = String(flags['access-token'] ?? flags.token ?? '');
544
+ if (flags['access-token'] !== undefined || flags.token !== undefined) {
545
+ preset.accessToken = accessToken;
546
+ }
528
547
  if (flags.lang !== undefined && String(flags.lang).trim() !== '') {
529
548
  preset.lang = String(flags.lang).trim();
530
549
  }
@@ -557,6 +576,7 @@ Prompt modes:
557
576
  }
558
577
  if (flags['db-host'] !== undefined && String(flags['db-host']).trim() !== '') {
559
578
  preset.dbHost = String(flags['db-host']).trim();
579
+ preset.builtinDb = false;
560
580
  }
561
581
  if (flags['db-port'] !== undefined && String(flags['db-port']).trim() !== '') {
562
582
  preset.dbPort = String(flags['db-port']).trim();
@@ -577,7 +597,11 @@ Prompt modes:
577
597
  preset.source = String(flags.source).trim();
578
598
  }
579
599
  if (flags.version !== undefined) {
580
- preset.version = String(flags.version).trim() || 'latest';
600
+ const version = String(flags.version).trim() || 'latest';
601
+ preset.version = initVersionPromptValue(version);
602
+ if (preset.version === 'other') {
603
+ preset.otherVersion = version;
604
+ }
581
605
  }
582
606
  if (flags['docker-registry'] !== undefined && String(flags['docker-registry']).trim() !== '') {
583
607
  preset.dockerRegistry = String(flags['docker-registry']).trim();
@@ -609,21 +633,38 @@ Prompt modes:
609
633
  if (argvHasToken(argv, ['--build-dts', '--no-build-dts'])) {
610
634
  preset.buildDts = Boolean(flags['build-dts']);
611
635
  }
612
- if (argvHasToken(argv, ['--builtin-db'])) {
636
+ if (argvHasToken(argv, ['--builtin-db', '--no-builtin-db'])) {
613
637
  preset.builtinDb = Boolean(flags['builtin-db']);
614
638
  }
615
- if (argvHasToken(argv, ['--install-skills'])) {
616
- preset.installSkills = Boolean(flags['install-skills']);
639
+ if (explicitDbHostFlag(flags)) {
640
+ preset.builtinDb = false;
617
641
  }
618
- else if (this.hasAgentsDirInCwd()) {
619
- preset.installSkills = false;
642
+ if (!preset.hasNocobase && hasDownloadOverride(flags)) {
643
+ preset.hasNocobase = 'no';
620
644
  }
621
645
  return preset;
622
646
  }
623
647
  hasAgentsDirInCwd() {
624
648
  return existsSync(path.resolve(process.cwd(), '.agents'));
625
649
  }
626
- async persistManagedEnvConfig(results) {
650
+ async syncNocoBaseSkills() {
651
+ try {
652
+ const status = await inspectSkillsStatus();
653
+ if (!status.installed) {
654
+ p.log.step('Installing NocoBase agent skills (nb skills install)');
655
+ await installNocoBaseSkills();
656
+ return;
657
+ }
658
+ p.log.step('Updating NocoBase agent skills (nb skills update)');
659
+ await updateNocoBaseSkills();
660
+ }
661
+ catch (error) {
662
+ const message = error instanceof Error ? error.message : String(error);
663
+ p.outro(pc.red(`Skills sync failed: ${message}`));
664
+ this.exit(1);
665
+ }
666
+ }
667
+ async persistManagedEnvConfig(results, flags = {}) {
627
668
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
628
669
  const appPort = String(results.appPort ?? '').trim();
629
670
  const source = String(results.source ?? '').trim();
@@ -641,8 +682,20 @@ Prompt modes:
641
682
  const dbDatabase = String(results.dbDatabase ?? '').trim();
642
683
  const dbUser = String(results.dbUser ?? '').trim();
643
684
  const dbPassword = String(results.dbPassword ?? '');
685
+ const builtinDb = explicitDbHostFlag(flags)
686
+ ? false
687
+ : results.builtinDb === undefined
688
+ ? undefined
689
+ : Boolean(results.builtinDb);
644
690
  await upsertEnv(envName, {
645
- ...(appPort ? { baseUrl: `http://127.0.0.1:${appPort}/api` } : {}),
691
+ ...(source === 'docker'
692
+ ? { kind: 'docker' }
693
+ : source || appRootPath
694
+ ? { kind: 'local' }
695
+ : appPort
696
+ ? { kind: 'http' }
697
+ : {}),
698
+ ...(appPort ? { apiBaseUrl: `http://127.0.0.1:${appPort}/api` } : {}),
646
699
  ...(source ? { source } : {}),
647
700
  ...(version ? { downloadVersion: version } : {}),
648
701
  ...(dockerRegistry ? { dockerRegistry } : {}),
@@ -655,20 +708,19 @@ Prompt modes:
655
708
  ...(results.devDependencies !== undefined ? { devDependencies: Boolean(results.devDependencies) } : {}),
656
709
  ...(results.build !== undefined ? { build: Boolean(results.build) } : {}),
657
710
  ...(results.buildDts !== undefined ? { buildDts: Boolean(results.buildDts) } : {}),
658
- ...(results.builtinDb !== undefined ? { builtinDb: Boolean(results.builtinDb) } : {}),
711
+ ...(builtinDb !== undefined ? { builtinDb } : {}),
659
712
  ...(dbDialect ? { dbDialect } : {}),
660
- ...(builtinDbImage || results.builtinDb === false ? { builtinDbImage: builtinDbImage || undefined } : {}),
713
+ ...(builtinDbImage || builtinDb === false ? { builtinDbImage: builtinDbImage || undefined } : {}),
661
714
  ...(dbHost ? { dbHost } : {}),
662
715
  ...(dbPort ? { dbPort } : {}),
663
716
  ...(dbDatabase ? { dbDatabase } : {}),
664
717
  ...(dbUser ? { dbUser } : {}),
665
718
  ...(dbPassword ? { dbPassword } : {}),
666
- }, { scope: CONFIG_SCOPE });
719
+ }, { scope: resolveDefaultConfigScope() });
667
720
  }
668
721
  buildEnvAddArgv(results) {
669
722
  const argv = [String(results.appName ?? DEFAULT_INIT_APP_NAME)];
670
723
  argv.push('--no-intro');
671
- argv.push('--scope', CONFIG_SCOPE);
672
724
  argv.push('--api-base-url', String(results.apiBaseUrl ?? DEFAULT_INIT_API_BASE_URL));
673
725
  argv.push('--auth-type', String(results.authType ?? 'oauth'));
674
726
  if (results.authType === 'token') {
@@ -758,10 +810,15 @@ Prompt modes:
758
810
  argv.push('--build-dts');
759
811
  }
760
812
  }
761
- const builtinDb = Boolean(results.builtinDb);
813
+ const explicitDbHost = explicitDbHostFlag(flags);
814
+ const dbHost = explicitDbHost || String(results.dbHost ?? '').trim();
815
+ const builtinDb = explicitDbHost ? false : Boolean(results.builtinDb);
762
816
  if (builtinDb) {
763
817
  argv.push('--builtin-db');
764
818
  }
819
+ else if (explicitDbHost || results.builtinDb !== undefined) {
820
+ argv.push('--no-builtin-db');
821
+ }
765
822
  const dbDialect = String(results.dbDialect ?? '').trim();
766
823
  if (dbDialect) {
767
824
  argv.push('--db-dialect', dbDialect);
@@ -770,7 +827,6 @@ Prompt modes:
770
827
  if (builtinDb && builtinDbImage) {
771
828
  argv.push('--builtin-db-image', builtinDbImage);
772
829
  }
773
- const dbHost = String(results.dbHost ?? '').trim();
774
830
  if (dbHost) {
775
831
  argv.push('--db-host', dbHost);
776
832
  }
@@ -813,8 +869,80 @@ Prompt modes:
813
869
  }
814
870
  return argv;
815
871
  }
872
+ buildManagedInstallResumeCommand(results, flags) {
873
+ const argv = ['nb', 'init'];
874
+ if (Boolean(flags.yes)) {
875
+ argv.push('--yes');
876
+ }
877
+ const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
878
+ argv.push('--env', envName);
879
+ if (Boolean(results.fetchSource)) {
880
+ const source = String(results.source ?? '').trim();
881
+ if (source) {
882
+ argv.push('--source', source);
883
+ }
884
+ const version = resolveInitDownloadVersion(results);
885
+ if (version) {
886
+ argv.push('--version', version);
887
+ }
888
+ const outputDir = String(results.outputDir ?? '').trim();
889
+ if (outputDir && outputDir !== String(results.appRootPath ?? '').trim()) {
890
+ argv.push('--output-dir', outputDir);
891
+ }
892
+ const gitUrl = String(results.gitUrl ?? '').trim();
893
+ if (gitUrl) {
894
+ argv.push('--git-url', gitUrl);
895
+ }
896
+ const dockerRegistry = String(results.dockerRegistry ?? '').trim();
897
+ if (dockerRegistry) {
898
+ argv.push('--docker-registry', dockerRegistry);
899
+ }
900
+ const dockerPlatform = String(results.dockerPlatform ?? '').trim();
901
+ if (dockerPlatform) {
902
+ argv.push('--docker-platform', dockerPlatform);
903
+ }
904
+ const npmRegistry = String(results.npmRegistry ?? '').trim();
905
+ if (npmRegistry) {
906
+ argv.push('--npm-registry', npmRegistry);
907
+ }
908
+ if (Boolean(results.devDependencies)) {
909
+ argv.push('--dev-dependencies');
910
+ }
911
+ if (Boolean(results.dockerSave)) {
912
+ argv.push('--docker-save');
913
+ }
914
+ if (results.build !== undefined && !Boolean(results.build)) {
915
+ argv.push('--no-build');
916
+ }
917
+ if (Boolean(results.buildDts)) {
918
+ argv.push('--build-dts');
919
+ }
920
+ }
921
+ argv.push('--resume', '--verbose');
922
+ return argv.map(shellQuoteArg).join(' ');
923
+ }
924
+ formatManagedInstallFailureMessage(message, results, flags) {
925
+ const command = this.buildManagedInstallResumeCommand(results, flags);
926
+ return [
927
+ message,
928
+ '',
929
+ translateCli('commands.init.messages.resumeAfterInstallFailure', { command }),
930
+ ].join('\n');
931
+ }
816
932
  buildResumeInstallArgv(flags) {
817
- return this.buildInstallArgv(this.buildPresetValuesFromFlags(flags), flags, {
933
+ const preset = this.buildPresetValuesFromFlags(flags);
934
+ if (flags.yes) {
935
+ preset.lang ??= yesInitialValue(Install.appPrompts.lang, 'en-US');
936
+ preset.rootUsername ??= yesInitialValue(Install.rootUserPrompts.rootUsername, 'nocobase');
937
+ preset.rootEmail ??= yesInitialValue(Install.rootUserPrompts.rootEmail, 'admin@nocobase.com');
938
+ preset.rootPassword ??= yesInitialValue(Install.rootUserPrompts.rootPassword, 'admin123');
939
+ preset.rootNickname ??= yesInitialValue(Install.rootUserPrompts.rootNickname, 'Super Admin');
940
+ }
941
+ if (hasDownloadOverride(flags)) {
942
+ preset.fetchSource ??= true;
943
+ }
944
+ preset.replace ??= true;
945
+ return this.buildInstallArgv(preset, flags, {
818
946
  nonInteractive: Boolean(flags.yes),
819
947
  resume: true,
820
948
  });