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

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 (84) hide show
  1. package/README.md +61 -49
  2. package/README.zh-CN.md +40 -47
  3. package/dist/commands/app/down.js +259 -0
  4. package/dist/commands/app/logs.js +98 -0
  5. package/dist/commands/app/restart.js +75 -0
  6. package/dist/commands/app/start.js +252 -0
  7. package/dist/commands/app/stop.js +98 -0
  8. package/dist/commands/app/upgrade.js +579 -0
  9. package/dist/commands/build.js +3 -48
  10. package/dist/commands/config/delete.js +30 -0
  11. package/dist/commands/config/get.js +29 -0
  12. package/dist/commands/config/index.js +20 -0
  13. package/dist/commands/config/list.js +29 -0
  14. package/dist/commands/config/set.js +35 -0
  15. package/dist/commands/db/check.js +230 -0
  16. package/dist/commands/db/shared.js +1 -1
  17. package/dist/commands/dev.js +3 -147
  18. package/dist/commands/down.js +3 -188
  19. package/dist/commands/download.js +4 -856
  20. package/dist/commands/env/add.js +28 -23
  21. package/dist/commands/env/info.js +152 -0
  22. package/dist/commands/env/list.js +23 -9
  23. package/dist/commands/env/shared.js +158 -0
  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 +83 -6
  27. package/dist/commands/install.js +361 -82
  28. package/dist/commands/license/activate.js +357 -0
  29. package/dist/commands/license/env.js +94 -0
  30. package/dist/commands/license/generate-id.js +107 -0
  31. package/dist/commands/license/id.js +52 -0
  32. package/dist/commands/license/index.js +20 -0
  33. package/dist/commands/license/plugins/clean.js +98 -0
  34. package/dist/commands/license/plugins/index.js +20 -0
  35. package/dist/commands/license/plugins/list.js +50 -0
  36. package/dist/commands/license/plugins/shared.js +325 -0
  37. package/dist/commands/license/plugins/sync.js +267 -0
  38. package/dist/commands/license/shared.js +411 -0
  39. package/dist/commands/license/status.js +50 -0
  40. package/dist/commands/logs.js +3 -88
  41. package/dist/commands/plugin/disable.js +64 -0
  42. package/dist/commands/plugin/enable.js +64 -0
  43. package/dist/commands/plugin/list.js +62 -0
  44. package/dist/commands/pm/disable.js +3 -54
  45. package/dist/commands/pm/enable.js +3 -54
  46. package/dist/commands/pm/list.js +3 -52
  47. package/dist/commands/restart.js +3 -65
  48. package/dist/commands/scaffold/migration.js +1 -1
  49. package/dist/commands/scaffold/plugin.js +1 -1
  50. package/dist/commands/skills/remove.js +71 -0
  51. package/dist/commands/skills/update.js +7 -0
  52. package/dist/commands/source/build.js +58 -0
  53. package/dist/commands/source/dev.js +157 -0
  54. package/dist/commands/source/download.js +866 -0
  55. package/dist/commands/source/test.js +467 -0
  56. package/dist/commands/start.js +3 -209
  57. package/dist/commands/stop.js +3 -88
  58. package/dist/commands/test.js +3 -457
  59. package/dist/commands/upgrade.js +3 -585
  60. package/dist/help/runtime-help.js +3 -0
  61. package/dist/lib/api-client.js +94 -9
  62. package/dist/lib/app-health.js +126 -0
  63. package/dist/lib/app-managed-resources.js +264 -0
  64. package/dist/lib/app-runtime.js +26 -10
  65. package/dist/lib/auth-store.js +29 -63
  66. package/dist/lib/build-config.js +8 -0
  67. package/dist/lib/cli-config.js +176 -0
  68. package/dist/lib/cli-home.js +12 -26
  69. package/dist/lib/cli-locale.js +15 -1
  70. package/dist/lib/db-connection-check.js +178 -0
  71. package/dist/lib/env-config.js +80 -0
  72. package/dist/lib/generated-command.js +23 -3
  73. package/dist/lib/plugin-storage.js +127 -0
  74. package/dist/lib/prompt-validators.js +4 -4
  75. package/dist/lib/prompt-web-ui.js +13 -6
  76. package/dist/lib/runtime-generator.js +89 -10
  77. package/dist/lib/self-manager.js +57 -2
  78. package/dist/lib/skills-manager.js +34 -7
  79. package/dist/lib/startup-update.js +85 -7
  80. package/dist/locale/en-US.json +16 -13
  81. package/dist/locale/zh-CN.json +16 -13
  82. package/nocobase-ctl.config.json +82 -0
  83. package/package.json +41 -6
  84. package/dist/commands/ps.js +0 -119
@@ -16,12 +16,16 @@ 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, resolveEnvRelativePath, } from '../lib/cli-home.js';
19
+ import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
20
+ import { defaultDockerContainerPrefix, defaultDockerNetworkName, } from '../lib/app-runtime.js';
21
+ import { resolveDockerContainerPrefix, resolveDockerNetworkName, } from '../lib/cli-config.js';
20
22
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
23
+ import { validateExternalDbConfig } from "../lib/db-connection-check.js";
21
24
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
22
25
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
23
26
  import { startTask, stopTask, updateTask } from '../lib/ui.js';
24
- import { ensureWorkspaceName, getEnv } from '../lib/auth-store.js';
27
+ import { getEnv, upsertEnv } from '../lib/auth-store.js';
28
+ import { buildStoredEnvConfig } from '../lib/env-config.js';
25
29
  import Download, { defaultDockerRegistryForLang, } from './download.js';
26
30
  import EnvAdd from "./env/add.js";
27
31
  const DEFAULT_INSTALL_ENV_NAME = 'local';
@@ -179,6 +183,16 @@ function validateBuiltinDbEnabled(value, values) {
179
183
  }
180
184
  return translateCli('commands.install.validation.builtinDbUnsupported', { dialect });
181
185
  }
186
+ async function validateExternalDbPromptField(value, values) {
187
+ const builtinDb = values.builtinDb === undefined ? true : Boolean(values.builtinDb);
188
+ if (builtinDb) {
189
+ return undefined;
190
+ }
191
+ if (typeof value === 'string' && value.trim() === '') {
192
+ return undefined;
193
+ }
194
+ return await validateExternalDbConfig(values);
195
+ }
182
196
  function defaultInstallAppRootPath(envName) {
183
197
  const name = String(envName ?? DEFAULT_INSTALL_ENV_NAME).trim() || DEFAULT_INSTALL_ENV_NAME;
184
198
  return `./${name}/source/`;
@@ -245,7 +259,8 @@ async function commandOutput(command, args, options) {
245
259
  });
246
260
  }
247
261
  export default class Install extends Command {
248
- 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.';
262
+ static hidden = true;
263
+ 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.';
249
264
  static examples = [
250
265
  '<%= config.bin %> <%= command.id %>',
251
266
  '<%= config.bin %> <%= command.id %> --env app1',
@@ -276,6 +291,25 @@ export default class Install extends Command {
276
291
  char: 'e',
277
292
  description: 'App/env name to create or update. Defaults app paths to ./<envName>/source/ and ./<envName>/storage/.',
278
293
  }),
294
+ 'default-api-base-url': Flags.string({
295
+ char: 'd',
296
+ hidden: true,
297
+ description: 'Default API base URL for HTTP API calls, including the /api prefix (e.g. http://localhost:13000/api)',
298
+ }),
299
+ 'api-base-url': Flags.string({
300
+ char: 'u',
301
+ description: 'Root URL for HTTP API calls, including the /api prefix (e.g. http://localhost:13000/api)',
302
+ }),
303
+ 'auth-type': Flags.string({
304
+ char: 'a',
305
+ description: 'Authentication: token (API key) or oauth (browser login via `nb env auth`)',
306
+ options: ['token', 'oauth'],
307
+ }),
308
+ 'access-token': Flags.string({
309
+ char: 't',
310
+ aliases: ['token'],
311
+ description: 'API key or access token when using --auth-type token',
312
+ }),
279
313
  lang: Flags.string({ description: 'Language for the installed NocoBase app', char: 'l', required: false }),
280
314
  force: Flags.boolean({
281
315
  description: 'Reconfigure an existing env and replace conflicting runtime resources when needed',
@@ -374,7 +408,7 @@ export default class Install extends Command {
374
408
  type: 'text',
375
409
  message: installText('prompts.appPort.message'),
376
410
  placeholder: installText('prompts.appPort.placeholder'),
377
- validate: validateAvailableTcpPort,
411
+ validate: Install.validateAppPort,
378
412
  },
379
413
  storagePath: {
380
414
  type: 'text',
@@ -426,6 +460,7 @@ export default class Install extends Command {
426
460
  initialValue: (values) => defaultDbHostForBuiltinDb(values),
427
461
  yesInitialValue: DEFAULT_INSTALL_BUILTIN_DB_HOST,
428
462
  required: true,
463
+ validate: validateExternalDbPromptField,
429
464
  hidden: (values) => Boolean(values.builtinDb),
430
465
  },
431
466
  dbPort: {
@@ -434,7 +469,7 @@ export default class Install extends Command {
434
469
  placeholder: installText('prompts.dbPort.placeholder'),
435
470
  initialValue: (values) => defaultDbPortForDialect(values.dbDialect),
436
471
  required: true,
437
- validate: validateTcpPort,
472
+ validate: Install.validateDbPort,
438
473
  hidden: (values) => Boolean(values.builtinDb)
439
474
  && String(values.source ?? '').trim() === 'docker',
440
475
  },
@@ -443,6 +478,7 @@ export default class Install extends Command {
443
478
  message: installText('prompts.dbDatabase.message'),
444
479
  initialValue: (values) => defaultDbDatabaseForDialect(values.dbDialect),
445
480
  required: true,
481
+ validate: validateExternalDbPromptField,
446
482
  },
447
483
  dbUser: {
448
484
  type: 'text',
@@ -450,6 +486,7 @@ export default class Install extends Command {
450
486
  initialValue: DEFAULT_INSTALL_DB_USER,
451
487
  yesInitialValue: DEFAULT_INSTALL_DB_USER,
452
488
  required: true,
489
+ validate: validateExternalDbPromptField,
453
490
  },
454
491
  dbPassword: {
455
492
  type: 'password',
@@ -457,6 +494,7 @@ export default class Install extends Command {
457
494
  initialValue: DEFAULT_INSTALL_DB_PASSWORD,
458
495
  yesInitialValue: DEFAULT_INSTALL_DB_PASSWORD,
459
496
  required: true,
497
+ validate: validateExternalDbPromptField,
460
498
  },
461
499
  };
462
500
  static rootUserPrompts = {
@@ -492,7 +530,7 @@ export default class Install extends Command {
492
530
  * App catalog with `env` seeded into `out` first so `storagePath`’s `initialValue(values)`
493
531
  * sees `values.env` (same iteration order as {@link runPromptCatalog}).
494
532
  */
495
- static buildAppPromptsCatalog(seedEnv) {
533
+ static buildAppPromptsCatalog(seedEnv, options) {
496
534
  return {
497
535
  seedEnv: {
498
536
  type: 'run',
@@ -500,12 +538,27 @@ export default class Install extends Command {
500
538
  values.env = seedEnv;
501
539
  },
502
540
  },
541
+ seedResume: {
542
+ type: 'run',
543
+ run: (values) => {
544
+ const record = values;
545
+ record.resume = Boolean(options?.resume);
546
+ },
547
+ },
503
548
  ...Install.appPrompts,
504
549
  };
505
550
  }
506
- static buildDbPromptsCatalog(downloadResults) {
551
+ static buildDbPromptsCatalog(envName, downloadResults, options) {
507
552
  const source = String(downloadResults.source ?? '').trim();
508
553
  return {
554
+ seedEnv: {
555
+ type: 'run',
556
+ run: (values) => {
557
+ if (envName) {
558
+ values.env = envName;
559
+ }
560
+ },
561
+ },
509
562
  seedDownloadSource: {
510
563
  type: 'run',
511
564
  run: (values) => {
@@ -514,6 +567,13 @@ export default class Install extends Command {
514
567
  }
515
568
  },
516
569
  },
570
+ seedResume: {
571
+ type: 'run',
572
+ run: (values) => {
573
+ const record = values;
574
+ record.resume = Boolean(options?.resume);
575
+ },
576
+ },
517
577
  ...Install.dbPrompts,
518
578
  };
519
579
  }
@@ -533,6 +593,25 @@ export default class Install extends Command {
533
593
  static buildPresetValuesFromFlags(flags) {
534
594
  const preset = {};
535
595
  const argv = process.argv.slice(2);
596
+ const apiBaseUrl = Install.toOptionalPromptString(flags['api-base-url']);
597
+ if (apiBaseUrl) {
598
+ preset.apiBaseUrl = apiBaseUrl;
599
+ }
600
+ else if (flags['default-api-base-url'] !== undefined) {
601
+ const defaultApiBaseUrl = Install.toOptionalPromptString(flags['default-api-base-url']);
602
+ if (defaultApiBaseUrl) {
603
+ preset.apiBaseUrl = defaultApiBaseUrl;
604
+ }
605
+ }
606
+ if (flags['auth-type'] !== undefined) {
607
+ const authType = Install.toOptionalPromptString(flags['auth-type']);
608
+ if (authType) {
609
+ preset.authType = authType;
610
+ }
611
+ }
612
+ if (flags['access-token'] !== undefined || flags.token !== undefined) {
613
+ preset.accessToken = String(flags['access-token'] ?? flags.token ?? '');
614
+ }
536
615
  if (flags.lang !== undefined) {
537
616
  const v = String(flags.lang).trim();
538
617
  if (v) {
@@ -650,6 +729,13 @@ export default class Install extends Command {
650
729
  'rootNickname',
651
730
  ]);
652
731
  }
732
+ static buildEnvAddPresetValuesFromFlags(flags) {
733
+ return pickPresetKeys(Install.buildPresetValuesFromFlags(flags), [
734
+ 'apiBaseUrl',
735
+ 'authType',
736
+ 'accessToken',
737
+ ]);
738
+ }
653
739
  static toOptionalPromptString(value) {
654
740
  if (value === undefined || value === null) {
655
741
  return undefined;
@@ -657,6 +743,150 @@ export default class Install extends Command {
657
743
  const text = String(value).trim();
658
744
  return text || undefined;
659
745
  }
746
+ static async validateAppPort(value, values) {
747
+ const formatError = validateTcpPort(value);
748
+ if (formatError) {
749
+ return formatError;
750
+ }
751
+ return await Install.validateResumeAwareTcpPort(value, values, 'app');
752
+ }
753
+ static async validateDbPort(value, values) {
754
+ const formatError = validateTcpPort(value);
755
+ if (formatError) {
756
+ return formatError;
757
+ }
758
+ const builtinDb = values.builtinDb === undefined ? true : Boolean(values.builtinDb);
759
+ const source = String(values.source ?? '').trim();
760
+ if (!builtinDb || source === 'docker') {
761
+ if (!builtinDb) {
762
+ return await validateExternalDbConfig({ ...values, dbPort: value });
763
+ }
764
+ return undefined;
765
+ }
766
+ return await Install.validateResumeAwareTcpPort(value, values, 'db');
767
+ }
768
+ static async validateResumeAwareTcpPort(value, values, target) {
769
+ const portError = await validateAvailableTcpPort(value);
770
+ if (!portError) {
771
+ return undefined;
772
+ }
773
+ const context = await Install.readResumePortValidationContext(values);
774
+ if (!context) {
775
+ return portError;
776
+ }
777
+ const port = Install.toOptionalPromptString(value);
778
+ if (!port) {
779
+ return portError;
780
+ }
781
+ const reusesManagedPort = await Install.isResumeManagedPortReuse({
782
+ target,
783
+ port,
784
+ context,
785
+ });
786
+ return reusesManagedPort ? undefined : portError;
787
+ }
788
+ static async ensureExternalDbReadyForInstall(dbResults) {
789
+ const builtinDb = dbResults.builtinDb === undefined ? true : Boolean(dbResults.builtinDb);
790
+ if (builtinDb) {
791
+ return;
792
+ }
793
+ const dialect = String(dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
794
+ const host = String(dbResults.dbHost ?? '').trim();
795
+ const port = String(dbResults.dbPort ?? '').trim();
796
+ const database = String(dbResults.dbDatabase ?? '').trim();
797
+ const address = host && port ? `${host}:${port}` : host || port || '(unknown address)';
798
+ const target = database ? `${address}/${database}` : address;
799
+ p.log.step(`Checking external ${dialect} database: ${target}`);
800
+ const validationError = await validateExternalDbConfig(dbResults);
801
+ if (validationError) {
802
+ throw new Error(validationError);
803
+ }
804
+ }
805
+ static async readResumePortValidationContext(values) {
806
+ if (!Boolean(values.resume)) {
807
+ return undefined;
808
+ }
809
+ const envName = Install.toOptionalPromptString(values.env);
810
+ if (!envName) {
811
+ return undefined;
812
+ }
813
+ const source = Install.toOptionalPromptString(values.source);
814
+ const builtinDb = values.builtinDb === undefined ? undefined : Boolean(values.builtinDb);
815
+ const dbDialect = Install.toOptionalPromptString(values.dbDialect);
816
+ const appRootPath = Install.toOptionalPromptString(values.appRootPath);
817
+ const dockerNetworkName = await Install.resolveResumeDockerNetworkName();
818
+ const dockerContainerPrefix = await Install.resolveResumeDockerContainerPrefix();
819
+ return {
820
+ envName,
821
+ ...(dockerNetworkName ? { dockerNetworkName } : {}),
822
+ ...(dockerContainerPrefix ? { dockerContainerPrefix } : {}),
823
+ ...(source ? { source } : {}),
824
+ ...(builtinDb !== undefined ? { builtinDb } : {}),
825
+ ...(dbDialect ? { dbDialect } : {}),
826
+ ...(appRootPath ? { appRootPath } : {}),
827
+ };
828
+ }
829
+ static async resolveResumeDockerNetworkName() {
830
+ return await resolveDockerNetworkName({ scope: resolveDefaultConfigScope() });
831
+ }
832
+ static async resolveResumeDockerContainerPrefix() {
833
+ return await resolveDockerContainerPrefix({ scope: resolveDefaultConfigScope() });
834
+ }
835
+ static async isResumeManagedPortReuse(params) {
836
+ if (params.target === 'app') {
837
+ if ((params.context.source === 'npm' || params.context.source === 'git')
838
+ && params.context.appRootPath) {
839
+ return await Install.isLocalPm2ProcessUsingPort(params.context.appRootPath, params.port);
840
+ }
841
+ const containerName = Install.buildDockerAppContainerName(params.context.envName, params.context.dockerContainerPrefix);
842
+ return await Install.isDockerContainerPublishingPort(containerName, params.port);
843
+ }
844
+ if (!params.context.builtinDb || params.context.source === 'docker') {
845
+ return false;
846
+ }
847
+ const containerName = Install.buildBuiltinDbContainerName(params.context.envName, params.context.dbDialect ?? 'postgres', params.context.dockerContainerPrefix);
848
+ return await Install.isDockerContainerPublishingPort(containerName, params.port);
849
+ }
850
+ static async isDockerContainerPublishingPort(containerName, port) {
851
+ if (!containerName || !port) {
852
+ return false;
853
+ }
854
+ const exists = await commandSucceeds('docker', [
855
+ 'container',
856
+ 'inspect',
857
+ containerName,
858
+ ]);
859
+ if (!exists) {
860
+ return false;
861
+ }
862
+ try {
863
+ const output = await commandOutput('docker', ['port', containerName]);
864
+ return output
865
+ .split(/\r?\n/)
866
+ .some((line) => line.includes(`:${port}`));
867
+ }
868
+ catch {
869
+ return false;
870
+ }
871
+ }
872
+ static async isLocalPm2ProcessUsingPort(appRootPath, port) {
873
+ const cwd = resolveConfiguredEnvPath(appRootPath);
874
+ if (!cwd) {
875
+ return false;
876
+ }
877
+ try {
878
+ const output = await commandOutput('pm2', ['jlist'], { cwd });
879
+ const rows = JSON.parse(output);
880
+ return rows.some((row) => {
881
+ const pmCwd = Install.toOptionalPromptString(row.pm2_env?.pm_cwd);
882
+ const appPort = Install.toOptionalPromptString(row.pm2_env?.env?.APP_PORT);
883
+ return Boolean(pmCwd && appPort && pmCwd === cwd && appPort === port);
884
+ });
885
+ }
886
+ catch {
887
+ return false;
888
+ }
889
+ }
660
890
  static buildResumePresetValues(env) {
661
891
  const envName = String(env.name ?? '').trim();
662
892
  const config = env.config ?? {};
@@ -676,6 +906,10 @@ export default class Install extends Command {
676
906
  const dbUser = Install.toOptionalPromptString(config.dbUser);
677
907
  const dbPassword = Install.toOptionalPromptString(config.dbPassword);
678
908
  const builtinDbImage = Install.toOptionalPromptString(config.builtinDbImage);
909
+ const rootUsername = Install.toOptionalPromptString(config.rootUsername);
910
+ const rootEmail = Install.toOptionalPromptString(config.rootEmail);
911
+ const rootPassword = Install.toOptionalPromptString(config.rootPassword);
912
+ const rootNickname = Install.toOptionalPromptString(config.rootNickname);
679
913
  const auth = config.auth;
680
914
  const appPreset = {
681
915
  ...(appRootPath ? { appRootPath } : {}),
@@ -717,6 +951,12 @@ export default class Install extends Command {
717
951
  ...(dbUser ? { dbUser } : {}),
718
952
  ...(dbPassword ? { dbPassword } : {}),
719
953
  };
954
+ const rootPreset = {
955
+ ...(rootUsername ? { rootUsername } : {}),
956
+ ...(rootEmail ? { rootEmail } : {}),
957
+ ...(rootPassword ? { rootPassword } : {}),
958
+ ...(rootNickname ? { rootNickname } : {}),
959
+ };
720
960
  const envAddPreset = {};
721
961
  if (auth?.type === 'token') {
722
962
  envAddPreset.authType = 'token';
@@ -734,6 +974,7 @@ export default class Install extends Command {
734
974
  appPreset,
735
975
  downloadPreset,
736
976
  dbPreset,
977
+ rootPreset,
737
978
  envAddPreset,
738
979
  };
739
980
  }
@@ -770,7 +1011,7 @@ export default class Install extends Command {
770
1011
  throw new Error([
771
1012
  `Cannot continue setup for "${env.name}" in non-interactive resume mode yet.`,
772
1013
  `These setup-only flags are not saved in the env config: ${missingFlags.join(', ')}`,
773
- `Run \`nb install --env ${env.name} --resume\` without \`--yes\`, or pass those flags again.`,
1014
+ `Run \`nb init --env ${env.name} --resume\` without \`--yes\`, or pass those flags again.`,
774
1015
  ].join('\n'));
775
1016
  }
776
1017
  }
@@ -943,27 +1184,33 @@ export default class Install extends Command {
943
1184
  .replace(/^-+|-+$/g, '');
944
1185
  return normalized || 'nocobase';
945
1186
  }
946
- static defaultWorkspaceName() {
947
- return Install.sanitizeDockerResourceName(`nb-${path.basename(process.cwd())}`);
1187
+ static defaultDockerNetworkName() {
1188
+ return Install.sanitizeDockerResourceName(defaultDockerNetworkName());
948
1189
  }
949
- static buildBuiltinDbResourcePrefix(envName, workspaceName) {
950
- void envName;
951
- const storedName = String(workspaceName ?? '').trim();
1190
+ static defaultDockerContainerPrefix() {
1191
+ return Install.sanitizeDockerResourceName(defaultDockerContainerPrefix(resolveEnvRoot(resolveDefaultConfigScope())));
1192
+ }
1193
+ static buildBuiltinDbContainerPrefix(containerPrefix) {
1194
+ const storedName = String(containerPrefix ?? '').trim();
952
1195
  return storedName
953
1196
  ? Install.sanitizeDockerResourceName(storedName)
954
- : Install.defaultWorkspaceName();
1197
+ : Install.defaultDockerContainerPrefix();
955
1198
  }
956
- static async ensureWorkspaceName() {
957
- return await ensureWorkspaceName(Install.defaultWorkspaceName(), { scope: resolveDefaultConfigScope() });
1199
+ static buildManagedDockerNetworkName(networkName) {
1200
+ const storedName = String(networkName ?? '').trim();
1201
+ return storedName
1202
+ ? Install.sanitizeDockerResourceName(storedName)
1203
+ : Install.defaultDockerNetworkName();
958
1204
  }
959
- static buildBuiltinDbNetworkName(envName, workspaceName) {
960
- return Install.buildBuiltinDbResourcePrefix(envName, workspaceName);
1205
+ static buildBuiltinDbNetworkName(envName, networkName) {
1206
+ void envName;
1207
+ return Install.buildManagedDockerNetworkName(networkName);
961
1208
  }
962
- static buildBuiltinDbContainerName(envName, dbDialect, workspaceName) {
963
- return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbResourcePrefix(envName, workspaceName)}-${envName}-${dbDialect}`);
1209
+ static buildBuiltinDbContainerName(envName, dbDialect, containerPrefix) {
1210
+ return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbContainerPrefix(containerPrefix)}-${envName}-${dbDialect}`);
964
1211
  }
965
- static buildDockerAppContainerName(envName, workspaceName) {
966
- return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbResourcePrefix(envName, workspaceName)}-${envName}-app`);
1212
+ static buildDockerAppContainerName(envName, containerPrefix) {
1213
+ return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbContainerPrefix(containerPrefix)}-${envName}-app`);
967
1214
  }
968
1215
  static buildInitAppEnvVars(params) {
969
1216
  const out = {};
@@ -989,8 +1236,8 @@ export default class Install extends Command {
989
1236
  const dbPort = String(params.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
990
1237
  || defaultDbPortForDialect(dbDialect);
991
1238
  const defaultDbDatabase = defaultDbDatabaseForDialect(dbDialect);
992
- const networkName = Install.buildBuiltinDbNetworkName(params.envName, params.workspaceName);
993
- const containerName = Install.buildBuiltinDbContainerName(params.envName, dbDialect, params.workspaceName);
1239
+ const networkName = Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
1240
+ const containerName = Install.buildBuiltinDbContainerName(params.envName, dbDialect, params.dockerContainerPrefix ?? params.workspaceName);
994
1241
  const dbHostInput = String(params.dbHost ?? '').trim();
995
1242
  const dbHost = Install.shouldPublishBuiltinDbPort(params.source)
996
1243
  ? (dbHostInput
@@ -1280,6 +1527,8 @@ export default class Install extends Command {
1280
1527
  const plan = Install.buildBuiltinDbPlan({
1281
1528
  envName: params.envName,
1282
1529
  workspaceName: params.workspaceName,
1530
+ dockerNetworkName: params.dockerNetworkName,
1531
+ dockerContainerPrefix: params.dockerContainerPrefix,
1283
1532
  storagePath,
1284
1533
  source: params.downloadResults.source,
1285
1534
  dbDialect: params.dbResults.dbDialect,
@@ -1327,7 +1576,7 @@ export default class Install extends Command {
1327
1576
  const dbPassword = String(params.dbResults.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
1328
1577
  const appKey = crypto.randomBytes(32).toString('hex');
1329
1578
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
1330
- const containerName = Install.buildDockerAppContainerName(params.envName, params.workspaceName);
1579
+ const containerName = Install.buildDockerAppContainerName(params.envName, params.dockerContainerPrefix ?? params.workspaceName);
1331
1580
  const initEnvVars = Install.buildInitAppEnvVars({
1332
1581
  appResults: params.appResults,
1333
1582
  rootResults: params.rootResults,
@@ -1375,11 +1624,12 @@ export default class Install extends Command {
1375
1624
  }
1376
1625
  async installDockerApp(params) {
1377
1626
  const networkName = params.builtinDbPlan?.networkName
1378
- ?? Install.buildBuiltinDbNetworkName(params.envName, params.workspaceName);
1627
+ ?? Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
1379
1628
  await this.ensureDockerNetwork(networkName);
1380
1629
  const plan = Install.buildDockerAppPlan({
1381
1630
  envName: params.envName,
1382
1631
  workspaceName: params.workspaceName,
1632
+ dockerContainerPrefix: params.dockerContainerPrefix,
1383
1633
  appResults: params.appResults,
1384
1634
  downloadResults: params.downloadResults,
1385
1635
  dbResults: params.dbResults,
@@ -1468,7 +1718,7 @@ export default class Install extends Command {
1468
1718
  p.log.step(source === 'docker'
1469
1719
  ? 'Downloading Docker image'
1470
1720
  : 'Downloading local NocoBase app files');
1471
- return await this.config.runCommand('download', argv);
1721
+ return await this.config.runCommand('source:download', argv);
1472
1722
  }
1473
1723
  async downloadLocalApp(params) {
1474
1724
  const result = await this.downloadManagedSource({
@@ -1657,21 +1907,20 @@ export default class Install extends Command {
1657
1907
  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}`);
1658
1908
  }
1659
1909
  async saveInstalledEnv(params) {
1660
- await this.config.runCommand('env:add', Install.buildEnvAddArgv(params));
1661
- }
1662
- static pushArgIfValue(argv, flag, value) {
1663
- const text = String(value ?? '').trim();
1664
- if (text) {
1665
- argv.push(flag, text);
1666
- }
1910
+ await upsertEnv(params.envName, Install.buildSavedEnvConfig(params), { scope: resolveDefaultConfigScope() });
1667
1911
  }
1668
- static pushBooleanArgIfSet(argv, flag, value) {
1669
- if (value === undefined) {
1912
+ async syncInstalledEnvConnection(params) {
1913
+ if (!params.appReady) {
1670
1914
  return;
1671
1915
  }
1672
- argv.push(Boolean(value) ? flag : `--no-${flag.replace(/^--/, '')}`);
1916
+ const authType = String(params.envAddResults.authType ?? 'oauth').trim()
1917
+ || 'oauth';
1918
+ if (authType === 'oauth') {
1919
+ await this.config.runCommand('env:auth', [params.envName]);
1920
+ }
1921
+ await this.config.runCommand('env:update', [params.envName]);
1673
1922
  }
1674
- static buildEnvAddArgv(params) {
1923
+ static buildSavedEnvConfig(params) {
1675
1924
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim()
1676
1925
  || DEFAULT_INSTALL_APP_PORT;
1677
1926
  const storagePath = String(params.appResults.storagePath ?? '').trim()
@@ -1682,42 +1931,37 @@ export default class Install extends Command {
1682
1931
  });
1683
1932
  const authType = String(params.envAddResults.authType ?? 'oauth').trim()
1684
1933
  || 'oauth';
1685
- const argv = [
1686
- params.envName,
1687
- '--no-intro',
1688
- '--api-base-url',
1934
+ return buildStoredEnvConfig({
1689
1935
  apiBaseUrl,
1690
- '--auth-type',
1691
1936
  authType,
1692
- '--app-port',
1937
+ accessToken: params.envAddResults.accessToken,
1938
+ source: downloadResultsValue(params.downloadResults, 'source'),
1939
+ downloadVersion: downloadResultsValue(params.downloadResults, 'version'),
1940
+ dockerRegistry: downloadResultsValue(params.downloadResults, 'dockerRegistry'),
1941
+ dockerPlatform: downloadResultsValue(params.downloadResults, 'dockerPlatform'),
1942
+ gitUrl: downloadResultsValue(params.downloadResults, 'gitUrl'),
1943
+ npmRegistry: downloadResultsValue(params.downloadResults, 'npmRegistry'),
1944
+ devDependencies: downloadResultsValue(params.downloadResults, 'devDependencies'),
1945
+ build: downloadResultsValue(params.downloadResults, 'build'),
1946
+ buildDts: downloadResultsValue(params.downloadResults, 'buildDts'),
1947
+ appRootPath: params.appResults.appRootPath,
1693
1948
  appPort,
1694
- '--storage-path',
1695
1949
  storagePath,
1696
- ];
1697
- Install.pushArgIfValue(argv, '--source', downloadResultsValue(params.downloadResults, 'source'));
1698
- Install.pushArgIfValue(argv, '--download-version', downloadResultsValue(params.downloadResults, 'version'));
1699
- Install.pushArgIfValue(argv, '--docker-registry', downloadResultsValue(params.downloadResults, 'dockerRegistry'));
1700
- Install.pushArgIfValue(argv, '--docker-platform', downloadResultsValue(params.downloadResults, 'dockerPlatform'));
1701
- Install.pushArgIfValue(argv, '--git-url', downloadResultsValue(params.downloadResults, 'gitUrl'));
1702
- Install.pushArgIfValue(argv, '--npm-registry', downloadResultsValue(params.downloadResults, 'npmRegistry'));
1703
- Install.pushBooleanArgIfSet(argv, '--dev-dependencies', downloadResultsValue(params.downloadResults, 'devDependencies'));
1704
- Install.pushBooleanArgIfSet(argv, '--build', downloadResultsValue(params.downloadResults, 'build'));
1705
- Install.pushBooleanArgIfSet(argv, '--build-dts', downloadResultsValue(params.downloadResults, 'buildDts'));
1706
- Install.pushArgIfValue(argv, '--app-root-path', params.appResults.appRootPath);
1707
- Install.pushArgIfValue(argv, '--app-key', params.appResults.appKey);
1708
- Install.pushArgIfValue(argv, '--timezone', params.appResults.timeZone);
1709
- Install.pushBooleanArgIfSet(argv, '--builtin-db', params.dbResults.builtinDb);
1710
- Install.pushArgIfValue(argv, '--db-dialect', params.dbResults.dbDialect);
1711
- Install.pushArgIfValue(argv, '--builtin-db-image', params.dbResults.builtinDbImage);
1712
- Install.pushArgIfValue(argv, '--db-host', params.dbResults.dbHost);
1713
- Install.pushArgIfValue(argv, '--db-port', params.dbResults.dbPort);
1714
- Install.pushArgIfValue(argv, '--db-database', params.dbResults.dbDatabase);
1715
- Install.pushArgIfValue(argv, '--db-user', params.dbResults.dbUser);
1716
- Install.pushArgIfValue(argv, '--db-password', params.dbResults.dbPassword);
1717
- if (authType === 'token') {
1718
- argv.push('--access-token', String(params.envAddResults.accessToken ?? ''));
1719
- }
1720
- return argv;
1950
+ appKey: params.appResults.appKey,
1951
+ timezone: params.appResults.timeZone,
1952
+ builtinDb: params.dbResults.builtinDb,
1953
+ dbDialect: params.dbResults.dbDialect,
1954
+ builtinDbImage: params.dbResults.builtinDbImage,
1955
+ dbHost: params.dbResults.dbHost,
1956
+ dbPort: params.dbResults.dbPort,
1957
+ dbDatabase: params.dbResults.dbDatabase,
1958
+ dbUser: params.dbResults.dbUser,
1959
+ dbPassword: params.dbResults.dbPassword,
1960
+ rootUsername: params.rootResults.rootUsername,
1961
+ rootEmail: params.rootResults.rootEmail,
1962
+ rootPassword: params.rootResults.rootPassword,
1963
+ rootNickname: params.rootResults.rootNickname,
1964
+ });
1721
1965
  }
1722
1966
  async collectPromptResults(parsed, yes) {
1723
1967
  const resumePreset = await this.resolveResumePresetValues(parsed, yes);
@@ -1737,7 +1981,9 @@ export default class Install extends Command {
1737
1981
  ...(resumePreset?.appPreset ?? {}),
1738
1982
  ...Install.buildAppPresetValuesFromFlags(parsed),
1739
1983
  };
1740
- const appCatalog = Install.buildAppPromptsCatalog(envName);
1984
+ const appCatalog = Install.buildAppPromptsCatalog(envName, {
1985
+ resume: parsed.resume,
1986
+ });
1741
1987
  const appResults = await runPromptCatalog(appCatalog, {
1742
1988
  initialValues: await Install.buildAppPromptInitialValues({
1743
1989
  envName,
@@ -1752,6 +1998,7 @@ export default class Install extends Command {
1752
1998
  },
1753
1999
  }),
1754
2000
  values: appPreset,
2001
+ yesInitialValues: { resume: parsed.resume },
1755
2002
  yes,
1756
2003
  });
1757
2004
  let downloadResults = {};
@@ -1769,7 +2016,9 @@ export default class Install extends Command {
1769
2016
  ...(resumePreset?.dbPreset ?? {}),
1770
2017
  ...Install.buildDbPresetValuesFromFlags(parsed),
1771
2018
  };
1772
- const dbResults = await runPromptCatalog(Install.buildDbPromptsCatalog(downloadResults), {
2019
+ const dbResults = await runPromptCatalog(Install.buildDbPromptsCatalog(envName, downloadResults, {
2020
+ resume: parsed.resume,
2021
+ }), {
1773
2022
  initialValues: {
1774
2023
  ...downloadResults,
1775
2024
  ...await Install.buildDbPromptInitialValues({
@@ -1788,7 +2037,10 @@ export default class Install extends Command {
1788
2037
  const rootPreset = Install.buildRootPresetValuesFromFlags(parsed);
1789
2038
  const rootResults = await runPromptCatalog(Install.rootUserPrompts, {
1790
2039
  initialValues: {},
1791
- values: rootPreset,
2040
+ values: {
2041
+ ...(resumePreset?.rootPreset ?? {}),
2042
+ ...rootPreset,
2043
+ },
1792
2044
  yes,
1793
2045
  });
1794
2046
  const envAddResults = await runPromptCatalog(EnvAdd.prompts, {
@@ -1798,6 +2050,7 @@ export default class Install extends Command {
1798
2050
  values: {
1799
2051
  name: envName,
1800
2052
  ...(resumePreset?.envAddPreset ?? {}),
2053
+ ...Install.buildEnvAddPresetValuesFromFlags(parsed),
1801
2054
  },
1802
2055
  yes,
1803
2056
  });
@@ -1833,14 +2086,30 @@ export default class Install extends Command {
1833
2086
  const source = String(downloadResultsValue(downloadResults, 'source') ?? '').trim();
1834
2087
  const usesDockerResources = Boolean(dbResults.builtinDb)
1835
2088
  || (Boolean(appResults.fetchSource) && source === 'docker');
1836
- const workspaceName = usesDockerResources
1837
- ? await Install.ensureWorkspaceName()
2089
+ const dockerNetworkName = usesDockerResources
2090
+ ? await resolveDockerNetworkName({ scope: resolveDefaultConfigScope() })
1838
2091
  : undefined;
2092
+ const dockerContainerPrefix = usesDockerResources
2093
+ ? await resolveDockerContainerPrefix({ scope: resolveDefaultConfigScope() })
2094
+ : undefined;
2095
+ await Install.ensureExternalDbReadyForInstall(dbResults);
2096
+ if (!parsed.resume) {
2097
+ await this.saveInstalledEnv({
2098
+ envName,
2099
+ appResults,
2100
+ downloadResults,
2101
+ dbResults,
2102
+ rootResults,
2103
+ envAddResults,
2104
+ });
2105
+ p.log.info(`Saved install config for env "${envName}"`);
2106
+ }
1839
2107
  let builtinDbPlan;
1840
2108
  if (Boolean(dbResults.builtinDb)) {
1841
2109
  builtinDbPlan = await this.startBuiltinDb({
1842
2110
  envName,
1843
- workspaceName,
2111
+ dockerNetworkName,
2112
+ dockerContainerPrefix,
1844
2113
  appResults,
1845
2114
  downloadResults,
1846
2115
  dbResults,
@@ -1864,7 +2133,8 @@ export default class Install extends Command {
1864
2133
  });
1865
2134
  dockerAppPlan = await this.installDockerApp({
1866
2135
  envName,
1867
- workspaceName,
2136
+ dockerNetworkName,
2137
+ dockerContainerPrefix,
1868
2138
  appResults,
1869
2139
  downloadResults,
1870
2140
  dbResults,
@@ -1877,6 +2147,7 @@ export default class Install extends Command {
1877
2147
  appResults.timeZone = dockerAppPlan.timeZone;
1878
2148
  }
1879
2149
  else if (source === 'npm' || source === 'git') {
2150
+ const localSource = source === 'npm' ? 'npm' : 'git';
1880
2151
  const projectRoot = await this.downloadLocalApp({
1881
2152
  envName,
1882
2153
  appResults,
@@ -1885,7 +2156,7 @@ export default class Install extends Command {
1885
2156
  });
1886
2157
  localAppPlan = await this.startLocalApp({
1887
2158
  envName,
1888
- source,
2159
+ source: localSource,
1889
2160
  projectRoot,
1890
2161
  appResults,
1891
2162
  dbResults,
@@ -1907,12 +2178,20 @@ export default class Install extends Command {
1907
2178
  containerName: dockerAppPlan?.containerName,
1908
2179
  });
1909
2180
  }
1910
- await this.saveInstalledEnv({
2181
+ if (dockerAppPlan || localAppPlan || builtinDbPlan) {
2182
+ await this.saveInstalledEnv({
2183
+ envName,
2184
+ appResults,
2185
+ downloadResults,
2186
+ dbResults,
2187
+ rootResults,
2188
+ envAddResults,
2189
+ });
2190
+ }
2191
+ await this.syncInstalledEnvConnection({
1911
2192
  envName,
1912
- appResults,
1913
- downloadResults,
1914
- dbResults,
1915
2193
  envAddResults,
2194
+ appReady: Boolean(dockerAppPlan || localAppPlan),
1916
2195
  });
1917
2196
  p.outro(dockerAppPlan || localAppPlan
1918
2197
  ? `NocoBase is ready at http://127.0.0.1:${dockerAppPlan?.appPort ?? localAppPlan?.appPort}`