@nocobase/cli 2.1.0-beta.20 → 2.1.0-beta.22

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 (74) hide show
  1. package/README.md +32 -50
  2. package/README.zh-CN.md +29 -46
  3. package/bin/run.js +15 -0
  4. package/dist/commands/app/down.js +260 -0
  5. package/dist/commands/app/info.js +140 -0
  6. package/dist/commands/app/logs.js +98 -0
  7. package/dist/commands/app/ps.js +60 -0
  8. package/dist/commands/app/restart.js +75 -0
  9. package/dist/commands/app/shared.js +95 -0
  10. package/dist/commands/app/start.js +252 -0
  11. package/dist/commands/app/stop.js +98 -0
  12. package/dist/commands/app/upgrade.js +595 -0
  13. package/dist/commands/build.js +3 -48
  14. package/dist/commands/db/shared.js +19 -5
  15. package/dist/commands/dev.js +3 -140
  16. package/dist/commands/down.js +3 -184
  17. package/dist/commands/download.js +4 -856
  18. package/dist/commands/env/add.js +33 -48
  19. package/dist/commands/env/auth.js +6 -13
  20. package/dist/commands/env/list.js +10 -15
  21. package/dist/commands/env/remove.js +4 -10
  22. package/dist/commands/env/update.js +7 -13
  23. package/dist/commands/env/use.js +5 -13
  24. package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
  25. package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
  26. package/dist/commands/init.js +262 -63
  27. package/dist/commands/install.js +352 -86
  28. package/dist/commands/logs.js +3 -81
  29. package/dist/commands/plugin/disable.js +64 -0
  30. package/dist/commands/plugin/enable.js +64 -0
  31. package/dist/commands/plugin/list.js +62 -0
  32. package/dist/commands/pm/disable.js +3 -54
  33. package/dist/commands/pm/enable.js +3 -54
  34. package/dist/commands/pm/list.js +3 -45
  35. package/dist/commands/ps.js +3 -107
  36. package/dist/commands/restart.js +3 -65
  37. package/dist/commands/scaffold/migration.js +1 -1
  38. package/dist/commands/scaffold/plugin.js +1 -1
  39. package/dist/commands/self/check.js +1 -1
  40. package/dist/commands/self/update.js +13 -3
  41. package/dist/commands/skills/check.js +11 -5
  42. package/dist/commands/skills/index.js +1 -1
  43. package/dist/commands/skills/install.js +20 -7
  44. package/dist/commands/skills/remove.js +71 -0
  45. package/dist/commands/skills/update.js +27 -7
  46. package/dist/commands/source/build.js +58 -0
  47. package/dist/commands/source/dev.js +157 -0
  48. package/dist/commands/source/download.js +866 -0
  49. package/dist/commands/source/test.js +467 -0
  50. package/dist/commands/start.js +3 -202
  51. package/dist/commands/stop.js +3 -81
  52. package/dist/commands/test.js +3 -457
  53. package/dist/commands/upgrade.js +3 -574
  54. package/dist/help/runtime-help.js +3 -0
  55. package/dist/lib/api-client.js +3 -2
  56. package/dist/lib/app-health.js +126 -0
  57. package/dist/lib/app-managed-resources.js +264 -0
  58. package/dist/lib/app-runtime.js +16 -5
  59. package/dist/lib/auth-store.js +162 -43
  60. package/dist/lib/bootstrap.js +13 -12
  61. package/dist/lib/cli-home.js +38 -6
  62. package/dist/lib/cli-locale.js +15 -1
  63. package/dist/lib/env-auth.js +3 -3
  64. package/dist/lib/env-config.js +80 -0
  65. package/dist/lib/generated-command.js +10 -2
  66. package/dist/lib/http-request.js +49 -0
  67. package/dist/lib/resource-command.js +10 -2
  68. package/dist/lib/runtime-generator.js +1 -1
  69. package/dist/lib/self-manager.js +1 -1
  70. package/dist/lib/skills-manager.js +173 -79
  71. package/dist/lib/startup-update.js +203 -0
  72. package/dist/locale/en-US.json +4 -1
  73. package/dist/locale/zh-CN.json +4 -1
  74. package/package.json +26 -3
@@ -16,11 +16,13 @@ import path from 'node:path';
16
16
  import { exit } from 'node:process';
17
17
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
18
18
  import { applyCliLocale, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
19
+ import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
19
20
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
20
21
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
21
22
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
22
23
  import { startTask, stopTask, updateTask } from '../lib/ui.js';
23
- import { ensureWorkspaceName, getEnv } from '../lib/auth-store.js';
24
+ import { ensureWorkspaceName, getEnv, loadAuthConfig, upsertEnv } from '../lib/auth-store.js';
25
+ import { buildStoredEnvConfig } from '../lib/env-config.js';
24
26
  import Download, { defaultDockerRegistryForLang, } from './download.js';
25
27
  import EnvAdd from "./env/add.js";
26
28
  const DEFAULT_INSTALL_ENV_NAME = 'local';
@@ -53,7 +55,6 @@ const DEFAULT_INSTALL_ROOT_USERNAME = 'nocobase';
53
55
  const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
54
56
  const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
55
57
  const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
56
- const CONFIG_SCOPE = 'project';
57
58
  const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
58
59
  const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
59
60
  const APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS = 5_000;
@@ -137,6 +138,11 @@ function argvHasToken(argv, tokens) {
137
138
  function isInstallDbDialect(value) {
138
139
  return INSTALL_DB_DIALECTS.includes(value);
139
140
  }
141
+ function downloadVersionPromptValue(version) {
142
+ return version === 'latest' || version === 'beta' || version === 'alpha'
143
+ ? version
144
+ : 'other';
145
+ }
140
146
  function supportsBuiltinDbDialect(value) {
141
147
  const dialect = String(value ?? '').trim();
142
148
  return Object.prototype.hasOwnProperty.call(DEFAULT_INSTALL_BUILTIN_DB_IMAGES, dialect);
@@ -240,7 +246,8 @@ async function commandOutput(command, args, options) {
240
246
  });
241
247
  }
242
248
  export default class Install extends Command {
243
- static description = 'Install NocoBase: database, storage, admin user, and `nocobase-v1 install`. Optionally run `nb download` first; distribution and image details are configured on `nb download`, not here. Use `--resume` to continue an interrupted setup from the saved workspace env config.';
249
+ static hidden = true;
250
+ static description = 'Install NocoBase: database, storage, admin user, and `nocobase-v1 install`. Optionally run `nb source download` first; distribution and image details are configured on `nb source download`, not here. Use `--resume` to continue an interrupted setup from the saved workspace env config.';
244
251
  static examples = [
245
252
  '<%= config.bin %> <%= command.id %>',
246
253
  '<%= config.bin %> <%= command.id %> --env app1',
@@ -271,6 +278,25 @@ export default class Install extends Command {
271
278
  char: 'e',
272
279
  description: 'App/env name to create or update. Defaults app paths to ./<envName>/source/ and ./<envName>/storage/.',
273
280
  }),
281
+ 'default-api-base-url': Flags.string({
282
+ char: 'd',
283
+ hidden: true,
284
+ description: 'Default API base URL for HTTP API calls, including the /api prefix (e.g. http://localhost:13000/api)',
285
+ }),
286
+ 'api-base-url': Flags.string({
287
+ char: 'u',
288
+ description: 'Root URL for HTTP API calls, including the /api prefix (e.g. http://localhost:13000/api)',
289
+ }),
290
+ 'auth-type': Flags.string({
291
+ char: 'a',
292
+ description: 'Authentication: token (API key) or oauth (browser login via `nb env auth`)',
293
+ options: ['token', 'oauth'],
294
+ }),
295
+ 'access-token': Flags.string({
296
+ char: 't',
297
+ aliases: ['token'],
298
+ description: 'API key or access token when using --auth-type token',
299
+ }),
274
300
  lang: Flags.string({ description: 'Language for the installed NocoBase app', char: 'l', required: false }),
275
301
  force: Flags.boolean({
276
302
  description: 'Reconfigure an existing env and replace conflicting runtime resources when needed',
@@ -303,6 +329,7 @@ export default class Install extends Command {
303
329
  required: false,
304
330
  }),
305
331
  'builtin-db': Flags.boolean({
332
+ allowNo: true,
306
333
  description: 'Create and connect a CLI-managed built-in database for the app',
307
334
  default: false,
308
335
  }),
@@ -368,7 +395,7 @@ export default class Install extends Command {
368
395
  type: 'text',
369
396
  message: installText('prompts.appPort.message'),
370
397
  placeholder: installText('prompts.appPort.placeholder'),
371
- validate: validateAvailableTcpPort,
398
+ validate: Install.validateAppPort,
372
399
  },
373
400
  storagePath: {
374
401
  type: 'text',
@@ -428,7 +455,7 @@ export default class Install extends Command {
428
455
  placeholder: installText('prompts.dbPort.placeholder'),
429
456
  initialValue: (values) => defaultDbPortForDialect(values.dbDialect),
430
457
  required: true,
431
- validate: validateTcpPort,
458
+ validate: Install.validateDbPort,
432
459
  hidden: (values) => Boolean(values.builtinDb)
433
460
  && String(values.source ?? '').trim() === 'docker',
434
461
  },
@@ -486,7 +513,7 @@ export default class Install extends Command {
486
513
  * App catalog with `env` seeded into `out` first so `storagePath`’s `initialValue(values)`
487
514
  * sees `values.env` (same iteration order as {@link runPromptCatalog}).
488
515
  */
489
- static buildAppPromptsCatalog(seedEnv) {
516
+ static buildAppPromptsCatalog(seedEnv, options) {
490
517
  return {
491
518
  seedEnv: {
492
519
  type: 'run',
@@ -494,12 +521,27 @@ export default class Install extends Command {
494
521
  values.env = seedEnv;
495
522
  },
496
523
  },
524
+ seedResume: {
525
+ type: 'run',
526
+ run: (values) => {
527
+ const record = values;
528
+ record.resume = Boolean(options?.resume);
529
+ },
530
+ },
497
531
  ...Install.appPrompts,
498
532
  };
499
533
  }
500
- static buildDbPromptsCatalog(downloadResults) {
534
+ static buildDbPromptsCatalog(envName, downloadResults, options) {
501
535
  const source = String(downloadResults.source ?? '').trim();
502
536
  return {
537
+ seedEnv: {
538
+ type: 'run',
539
+ run: (values) => {
540
+ if (envName) {
541
+ values.env = envName;
542
+ }
543
+ },
544
+ },
503
545
  seedDownloadSource: {
504
546
  type: 'run',
505
547
  run: (values) => {
@@ -508,6 +550,13 @@ export default class Install extends Command {
508
550
  }
509
551
  },
510
552
  },
553
+ seedResume: {
554
+ type: 'run',
555
+ run: (values) => {
556
+ const record = values;
557
+ record.resume = Boolean(options?.resume);
558
+ },
559
+ },
511
560
  ...Install.dbPrompts,
512
561
  };
513
562
  }
@@ -527,6 +576,25 @@ export default class Install extends Command {
527
576
  static buildPresetValuesFromFlags(flags) {
528
577
  const preset = {};
529
578
  const argv = process.argv.slice(2);
579
+ const apiBaseUrl = Install.toOptionalPromptString(flags['api-base-url']);
580
+ if (apiBaseUrl) {
581
+ preset.apiBaseUrl = apiBaseUrl;
582
+ }
583
+ else if (flags['default-api-base-url'] !== undefined) {
584
+ const defaultApiBaseUrl = Install.toOptionalPromptString(flags['default-api-base-url']);
585
+ if (defaultApiBaseUrl) {
586
+ preset.apiBaseUrl = defaultApiBaseUrl;
587
+ }
588
+ }
589
+ if (flags['auth-type'] !== undefined) {
590
+ const authType = Install.toOptionalPromptString(flags['auth-type']);
591
+ if (authType) {
592
+ preset.authType = authType;
593
+ }
594
+ }
595
+ if (flags['access-token'] !== undefined || flags.token !== undefined) {
596
+ preset.accessToken = String(flags['access-token'] ?? flags.token ?? '');
597
+ }
530
598
  if (flags.lang !== undefined) {
531
599
  const v = String(flags.lang).trim();
532
600
  if (v) {
@@ -569,7 +637,7 @@ export default class Install extends Command {
569
637
  if (argvHasToken(argv, ['--fetch-source'])) {
570
638
  preset.fetchSource = flags['fetch-source'];
571
639
  }
572
- if (argvHasToken(argv, ['--builtin-db'])) {
640
+ if (argvHasToken(argv, ['--builtin-db', '--no-builtin-db'])) {
573
641
  preset.builtinDb = flags['builtin-db'];
574
642
  }
575
643
  if (flags['db-dialect'] !== undefined) {
@@ -588,6 +656,7 @@ export default class Install extends Command {
588
656
  const v = String(flags['db-host'] ?? '').trim();
589
657
  if (v) {
590
658
  preset.dbHost = v;
659
+ preset.builtinDb = false;
591
660
  }
592
661
  }
593
662
  if (flags['db-port'] !== undefined) {
@@ -643,6 +712,13 @@ export default class Install extends Command {
643
712
  'rootNickname',
644
713
  ]);
645
714
  }
715
+ static buildEnvAddPresetValuesFromFlags(flags) {
716
+ return pickPresetKeys(Install.buildPresetValuesFromFlags(flags), [
717
+ 'apiBaseUrl',
718
+ 'authType',
719
+ 'accessToken',
720
+ ]);
721
+ }
646
722
  static toOptionalPromptString(value) {
647
723
  if (value === undefined || value === null) {
648
724
  return undefined;
@@ -650,6 +726,131 @@ export default class Install extends Command {
650
726
  const text = String(value).trim();
651
727
  return text || undefined;
652
728
  }
729
+ static async validateAppPort(value, values) {
730
+ const formatError = validateTcpPort(value);
731
+ if (formatError) {
732
+ return formatError;
733
+ }
734
+ return await Install.validateResumeAwareTcpPort(value, values, 'app');
735
+ }
736
+ static async validateDbPort(value, values) {
737
+ const formatError = validateTcpPort(value);
738
+ if (formatError) {
739
+ return formatError;
740
+ }
741
+ const builtinDb = values.builtinDb === undefined ? true : Boolean(values.builtinDb);
742
+ const source = String(values.source ?? '').trim();
743
+ if (!builtinDb || source === 'docker') {
744
+ return undefined;
745
+ }
746
+ return await Install.validateResumeAwareTcpPort(value, values, 'db');
747
+ }
748
+ static async validateResumeAwareTcpPort(value, values, target) {
749
+ const portError = await validateAvailableTcpPort(value);
750
+ if (!portError) {
751
+ return undefined;
752
+ }
753
+ const context = await Install.readResumePortValidationContext(values);
754
+ if (!context) {
755
+ return portError;
756
+ }
757
+ const port = Install.toOptionalPromptString(value);
758
+ if (!port) {
759
+ return portError;
760
+ }
761
+ const reusesManagedPort = await Install.isResumeManagedPortReuse({
762
+ target,
763
+ port,
764
+ context,
765
+ });
766
+ return reusesManagedPort ? undefined : portError;
767
+ }
768
+ static async readResumePortValidationContext(values) {
769
+ if (!Boolean(values.resume)) {
770
+ return undefined;
771
+ }
772
+ const envName = Install.toOptionalPromptString(values.env);
773
+ if (!envName) {
774
+ return undefined;
775
+ }
776
+ const source = Install.toOptionalPromptString(values.source);
777
+ const builtinDb = values.builtinDb === undefined ? undefined : Boolean(values.builtinDb);
778
+ const dbDialect = Install.toOptionalPromptString(values.dbDialect);
779
+ const appRootPath = Install.toOptionalPromptString(values.appRootPath);
780
+ const workspaceName = Install.toOptionalPromptString(values.workspaceName)
781
+ ?? await Install.resolveResumeWorkspaceName(envName);
782
+ return {
783
+ envName,
784
+ ...(workspaceName ? { workspaceName } : {}),
785
+ ...(source ? { source } : {}),
786
+ ...(builtinDb !== undefined ? { builtinDb } : {}),
787
+ ...(dbDialect ? { dbDialect } : {}),
788
+ ...(appRootPath ? { appRootPath } : {}),
789
+ };
790
+ }
791
+ static async resolveResumeWorkspaceName(envName) {
792
+ if (!envName) {
793
+ return undefined;
794
+ }
795
+ const config = await loadAuthConfig({ scope: resolveDefaultConfigScope() });
796
+ const stored = String(config.name ?? '').trim();
797
+ return stored || Install.defaultWorkspaceName();
798
+ }
799
+ static async isResumeManagedPortReuse(params) {
800
+ if (params.target === 'app') {
801
+ if ((params.context.source === 'npm' || params.context.source === 'git')
802
+ && params.context.appRootPath) {
803
+ return await Install.isLocalPm2ProcessUsingPort(params.context.appRootPath, params.port);
804
+ }
805
+ const containerName = Install.buildDockerAppContainerName(params.context.envName, params.context.workspaceName);
806
+ return await Install.isDockerContainerPublishingPort(containerName, params.port);
807
+ }
808
+ if (!params.context.builtinDb || params.context.source === 'docker') {
809
+ return false;
810
+ }
811
+ const containerName = Install.buildBuiltinDbContainerName(params.context.envName, params.context.dbDialect ?? 'postgres', params.context.workspaceName);
812
+ return await Install.isDockerContainerPublishingPort(containerName, params.port);
813
+ }
814
+ static async isDockerContainerPublishingPort(containerName, port) {
815
+ if (!containerName || !port) {
816
+ return false;
817
+ }
818
+ const exists = await commandSucceeds('docker', [
819
+ 'container',
820
+ 'inspect',
821
+ containerName,
822
+ ]);
823
+ if (!exists) {
824
+ return false;
825
+ }
826
+ try {
827
+ const output = await commandOutput('docker', ['port', containerName]);
828
+ return output
829
+ .split(/\r?\n/)
830
+ .some((line) => line.includes(`:${port}`));
831
+ }
832
+ catch {
833
+ return false;
834
+ }
835
+ }
836
+ static async isLocalPm2ProcessUsingPort(appRootPath, port) {
837
+ const cwd = resolveConfiguredEnvPath(appRootPath);
838
+ if (!cwd) {
839
+ return false;
840
+ }
841
+ try {
842
+ const output = await commandOutput('pm2', ['jlist'], { cwd });
843
+ const rows = JSON.parse(output);
844
+ return rows.some((row) => {
845
+ const pmCwd = Install.toOptionalPromptString(row.pm2_env?.pm_cwd);
846
+ const appPort = Install.toOptionalPromptString(row.pm2_env?.env?.APP_PORT);
847
+ return Boolean(pmCwd && appPort && pmCwd === cwd && appPort === port);
848
+ });
849
+ }
850
+ catch {
851
+ return false;
852
+ }
853
+ }
653
854
  static buildResumePresetValues(env) {
654
855
  const envName = String(env.name ?? '').trim();
655
856
  const config = env.config ?? {};
@@ -669,6 +870,10 @@ export default class Install extends Command {
669
870
  const dbUser = Install.toOptionalPromptString(config.dbUser);
670
871
  const dbPassword = Install.toOptionalPromptString(config.dbPassword);
671
872
  const builtinDbImage = Install.toOptionalPromptString(config.builtinDbImage);
873
+ const rootUsername = Install.toOptionalPromptString(config.rootUsername);
874
+ const rootEmail = Install.toOptionalPromptString(config.rootEmail);
875
+ const rootPassword = Install.toOptionalPromptString(config.rootPassword);
876
+ const rootNickname = Install.toOptionalPromptString(config.rootNickname);
672
877
  const auth = config.auth;
673
878
  const appPreset = {
674
879
  ...(appRootPath ? { appRootPath } : {}),
@@ -682,7 +887,14 @@ export default class Install extends Command {
682
887
  };
683
888
  const downloadPreset = {
684
889
  ...(source ? { source } : {}),
685
- ...(downloadVersion ? { version: downloadVersion } : {}),
890
+ ...(downloadVersion
891
+ ? {
892
+ version: downloadVersionPromptValue(downloadVersion),
893
+ ...(downloadVersionPromptValue(downloadVersion) === 'other'
894
+ ? { otherVersion: downloadVersion }
895
+ : {}),
896
+ }
897
+ : {}),
686
898
  ...(dockerRegistry ? { dockerRegistry } : {}),
687
899
  ...(dockerPlatform ? { dockerPlatform } : {}),
688
900
  ...(gitUrl ? { gitUrl } : {}),
@@ -703,6 +915,12 @@ export default class Install extends Command {
703
915
  ...(dbUser ? { dbUser } : {}),
704
916
  ...(dbPassword ? { dbPassword } : {}),
705
917
  };
918
+ const rootPreset = {
919
+ ...(rootUsername ? { rootUsername } : {}),
920
+ ...(rootEmail ? { rootEmail } : {}),
921
+ ...(rootPassword ? { rootPassword } : {}),
922
+ ...(rootNickname ? { rootNickname } : {}),
923
+ };
706
924
  const envAddPreset = {};
707
925
  if (auth?.type === 'token') {
708
926
  envAddPreset.authType = 'token';
@@ -720,6 +938,7 @@ export default class Install extends Command {
720
938
  appPreset,
721
939
  downloadPreset,
722
940
  dbPreset,
941
+ rootPreset,
723
942
  envAddPreset,
724
943
  };
725
944
  }
@@ -746,7 +965,7 @@ export default class Install extends Command {
746
965
  if (!parsed.resume) {
747
966
  return undefined;
748
967
  }
749
- const env = await getEnv(parsed.env, { scope: CONFIG_SCOPE });
968
+ const env = await getEnv(parsed.env, { scope: resolveDefaultConfigScope() });
750
969
  if (!env) {
751
970
  throw new Error(formatMissingManagedAppEnvMessage(parsed.env));
752
971
  }
@@ -756,7 +975,7 @@ export default class Install extends Command {
756
975
  throw new Error([
757
976
  `Cannot continue setup for "${env.name}" in non-interactive resume mode yet.`,
758
977
  `These setup-only flags are not saved in the env config: ${missingFlags.join(', ')}`,
759
- `Run \`nb install --env ${env.name} --resume\` without \`--yes\`, or pass those flags again.`,
978
+ `Run \`nb init --env ${env.name} --resume\` without \`--yes\`, or pass those flags again.`,
760
979
  ].join('\n'));
761
980
  }
762
981
  }
@@ -861,7 +1080,11 @@ export default class Install extends Command {
861
1080
  preset.source = String(flags.source).trim();
862
1081
  }
863
1082
  if (flags.version !== undefined) {
864
- preset.version = String(flags.version).trim() || 'latest';
1083
+ const version = String(flags.version).trim() || 'latest';
1084
+ preset.version = downloadVersionPromptValue(version);
1085
+ if (preset.version === 'other') {
1086
+ preset.otherVersion = version;
1087
+ }
865
1088
  }
866
1089
  if (flags['docker-registry'] !== undefined) {
867
1090
  const value = String(flags['docker-registry'] ?? '').trim();
@@ -891,7 +1114,10 @@ export default class Install extends Command {
891
1114
  preset.npmRegistry =
892
1115
  typeof flags['npm-registry'] === 'string' ? flags['npm-registry'] : '';
893
1116
  }
894
- if (argvHasToken(argv, ['--replace', '-r'])) {
1117
+ if (flags.resume && !argvHasToken(argv, ['--replace', '-r'])) {
1118
+ preset.replace = true;
1119
+ }
1120
+ else if (argvHasToken(argv, ['--replace', '-r'])) {
895
1121
  preset.replace = flags.replace;
896
1122
  }
897
1123
  if (argvHasToken(argv, ['--dev-dependencies', '--no-dev-dependencies', '-D'])) {
@@ -923,7 +1149,7 @@ export default class Install extends Command {
923
1149
  return normalized || 'nocobase';
924
1150
  }
925
1151
  static defaultWorkspaceName() {
926
- return Install.sanitizeDockerResourceName(`nb-${path.basename(process.cwd())}`);
1152
+ return Install.sanitizeDockerResourceName(`nb-${path.basename(resolveEnvRoot(resolveDefaultConfigScope()))}`);
927
1153
  }
928
1154
  static buildBuiltinDbResourcePrefix(envName, workspaceName) {
929
1155
  void envName;
@@ -933,7 +1159,7 @@ export default class Install extends Command {
933
1159
  : Install.defaultWorkspaceName();
934
1160
  }
935
1161
  static async ensureWorkspaceName() {
936
- return await ensureWorkspaceName(Install.defaultWorkspaceName(), { scope: CONFIG_SCOPE });
1162
+ return await ensureWorkspaceName(Install.defaultWorkspaceName(), { scope: resolveDefaultConfigScope() });
937
1163
  }
938
1164
  static buildBuiltinDbNetworkName(envName, workspaceName) {
939
1165
  return Install.buildBuiltinDbResourcePrefix(envName, workspaceName);
@@ -982,9 +1208,11 @@ export default class Install extends Command {
982
1208
  && dbHostInput !== 'localhost'
983
1209
  ? dbHostInput
984
1210
  : containerName);
1211
+ const storagePath = resolveConfiguredEnvPath(params.storagePath)
1212
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
985
1213
  if (dbDialect === 'postgres') {
986
1214
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
987
- const dataDir = path.resolve(params.storagePath, 'db', 'postgres');
1215
+ const dataDir = path.resolve(storagePath, 'db', 'postgres');
988
1216
  const args = [
989
1217
  'run',
990
1218
  '-d',
@@ -1028,7 +1256,7 @@ export default class Install extends Command {
1028
1256
  }
1029
1257
  if (dbDialect === 'mysql') {
1030
1258
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1031
- const dataDir = path.resolve(params.storagePath, 'db', 'mysql');
1259
+ const dataDir = path.resolve(storagePath, 'db', 'mysql');
1032
1260
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1033
1261
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1034
1262
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1074,7 +1302,7 @@ export default class Install extends Command {
1074
1302
  }
1075
1303
  if (dbDialect === 'mariadb') {
1076
1304
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1077
- const dataDir = path.resolve(params.storagePath, 'db', 'mariadb');
1305
+ const dataDir = path.resolve(storagePath, 'db', 'mariadb');
1078
1306
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1079
1307
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1080
1308
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1120,7 +1348,7 @@ export default class Install extends Command {
1120
1348
  }
1121
1349
  if (dbDialect === 'kingbase') {
1122
1350
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1123
- const dataDir = path.resolve(params.storagePath, 'db', 'kingbase');
1351
+ const dataDir = path.resolve(storagePath, 'db', 'kingbase');
1124
1352
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1125
1353
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1126
1354
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1269,12 +1497,12 @@ export default class Install extends Command {
1269
1497
  });
1270
1498
  p.log.step(`Preparing built-in ${plan.dbDialect} database`);
1271
1499
  await this.ensureDockerNetwork(plan.networkName);
1272
- await this.removeDockerContainerIfForced({
1500
+ const existingContainerKept = await this.removeDockerContainerIfForced({
1273
1501
  containerName: plan.containerName,
1274
1502
  displayName: `built-in ${plan.dbDialect} container`,
1275
1503
  force: params.force,
1276
1504
  });
1277
- if (Install.shouldPublishBuiltinDbPort(params.downloadResults.source)) {
1505
+ if (!existingContainerKept && Install.shouldPublishBuiltinDbPort(params.downloadResults.source)) {
1278
1506
  const portError = await validateAvailableTcpPort(plan.dbPort);
1279
1507
  if (portError) {
1280
1508
  throw new Error(`Built-in ${plan.dbDialect} needs host port ${plan.dbPort}, but ${portError}`);
@@ -1291,8 +1519,9 @@ export default class Install extends Command {
1291
1519
  || defaultDockerRegistryForLang(process.env.NB_LOCALE);
1292
1520
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || 'latest';
1293
1521
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1294
- const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
1295
- || defaultInstallStoragePath(params.envName));
1522
+ const storagePath = resolveConfiguredEnvPath(String(params.appResults.storagePath ?? '').trim()
1523
+ || defaultInstallStoragePath(params.envName))
1524
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1296
1525
  const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
1297
1526
  const dbHost = String(params.dbResults.dbHost ?? DEFAULT_INSTALL_DB_HOST).trim() || DEFAULT_INSTALL_DB_HOST;
1298
1527
  const dbPort = String(params.dbResults.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
@@ -1387,12 +1616,16 @@ export default class Install extends Command {
1387
1616
  }
1388
1617
  static buildDownloadArgvFromResults(results, options) {
1389
1618
  const argv = ['-y', '--no-intro'];
1619
+ const source = String(results.source ?? '').trim();
1390
1620
  if (options?.verbose) {
1391
1621
  argv.push('--verbose');
1392
1622
  }
1393
1623
  Install.pushDownloadArgIfValue(argv, '--source', results.source);
1394
- Install.pushDownloadArgIfValue(argv, '--version', results.version);
1395
- Install.pushDownloadArgIfValue(argv, '--output-dir', results.outputDir);
1624
+ Install.pushDownloadArgIfValue(argv, '--version', downloadResultsValue(results, 'version'));
1625
+ Install.pushDownloadArgIfValue(argv, '--output-dir', source === 'npm' || source === 'git'
1626
+ ? (resolveConfiguredEnvPath(results.outputDir)
1627
+ ?? resolveConfiguredEnvPath(String(results.outputDir ?? '').trim() || defaultInstallAppRootPath(results.env)))
1628
+ : results.outputDir);
1396
1629
  Install.pushDownloadArgIfValue(argv, '--git-url', results.gitUrl);
1397
1630
  Install.pushDownloadArgIfValue(argv, '--docker-registry', results.dockerRegistry);
1398
1631
  Install.pushDownloadArgIfValue(argv, '--docker-platform', results.dockerPlatform);
@@ -1422,7 +1655,12 @@ export default class Install extends Command {
1422
1655
  const outputDir = String(params.downloadResults.outputDir ?? '').trim()
1423
1656
  || String(params.appResults.appRootPath ?? '').trim()
1424
1657
  || defaultInstallAppRootPath(params.envName);
1425
- return path.resolve(process.cwd(), outputDir);
1658
+ return resolveConfiguredEnvPath(outputDir) ?? resolveEnvRelativePath(defaultInstallAppRootPath(params.envName));
1659
+ }
1660
+ static resolveLocalProjectConfigPath(params) {
1661
+ return (String(params.downloadResults.outputDir ?? '').trim()
1662
+ || String(params.appResults.appRootPath ?? '').trim()
1663
+ || defaultInstallAppRootPath(params.envName));
1426
1664
  }
1427
1665
  commandStdio(verbose) {
1428
1666
  return verbose ? 'inherit' : 'ignore';
@@ -1435,25 +1673,31 @@ export default class Install extends Command {
1435
1673
  p.log.step(source === 'docker'
1436
1674
  ? 'Downloading Docker image'
1437
1675
  : 'Downloading local NocoBase app files');
1438
- return await this.config.runCommand('download', argv);
1676
+ return await this.config.runCommand('source:download', argv);
1439
1677
  }
1440
1678
  async downloadLocalApp(params) {
1441
1679
  const result = await this.downloadManagedSource({
1442
1680
  downloadResults: params.downloadResults,
1443
1681
  verbose: params.verbose,
1444
1682
  });
1445
- const projectRoot = Install.resolveLocalProjectRoot({
1683
+ const downloadedProjectRoot = Install.resolveLocalProjectRoot({
1446
1684
  envName: params.envName,
1447
1685
  appResults: params.appResults,
1448
1686
  downloadResults: params.downloadResults,
1449
1687
  downloadCommandResult: result,
1450
1688
  });
1451
- params.appResults.appRootPath = projectRoot;
1452
- return projectRoot;
1689
+ params.appResults.appRootPath = Install.resolveLocalProjectConfigPath({
1690
+ envName: params.envName,
1691
+ appResults: params.appResults,
1692
+ downloadResults: params.downloadResults,
1693
+ });
1694
+ return downloadedProjectRoot;
1453
1695
  }
1454
1696
  static buildLocalAppEnvVars(params) {
1455
- const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
1456
- || defaultInstallStoragePath(params.envName));
1697
+ const configuredStoragePath = String(params.appResults.storagePath ?? '').trim()
1698
+ || defaultInstallStoragePath(params.envName);
1699
+ const storagePath = resolveConfiguredEnvPath(configuredStoragePath)
1700
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1457
1701
  const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim()
1458
1702
  || 'postgres';
1459
1703
  const appKey = crypto.randomBytes(32).toString('hex');
@@ -1618,21 +1862,20 @@ export default class Install extends Command {
1618
1862
  throw new Error(`The application did not become ready in time. Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${Install.formatHealthCheckMessage(lastMessage)}.${logHint}`);
1619
1863
  }
1620
1864
  async saveInstalledEnv(params) {
1621
- await this.config.runCommand('env:add', Install.buildEnvAddArgv(params));
1622
- }
1623
- static pushArgIfValue(argv, flag, value) {
1624
- const text = String(value ?? '').trim();
1625
- if (text) {
1626
- argv.push(flag, text);
1627
- }
1865
+ await upsertEnv(params.envName, Install.buildSavedEnvConfig(params), { scope: resolveDefaultConfigScope() });
1628
1866
  }
1629
- static pushBooleanArgIfSet(argv, flag, value) {
1630
- if (value === undefined) {
1867
+ async syncInstalledEnvConnection(params) {
1868
+ if (!params.appReady) {
1631
1869
  return;
1632
1870
  }
1633
- argv.push(Boolean(value) ? flag : `--no-${flag.replace(/^--/, '')}`);
1871
+ const authType = String(params.envAddResults.authType ?? 'oauth').trim()
1872
+ || 'oauth';
1873
+ if (authType === 'oauth') {
1874
+ await this.config.runCommand('env:auth', [params.envName]);
1875
+ }
1876
+ await this.config.runCommand('env:update', [params.envName]);
1634
1877
  }
1635
- static buildEnvAddArgv(params) {
1878
+ static buildSavedEnvConfig(params) {
1636
1879
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim()
1637
1880
  || DEFAULT_INSTALL_APP_PORT;
1638
1881
  const storagePath = String(params.appResults.storagePath ?? '').trim()
@@ -1643,44 +1886,37 @@ export default class Install extends Command {
1643
1886
  });
1644
1887
  const authType = String(params.envAddResults.authType ?? 'oauth').trim()
1645
1888
  || 'oauth';
1646
- const argv = [
1647
- params.envName,
1648
- '--no-intro',
1649
- '--scope',
1650
- CONFIG_SCOPE,
1651
- '--api-base-url',
1889
+ return buildStoredEnvConfig({
1652
1890
  apiBaseUrl,
1653
- '--auth-type',
1654
1891
  authType,
1655
- '--app-port',
1892
+ accessToken: params.envAddResults.accessToken,
1893
+ source: downloadResultsValue(params.downloadResults, 'source'),
1894
+ downloadVersion: downloadResultsValue(params.downloadResults, 'version'),
1895
+ dockerRegistry: downloadResultsValue(params.downloadResults, 'dockerRegistry'),
1896
+ dockerPlatform: downloadResultsValue(params.downloadResults, 'dockerPlatform'),
1897
+ gitUrl: downloadResultsValue(params.downloadResults, 'gitUrl'),
1898
+ npmRegistry: downloadResultsValue(params.downloadResults, 'npmRegistry'),
1899
+ devDependencies: downloadResultsValue(params.downloadResults, 'devDependencies'),
1900
+ build: downloadResultsValue(params.downloadResults, 'build'),
1901
+ buildDts: downloadResultsValue(params.downloadResults, 'buildDts'),
1902
+ appRootPath: params.appResults.appRootPath,
1656
1903
  appPort,
1657
- '--storage-path',
1658
1904
  storagePath,
1659
- ];
1660
- Install.pushArgIfValue(argv, '--source', downloadResultsValue(params.downloadResults, 'source'));
1661
- Install.pushArgIfValue(argv, '--download-version', downloadResultsValue(params.downloadResults, 'version'));
1662
- Install.pushArgIfValue(argv, '--docker-registry', downloadResultsValue(params.downloadResults, 'dockerRegistry'));
1663
- Install.pushArgIfValue(argv, '--docker-platform', downloadResultsValue(params.downloadResults, 'dockerPlatform'));
1664
- Install.pushArgIfValue(argv, '--git-url', downloadResultsValue(params.downloadResults, 'gitUrl'));
1665
- Install.pushArgIfValue(argv, '--npm-registry', downloadResultsValue(params.downloadResults, 'npmRegistry'));
1666
- Install.pushBooleanArgIfSet(argv, '--dev-dependencies', downloadResultsValue(params.downloadResults, 'devDependencies'));
1667
- Install.pushBooleanArgIfSet(argv, '--build', downloadResultsValue(params.downloadResults, 'build'));
1668
- Install.pushBooleanArgIfSet(argv, '--build-dts', downloadResultsValue(params.downloadResults, 'buildDts'));
1669
- Install.pushArgIfValue(argv, '--app-root-path', params.appResults.appRootPath);
1670
- Install.pushArgIfValue(argv, '--app-key', params.appResults.appKey);
1671
- Install.pushArgIfValue(argv, '--timezone', params.appResults.timeZone);
1672
- Install.pushBooleanArgIfSet(argv, '--builtin-db', params.dbResults.builtinDb);
1673
- Install.pushArgIfValue(argv, '--db-dialect', params.dbResults.dbDialect);
1674
- Install.pushArgIfValue(argv, '--builtin-db-image', params.dbResults.builtinDbImage);
1675
- Install.pushArgIfValue(argv, '--db-host', params.dbResults.dbHost);
1676
- Install.pushArgIfValue(argv, '--db-port', params.dbResults.dbPort);
1677
- Install.pushArgIfValue(argv, '--db-database', params.dbResults.dbDatabase);
1678
- Install.pushArgIfValue(argv, '--db-user', params.dbResults.dbUser);
1679
- Install.pushArgIfValue(argv, '--db-password', params.dbResults.dbPassword);
1680
- if (authType === 'token') {
1681
- argv.push('--access-token', String(params.envAddResults.accessToken ?? ''));
1682
- }
1683
- return argv;
1905
+ appKey: params.appResults.appKey,
1906
+ timezone: params.appResults.timeZone,
1907
+ builtinDb: params.dbResults.builtinDb,
1908
+ dbDialect: params.dbResults.dbDialect,
1909
+ builtinDbImage: params.dbResults.builtinDbImage,
1910
+ dbHost: params.dbResults.dbHost,
1911
+ dbPort: params.dbResults.dbPort,
1912
+ dbDatabase: params.dbResults.dbDatabase,
1913
+ dbUser: params.dbResults.dbUser,
1914
+ dbPassword: params.dbResults.dbPassword,
1915
+ rootUsername: params.rootResults.rootUsername,
1916
+ rootEmail: params.rootResults.rootEmail,
1917
+ rootPassword: params.rootResults.rootPassword,
1918
+ rootNickname: params.rootResults.rootNickname,
1919
+ });
1684
1920
  }
1685
1921
  async collectPromptResults(parsed, yes) {
1686
1922
  const resumePreset = await this.resolveResumePresetValues(parsed, yes);
@@ -1700,7 +1936,9 @@ export default class Install extends Command {
1700
1936
  ...(resumePreset?.appPreset ?? {}),
1701
1937
  ...Install.buildAppPresetValuesFromFlags(parsed),
1702
1938
  };
1703
- const appCatalog = Install.buildAppPromptsCatalog(envName);
1939
+ const appCatalog = Install.buildAppPromptsCatalog(envName, {
1940
+ resume: parsed.resume,
1941
+ });
1704
1942
  const appResults = await runPromptCatalog(appCatalog, {
1705
1943
  initialValues: await Install.buildAppPromptInitialValues({
1706
1944
  envName,
@@ -1715,6 +1953,7 @@ export default class Install extends Command {
1715
1953
  },
1716
1954
  }),
1717
1955
  values: appPreset,
1956
+ yesInitialValues: { resume: parsed.resume },
1718
1957
  yes,
1719
1958
  });
1720
1959
  let downloadResults = {};
@@ -1732,7 +1971,9 @@ export default class Install extends Command {
1732
1971
  ...(resumePreset?.dbPreset ?? {}),
1733
1972
  ...Install.buildDbPresetValuesFromFlags(parsed),
1734
1973
  };
1735
- const dbResults = await runPromptCatalog(Install.buildDbPromptsCatalog(downloadResults), {
1974
+ const dbResults = await runPromptCatalog(Install.buildDbPromptsCatalog(envName, downloadResults, {
1975
+ resume: parsed.resume,
1976
+ }), {
1736
1977
  initialValues: {
1737
1978
  ...downloadResults,
1738
1979
  ...await Install.buildDbPromptInitialValues({
@@ -1751,7 +1992,10 @@ export default class Install extends Command {
1751
1992
  const rootPreset = Install.buildRootPresetValuesFromFlags(parsed);
1752
1993
  const rootResults = await runPromptCatalog(Install.rootUserPrompts, {
1753
1994
  initialValues: {},
1754
- values: rootPreset,
1995
+ values: {
1996
+ ...(resumePreset?.rootPreset ?? {}),
1997
+ ...rootPreset,
1998
+ },
1755
1999
  yes,
1756
2000
  });
1757
2001
  const envAddResults = await runPromptCatalog(EnvAdd.prompts, {
@@ -1760,8 +2004,8 @@ export default class Install extends Command {
1760
2004
  },
1761
2005
  values: {
1762
2006
  name: envName,
1763
- scope: 'project',
1764
2007
  ...(resumePreset?.envAddPreset ?? {}),
2008
+ ...Install.buildEnvAddPresetValuesFromFlags(parsed),
1765
2009
  },
1766
2010
  yes,
1767
2011
  });
@@ -1800,6 +2044,16 @@ export default class Install extends Command {
1800
2044
  const workspaceName = usesDockerResources
1801
2045
  ? await Install.ensureWorkspaceName()
1802
2046
  : undefined;
2047
+ if (!parsed.resume) {
2048
+ await this.saveInstalledEnv({
2049
+ envName,
2050
+ appResults,
2051
+ downloadResults,
2052
+ dbResults,
2053
+ rootResults,
2054
+ envAddResults,
2055
+ });
2056
+ }
1803
2057
  let builtinDbPlan;
1804
2058
  if (Boolean(dbResults.builtinDb)) {
1805
2059
  builtinDbPlan = await this.startBuiltinDb({
@@ -1841,6 +2095,7 @@ export default class Install extends Command {
1841
2095
  appResults.timeZone = dockerAppPlan.timeZone;
1842
2096
  }
1843
2097
  else if (source === 'npm' || source === 'git') {
2098
+ const localSource = source === 'npm' ? 'npm' : 'git';
1844
2099
  const projectRoot = await this.downloadLocalApp({
1845
2100
  envName,
1846
2101
  appResults,
@@ -1849,7 +2104,7 @@ export default class Install extends Command {
1849
2104
  });
1850
2105
  localAppPlan = await this.startLocalApp({
1851
2106
  envName,
1852
- source,
2107
+ source: localSource,
1853
2108
  projectRoot,
1854
2109
  appResults,
1855
2110
  dbResults,
@@ -1871,12 +2126,20 @@ export default class Install extends Command {
1871
2126
  containerName: dockerAppPlan?.containerName,
1872
2127
  });
1873
2128
  }
1874
- await this.saveInstalledEnv({
2129
+ if (dockerAppPlan || localAppPlan || builtinDbPlan) {
2130
+ await this.saveInstalledEnv({
2131
+ envName,
2132
+ appResults,
2133
+ downloadResults,
2134
+ dbResults,
2135
+ rootResults,
2136
+ envAddResults,
2137
+ });
2138
+ }
2139
+ await this.syncInstalledEnvConnection({
1875
2140
  envName,
1876
- appResults,
1877
- downloadResults,
1878
- dbResults,
1879
2141
  envAddResults,
2142
+ appReady: Boolean(dockerAppPlan || localAppPlan),
1880
2143
  });
1881
2144
  p.outro(dockerAppPlan || localAppPlan
1882
2145
  ? `NocoBase is ready at http://127.0.0.1:${dockerAppPlan?.appPort ?? localAppPlan?.appPort}`
@@ -1884,5 +2147,8 @@ export default class Install extends Command {
1884
2147
  }
1885
2148
  }
1886
2149
  function downloadResultsValue(downloadResults, key) {
2150
+ if (key === 'version' && String(downloadResults.version ?? '').trim() === 'other') {
2151
+ return downloadResults.otherVersion;
2152
+ }
1887
2153
  return downloadResults[key];
1888
2154
  }