@nocobase/cli 2.2.0-beta.8 → 2.2.0-test.15

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 (56) hide show
  1. package/assets/env-proxy/nginx/app.conf.tpl +23 -0
  2. package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
  3. package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
  4. package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
  5. package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
  6. package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
  7. package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
  8. package/assets/env-proxy/nginx/snippets/proxy-location.conf +18 -0
  9. package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
  10. package/assets/env-proxy/nginx/snippets/uploads-location.conf +24 -0
  11. package/dist/commands/app/start.js +25 -2
  12. package/dist/commands/config/set.js +1 -0
  13. package/dist/commands/env/info.js +11 -1
  14. package/dist/commands/init.js +131 -4
  15. package/dist/commands/install.js +179 -131
  16. package/dist/commands/portal/create.js +105 -0
  17. package/dist/commands/portal/deploy.js +81 -0
  18. package/dist/commands/portal/destroy.js +104 -0
  19. package/dist/commands/portal/dev.js +71 -0
  20. package/dist/commands/portal/index.js +20 -0
  21. package/dist/commands/portal/info.js +82 -0
  22. package/dist/commands/portal/list.js +98 -0
  23. package/dist/commands/portal/pull.js +77 -0
  24. package/dist/commands/portal/push.js +79 -0
  25. package/dist/commands/proxy/caddy/generate.js +93 -7
  26. package/dist/commands/proxy/nginx/generate.js +98 -7
  27. package/dist/commands/revision/create.js +1 -1
  28. package/dist/commands/source/dev.js +1 -1
  29. package/dist/commands/source/download.js +18 -14
  30. package/dist/lib/app-managed-resources.js +3 -2
  31. package/dist/lib/auth-store.js +71 -1
  32. package/dist/lib/cli-config.js +74 -2
  33. package/dist/lib/docker-image.js +94 -6
  34. package/dist/lib/env-config.js +8 -0
  35. package/dist/lib/env-proxy-config.js +48 -0
  36. package/dist/lib/env-proxy.js +266 -61
  37. package/dist/lib/managed-init-env.js +6 -1
  38. package/dist/lib/portal-command-env.js +31 -0
  39. package/dist/lib/portal-create.js +488 -0
  40. package/dist/lib/portal-deploy.js +275 -0
  41. package/dist/lib/portal-destroy.js +100 -0
  42. package/dist/lib/portal-dev.js +79 -0
  43. package/dist/lib/portal-env-files.js +53 -0
  44. package/dist/lib/portal-info.js +31 -0
  45. package/dist/lib/portal-list.js +197 -0
  46. package/dist/lib/portal-source.js +416 -0
  47. package/dist/lib/portal-template.js +190 -0
  48. package/dist/lib/prompt-catalog-terminal.js +32 -19
  49. package/dist/lib/prompt-web-ui.js +13 -2
  50. package/dist/lib/proxy-caddy.js +77 -9
  51. package/dist/lib/proxy-nginx.js +71 -11
  52. package/dist/lib/run-npm.js +17 -16
  53. package/dist/lib/ui.js +28 -1
  54. package/dist/locale/en-US.json +192 -38
  55. package/dist/locale/zh-CN.json +192 -38
  56. package/package.json +6 -3
@@ -13,11 +13,11 @@ import path from 'node:path';
13
13
  import { exit } from 'node:process';
14
14
  import { appendAppPublicPath } from '../lib/app-public-path.js';
15
15
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
16
- import { applyCliLocale, localeText, resolveCliLocale, translateCli } from "../lib/cli-locale.js";
16
+ import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
17
17
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
18
18
  import { defaultDockerContainerPrefix, defaultDockerNetworkName, managedAppLifecycleEnvVars, } from '../lib/app-runtime.js';
19
- import { resolveDefaultApiHost, resolveDockerContainerPrefix, resolveDockerNetworkName } from '../lib/cli-config.js';
20
- import { DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../lib/docker-image.js";
19
+ import { getCliConfigValue, resolveDefaultApiHost, resolveDockerContainerPrefix, resolveDockerNetworkName, } from '../lib/cli-config.js';
20
+ import { DEFAULT_DOCKER_VERSION, DEFAULT_NB_IMAGE_VARIANT, inferNbImageRegistryFromRepository, normalizeNbImageVariant, resolveBuiltinDbImage, resolveDockerImageContainerPort, resolveDockerImageRef, resolveOfficialDockerRegistry, } from "../lib/docker-image.js";
21
21
  import { findAvailableTcpPort, validateAppPublicPath, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
22
22
  import { validateExternalDbConfig, validateMysqlLowerCaseTableNamesCompatibility } from "../lib/db-connection-check.js";
23
23
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
@@ -29,9 +29,10 @@ import { buildStoredEnvConfig } from '../lib/env-config.js';
29
29
  import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
30
30
  import { startDockerLogFollower } from '../lib/docker-log-stream.js';
31
31
  import { buildInitAppEnvVarsFromConfig } from '../lib/managed-init-env.js';
32
+ import { prepareInitialPortalTemplate } from '../lib/portal-template.js';
32
33
  import { buildHookContext, persistHookScript, resolveHookScriptPath, runHookScriptHook, } from '../lib/hook-script.js';
33
34
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
34
- import Download, { defaultDockerRegistryForLang } from './download.js';
35
+ import Download from './download.js';
35
36
  import EnvAdd from "./env/add.js";
36
37
  import { resolveAppUrlFromApiBaseUrl } from "./env/shared.js";
37
38
  const DEFAULT_INSTALL_ENV_NAME = 'local';
@@ -45,18 +46,6 @@ const DEFAULT_INSTALL_DB_PORTS = {
45
46
  mariadb: '3306',
46
47
  kingbase: '54321',
47
48
  };
48
- const DEFAULT_INSTALL_BUILTIN_DB_IMAGES = {
49
- postgres: 'postgres:16',
50
- mysql: 'mysql:8',
51
- mariadb: 'mariadb:11',
52
- kingbase: 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86',
53
- };
54
- const DEFAULT_INSTALL_BUILTIN_DB_IMAGES_ZH_CN = {
55
- postgres: 'registry.cn-shanghai.aliyuncs.com/nocobase/postgres:16',
56
- mysql: 'registry.cn-shanghai.aliyuncs.com/nocobase/mysql:8',
57
- mariadb: 'registry.cn-shanghai.aliyuncs.com/nocobase/mariadb:11',
58
- kingbase: 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86',
59
- };
60
49
  const DEFAULT_INSTALL_DB_DATABASE = 'nocobase';
61
50
  const DEFAULT_INSTALL_DB_USER = 'nocobase';
62
51
  const DEFAULT_INSTALL_DB_PASSWORD = 'nocobase';
@@ -65,6 +54,10 @@ const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
65
54
  const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
66
55
  const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
67
56
  const DEFAULT_INSTALL_API_HOST = '127.0.0.1';
57
+ const DEFAULT_INSTALL_DEVELOPMENT_MODE = 'no-code';
58
+ const DEFAULT_INSTALL_PORTAL_NAME = 'admin';
59
+ const DEFAULT_INSTALL_PORTAL_TEMPLATE = 'git@github.com:nocobase/admin-starter.git';
60
+ const INSTALL_DEVELOPMENT_MODES = ['no-code', 'vibe-coding'];
68
61
  function toOptionalPromptString(value) {
69
62
  const text = String(value ?? '').trim();
70
63
  return text || undefined;
@@ -179,22 +172,26 @@ function downloadVersionPromptValue(version) {
179
172
  }
180
173
  function supportsBuiltinDbDialect(value) {
181
174
  const dialect = String(value ?? '').trim();
182
- return Object.prototype.hasOwnProperty.call(DEFAULT_INSTALL_BUILTIN_DB_IMAGES, dialect);
175
+ return INSTALL_DB_DIALECTS.includes(dialect);
183
176
  }
184
177
  export function defaultDbPortForDialect(value) {
185
178
  const dialect = String(value ?? 'postgres').trim();
186
179
  return DEFAULT_INSTALL_DB_PORTS[isInstallDbDialect(dialect) ? dialect : 'postgres'];
187
180
  }
188
- function defaultBuiltinDbImageForDialect(value) {
181
+ function defaultBuiltinDbImageForDialect(value, options) {
189
182
  const dialect = String(value ?? 'postgres').trim();
190
- const defaults = resolveCliLocale(process.env.NB_LOCALE) === 'zh-CN'
191
- ? DEFAULT_INSTALL_BUILTIN_DB_IMAGES_ZH_CN
192
- : DEFAULT_INSTALL_BUILTIN_DB_IMAGES;
193
- return supportsBuiltinDbDialect(dialect) ? defaults[dialect] : defaults.postgres;
183
+ return resolveBuiltinDbImage(dialect, { registry: options?.registry });
194
184
  }
195
185
  function defaultDbDatabaseForDialect(value) {
196
186
  return String(value ?? '').trim() === 'kingbase' ? 'kingbase' : DEFAULT_INSTALL_DB_DATABASE;
197
187
  }
188
+ function isVibeCodingMode(values) {
189
+ return String(values.developmentMode ?? DEFAULT_INSTALL_DEVELOPMENT_MODE).trim() === 'vibe-coding';
190
+ }
191
+ function supportsDbSchemaPrompt(value) {
192
+ const dialect = String(value ?? '').trim();
193
+ return dialect === 'postgres' || dialect === 'kingbase';
194
+ }
198
195
  function defaultDbHostForBuiltinDb(values) {
199
196
  return values.builtinDb ? DEFAULT_INSTALL_BUILTIN_DB_HOST : DEFAULT_INSTALL_DB_HOST;
200
197
  }
@@ -415,6 +412,16 @@ export default class Install extends Command {
415
412
  'app-public-path': Flags.string({
416
413
  description: 'Public path for the local app, for example / or /console/',
417
414
  }),
415
+ 'development-mode': Flags.string({
416
+ description: 'Initial development mode for the installed app',
417
+ options: [...INSTALL_DEVELOPMENT_MODES],
418
+ }),
419
+ 'portal-name': Flags.string({
420
+ description: 'Initial portal name when --development-mode vibe-coding is used',
421
+ }),
422
+ 'portal-template': Flags.string({
423
+ description: 'Initial portal template Git URL or local path when --development-mode vibe-coding is used',
424
+ }),
418
425
  'root-username': Flags.string({
419
426
  description: 'Initial admin username for the installed app',
420
427
  required: false,
@@ -524,6 +531,42 @@ export default class Install extends Command {
524
531
  yesInitialValue: '/',
525
532
  validate: validateAppPublicPath,
526
533
  },
534
+ developmentMode: {
535
+ type: 'select',
536
+ message: installText('prompts.developmentMode.message'),
537
+ options: [
538
+ {
539
+ value: 'no-code',
540
+ label: installText('prompts.developmentMode.noCodeLabel'),
541
+ hint: installText('prompts.developmentMode.noCodeHint'),
542
+ },
543
+ {
544
+ value: 'vibe-coding',
545
+ label: installText('prompts.developmentMode.vibeCodingLabel'),
546
+ hint: installText('prompts.developmentMode.vibeCodingHint'),
547
+ },
548
+ ],
549
+ initialValue: DEFAULT_INSTALL_DEVELOPMENT_MODE,
550
+ yesInitialValue: DEFAULT_INSTALL_DEVELOPMENT_MODE,
551
+ required: true,
552
+ },
553
+ portalName: {
554
+ type: 'text',
555
+ message: installText('prompts.portalName.message'),
556
+ placeholder: DEFAULT_INSTALL_PORTAL_NAME,
557
+ initialValue: DEFAULT_INSTALL_PORTAL_NAME,
558
+ yesInitialValue: DEFAULT_INSTALL_PORTAL_NAME,
559
+ hidden: (values) => !isVibeCodingMode(values),
560
+ required: true,
561
+ },
562
+ portalTemplate: {
563
+ type: 'text',
564
+ message: installText('prompts.portalTemplate.message'),
565
+ placeholder: DEFAULT_INSTALL_PORTAL_TEMPLATE,
566
+ yesInitialValue: DEFAULT_INSTALL_PORTAL_TEMPLATE,
567
+ hidden: (values) => !isVibeCodingMode(values),
568
+ required: true,
569
+ },
527
570
  };
528
571
  }
529
572
  static dbPrompts = {
@@ -551,7 +594,9 @@ export default class Install extends Command {
551
594
  type: 'text',
552
595
  message: installText('prompts.builtinDbImage.message'),
553
596
  placeholder: installText('prompts.builtinDbImage.placeholder'),
554
- initialValue: (values) => defaultBuiltinDbImageForDialect(values.dbDialect),
597
+ initialValue: (values) => defaultBuiltinDbImageForDialect(values.dbDialect, {
598
+ registry: String(values.builtinDbImageRegistry ?? '').trim() || undefined,
599
+ }),
555
600
  hidden: (values) => !values.builtinDb || !supportsBuiltinDbDialect(values.dbDialect),
556
601
  required: true,
557
602
  },
@@ -601,7 +646,7 @@ export default class Install extends Command {
601
646
  type: 'text',
602
647
  message: installText('prompts.dbSchema.message'),
603
648
  placeholder: installText('prompts.dbSchema.placeholder'),
604
- hidden: (values) => String(values.dbDialect ?? '').trim() !== 'postgres',
649
+ hidden: (values) => !supportsDbSchemaPrompt(values.dbDialect),
605
650
  },
606
651
  dbTablePrefix: {
607
652
  type: 'text',
@@ -781,6 +826,24 @@ export default class Install extends Command {
781
826
  preset.appPublicPath = v;
782
827
  }
783
828
  }
829
+ if (flags['development-mode'] !== undefined) {
830
+ const v = String(flags['development-mode'] ?? '').trim();
831
+ if (v) {
832
+ preset.developmentMode = v;
833
+ }
834
+ }
835
+ if (flags['portal-name'] !== undefined) {
836
+ const v = String(flags['portal-name'] ?? '').trim();
837
+ if (v) {
838
+ preset.portalName = v;
839
+ }
840
+ }
841
+ if (flags['portal-template'] !== undefined) {
842
+ const v = String(flags['portal-template'] ?? '').trim();
843
+ if (v) {
844
+ preset.portalTemplate = v;
845
+ }
846
+ }
784
847
  if (flags['root-username'] !== undefined) {
785
848
  preset.rootUsername = String(flags['root-username'] ?? '').trim();
786
849
  }
@@ -872,6 +935,9 @@ export default class Install extends Command {
872
935
  'appPort',
873
936
  'storagePath',
874
937
  'appPublicPath',
938
+ 'developmentMode',
939
+ 'portalName',
940
+ 'portalTemplate',
875
941
  ]);
876
942
  }
877
943
  static buildDbPresetValuesFromFlags(flags, argv = process.argv.slice(2)) {
@@ -1121,6 +1187,9 @@ export default class Install extends Command {
1121
1187
  const rootPassword = Install.toOptionalPromptString(config.rootPassword);
1122
1188
  const rootNickname = Install.toOptionalPromptString(config.rootNickname);
1123
1189
  const lang = Install.toOptionalPromptString(config.lang);
1190
+ const developmentMode = Install.toOptionalPromptString(config.developmentMode);
1191
+ const portalName = Install.toOptionalPromptString(config.portalName);
1192
+ const portalTemplate = Install.toOptionalPromptString(config.portalTemplate);
1124
1193
  const auth = config.auth;
1125
1194
  const savedAuthType = Install.toOptionalPromptString(config.authType) ?? Install.toOptionalPromptString(auth?.type);
1126
1195
  const appPreset = {
@@ -1130,6 +1199,9 @@ export default class Install extends Command {
1130
1199
  ...(appPort ? { appPort } : {}),
1131
1200
  ...(storagePath ? { storagePath } : {}),
1132
1201
  ...(appPublicPath ? { appPublicPath } : {}),
1202
+ ...(developmentMode ? { developmentMode } : {}),
1203
+ ...(portalName ? { portalName } : {}),
1204
+ ...(portalTemplate ? { portalTemplate } : {}),
1133
1205
  ...(hookScript ? { hookScript } : {}),
1134
1206
  };
1135
1207
  const downloadPreset = {
@@ -1272,6 +1344,12 @@ export default class Install extends Command {
1272
1344
  warn: params.warnOnPortFallback ?? true,
1273
1345
  });
1274
1346
  }
1347
+ if (params.flags['portal-template'] === undefined) {
1348
+ const defaultPortalTemplate = Install.toOptionalPromptString(await getCliConfigValue('default-portal-template'));
1349
+ if (defaultPortalTemplate) {
1350
+ initialValues.portalTemplate = defaultPortalTemplate;
1351
+ }
1352
+ }
1275
1353
  return initialValues;
1276
1354
  }
1277
1355
  static shouldPublishBuiltinDbPortForValues(values) {
@@ -1279,19 +1357,25 @@ export default class Install extends Command {
1279
1357
  return builtinDb && Install.shouldPublishBuiltinDbPort(values.source);
1280
1358
  }
1281
1359
  static async buildDbPromptInitialValues(params) {
1282
- if (params.flags['db-port'] !== undefined) {
1283
- return {};
1284
- }
1360
+ const configuredRegistry = await getCliConfigValue('nb-image-registry');
1285
1361
  const values = {
1286
1362
  ...params.downloadResults,
1287
1363
  ...params.dbPreset,
1288
1364
  };
1365
+ const dockerRegistry = String(values.dockerRegistry ?? '').trim() || resolveOfficialDockerRegistry(configuredRegistry);
1366
+ const dialect = String(values.dbDialect ?? 'postgres').trim() || 'postgres';
1367
+ const initialValues = values.builtinDb !== false && params.dbPreset.builtinDbImage === undefined
1368
+ ? { builtinDbImage: defaultBuiltinDbImageForDialect(dialect, { registry: dockerRegistry }) }
1369
+ : {};
1370
+ if (params.flags['db-port'] !== undefined) {
1371
+ return initialValues;
1372
+ }
1289
1373
  if (!Install.shouldPublishBuiltinDbPortForValues(values)) {
1290
- return {};
1374
+ return initialValues;
1291
1375
  }
1292
- const dialect = String(values.dbDialect ?? 'postgres').trim() || 'postgres';
1293
1376
  const defaultPort = defaultDbPortForDialect(dialect);
1294
1377
  return {
1378
+ ...initialValues,
1295
1379
  dbPort: await Install.resolveAvailableDefaultPort(defaultPort, {
1296
1380
  label: `Default ${dialect} port`,
1297
1381
  warn: params.warnOnPortFallback ?? true,
@@ -1302,12 +1386,13 @@ export default class Install extends Command {
1302
1386
  * When install runs {@link Download.prompts} after app prompts, align the download
1303
1387
  * output directory with app settings, while Docker registry defaults follow the CLI locale.
1304
1388
  */
1305
- static buildDownloadPromptOptionsForInstall(appResults, envName) {
1389
+ static async buildDownloadPromptOptionsForInstall(appResults, envName) {
1306
1390
  const appRoot = resolveConfiguredSourcePathValue(appResults, envName);
1307
1391
  const lang = String(appResults.lang ?? DEFAULT_INSTALL_LANG).trim() || DEFAULT_INSTALL_LANG;
1392
+ const dockerRegistry = resolveOfficialDockerRegistry(await getCliConfigValue('nb-image-registry'));
1308
1393
  const initialValues = {
1309
1394
  lang,
1310
- dockerRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
1395
+ dockerRegistry,
1311
1396
  outputDir: appRoot,
1312
1397
  };
1313
1398
  const values = {
@@ -1432,14 +1517,17 @@ export default class Install extends Command {
1432
1517
  static buildDockerAppContainerName(envName, containerPrefix) {
1433
1518
  return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbContainerPrefix(containerPrefix)}-${envName}-app`);
1434
1519
  }
1435
- static buildInitAppEnvVars(params) {
1520
+ static buildInitAppEnvVars(params, options = {}) {
1436
1521
  return buildInitAppEnvVarsFromConfig({
1437
1522
  lang: String(params.appResults.lang ?? ''),
1438
1523
  rootUsername: String(params.rootResults.rootUsername ?? ''),
1439
1524
  rootEmail: String(params.rootResults.rootEmail ?? ''),
1440
1525
  rootPassword: String(params.rootResults.rootPassword ?? ''),
1441
1526
  rootNickname: String(params.rootResults.rootNickname ?? ''),
1442
- });
1527
+ developmentMode: String(params.appResults.developmentMode ?? ''),
1528
+ portalName: String(params.appResults.portalName ?? ''),
1529
+ portalTemplate: String(params.appResults.portalTemplate ?? ''),
1530
+ }, options);
1443
1531
  }
1444
1532
  static shouldPublishBuiltinDbPort(source) {
1445
1533
  return String(source ?? '').trim() !== 'docker';
@@ -1760,12 +1848,15 @@ export default class Install extends Command {
1760
1848
  return plan;
1761
1849
  }
1762
1850
  static async buildDockerAppPlan(params) {
1851
+ const configuredRegistry = await getCliConfigValue('nb-image-registry');
1852
+ const configuredVariant = normalizeNbImageVariant(await getCliConfigValue('nb-image-variant')) ?? DEFAULT_NB_IMAGE_VARIANT;
1763
1853
  const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim() ||
1764
- defaultDockerRegistryForLang(process.env.NB_LOCALE);
1854
+ resolveOfficialDockerRegistry(configuredRegistry);
1765
1855
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
1766
1856
  const imageRef = resolveDockerImageRef(dockerRegistry, version, {
1767
- defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
1857
+ defaultRegistry: resolveOfficialDockerRegistry(configuredRegistry),
1768
1858
  defaultVersion: DEFAULT_DOCKER_VERSION,
1859
+ variant: inferNbImageRegistryFromRepository(dockerRegistry) ? configuredVariant : undefined,
1769
1860
  });
1770
1861
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1771
1862
  const configuredStoragePath = resolveConfiguredStoragePathValue(params.appResults, params.envName);
@@ -1791,7 +1882,8 @@ export default class Install extends Command {
1791
1882
  const initEnvVars = Install.buildInitAppEnvVars({
1792
1883
  appResults: params.appResults,
1793
1884
  rootResults: params.rootResults,
1794
- });
1885
+ }, { includePortal: false });
1886
+ const containerPort = resolveDockerImageContainerPort(imageRef);
1795
1887
  const args = [
1796
1888
  'run',
1797
1889
  '-d',
@@ -1800,7 +1892,7 @@ export default class Install extends Command {
1800
1892
  '--network',
1801
1893
  params.networkName,
1802
1894
  '-p',
1803
- `${appPort}:80`,
1895
+ `${appPort}:${containerPort}`,
1804
1896
  ];
1805
1897
  if (envFile) {
1806
1898
  args.push('--env-file', envFile);
@@ -1990,12 +2082,15 @@ export default class Install extends Command {
1990
2082
  }
1991
2083
  async ensureSkippedDownloadInputsReady(params) {
1992
2084
  if (params.source === 'docker') {
2085
+ const configuredRegistry = await getCliConfigValue('nb-image-registry');
2086
+ const configuredVariant = normalizeNbImageVariant(await getCliConfigValue('nb-image-variant')) ?? DEFAULT_NB_IMAGE_VARIANT;
1993
2087
  const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim() ||
1994
- defaultDockerRegistryForLang(process.env.NB_LOCALE);
2088
+ resolveOfficialDockerRegistry(configuredRegistry);
1995
2089
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
1996
2090
  const imageRef = resolveDockerImageRef(dockerRegistry, version, {
1997
- defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
2091
+ defaultRegistry: resolveOfficialDockerRegistry(configuredRegistry),
1998
2092
  defaultVersion: DEFAULT_DOCKER_VERSION,
2093
+ variant: inferNbImageRegistryFromRepository(dockerRegistry) ? configuredVariant : undefined,
1999
2094
  });
2000
2095
  const imageExists = await commandSucceeds('docker', ['image', 'inspect', imageRef]);
2001
2096
  if (!imageExists) {
@@ -2064,7 +2159,7 @@ export default class Install extends Command {
2064
2159
  ...Install.buildInitAppEnvVars({
2065
2160
  appResults: params.appResults,
2066
2161
  rootResults: params.rootResults,
2067
- }),
2162
+ }, { includePortal: false }),
2068
2163
  };
2069
2164
  setOptionalEnvVar(env, 'APP_PUBLIC_PATH', Install.toOptionalPromptString(params.appResults.appPublicPath));
2070
2165
  setOptionalEnvVar(env, 'DB_SCHEMA', optionalEnvString(params.dbResults.dbSchema));
@@ -2254,6 +2349,26 @@ export default class Install extends Command {
2254
2349
  }
2255
2350
  await this.config.runCommand('env:update', [params.envName]);
2256
2351
  }
2352
+ buildAppStartArgv(params) {
2353
+ const argv = ['--env', params.envName, '--yes', '--no-sync-licensed-plugins', '--hook-command', 'init'];
2354
+ if (params.verbose) {
2355
+ argv.push('--verbose');
2356
+ }
2357
+ return argv;
2358
+ }
2359
+ async prepareInitialPortalIfNeeded(params) {
2360
+ await prepareInitialPortalTemplate({
2361
+ developmentMode: Install.toOptionalPromptString(params.appResults.developmentMode),
2362
+ portalName: Install.toOptionalPromptString(params.appResults.portalName),
2363
+ portalTemplate: Install.toOptionalPromptString(params.appResults.portalTemplate),
2364
+ npmRegistry: Install.toOptionalPromptString(params.appResults.npmRegistry),
2365
+ storagePath: Install.resolveAbsoluteStoragePath(params.envName, params.appResults),
2366
+ verbose: params.verbose,
2367
+ onStartTask: (message) => this.logStage(message),
2368
+ onSucceedTask: (message) => printInfo(message),
2369
+ onFailTask: (message) => printWarning(message),
2370
+ });
2371
+ }
2257
2372
  async runInstallHookIfNeeded(params) {
2258
2373
  const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
2259
2374
  const savedHookScript = Install.toOptionalPromptString(params.appResults.hookScript);
@@ -2297,6 +2412,9 @@ export default class Install extends Command {
2297
2412
  const appRootPath = Install.toOptionalPromptString(params.appResults.appRootPath);
2298
2413
  const storagePath = Install.toOptionalPromptString(params.appResults.storagePath);
2299
2414
  const appPublicPath = Install.toOptionalPromptString(params.appResults.appPublicPath);
2415
+ const developmentMode = Install.toOptionalPromptString(params.appResults.developmentMode);
2416
+ const portalName = Install.toOptionalPromptString(params.appResults.portalName);
2417
+ const portalTemplate = Install.toOptionalPromptString(params.appResults.portalTemplate);
2300
2418
  const derivedAppRootPath = appPath ? deriveConfiguredSourcePath(appPath) : undefined;
2301
2419
  const derivedStoragePath = appPath ? deriveConfiguredStoragePath(appPath) : undefined;
2302
2420
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
@@ -2331,6 +2449,9 @@ export default class Install extends Command {
2331
2449
  ...(appPublicPath ? { appPublicPath } : {}),
2332
2450
  ...(envFile ? { envFile } : {}),
2333
2451
  lang: params.appResults.lang,
2452
+ developmentMode,
2453
+ portalName,
2454
+ portalTemplate,
2334
2455
  appKey: params.appResults.appKey,
2335
2456
  timezone: params.appResults.timeZone,
2336
2457
  builtinDb: params.dbResults.builtinDb,
@@ -2382,6 +2503,7 @@ export default class Install extends Command {
2382
2503
  'app-root-path': parsed['app-root-path'] ?? Install.toOptionalPromptString(appPreset.appRootPath),
2383
2504
  'app-port': parsed['app-port'] ?? Install.toOptionalPromptString(appPreset.appPort),
2384
2505
  'storage-path': parsed['storage-path'] ?? Install.toOptionalPromptString(appPreset.storagePath),
2506
+ 'portal-template': parsed['portal-template'] ?? Install.toOptionalPromptString(appPreset.portalTemplate),
2385
2507
  },
2386
2508
  }),
2387
2509
  values: appPreset,
@@ -2391,7 +2513,7 @@ export default class Install extends Command {
2391
2513
  if (resumePreset?.appPreset?.hookScript !== undefined && appResults.hookScript === undefined) {
2392
2514
  appResults.hookScript = resumePreset.appPreset.hookScript;
2393
2515
  }
2394
- const downloadOpts = Install.buildDownloadPromptOptionsForInstall(appResults, envName);
2516
+ const downloadOpts = await Install.buildDownloadPromptOptionsForInstall(appResults, envName);
2395
2517
  downloadOpts.values = {
2396
2518
  ...(resumePreset?.downloadPreset ?? {}),
2397
2519
  ...downloadOpts.values,
@@ -2498,7 +2620,6 @@ export default class Install extends Command {
2498
2620
  const parsed = {
2499
2621
  ...flags,
2500
2622
  };
2501
- const defaultApiHost = await resolveDefaultApiHost();
2502
2623
  if (parsed['skip-auth'] && (parsed['access-token'] !== undefined || parsed.token !== undefined)) {
2503
2624
  this.error('--skip-auth cannot be used with --access-token or --token.');
2504
2625
  }
@@ -2578,8 +2699,7 @@ export default class Install extends Command {
2578
2699
  dbResults.dbUser = builtinDbPlan.dbUser;
2579
2700
  dbResults.dbPassword = builtinDbPlan.dbPassword;
2580
2701
  }
2581
- let dockerAppPlan;
2582
- let localAppPlan;
2702
+ let shouldStartApp = false;
2583
2703
  if (source === 'docker' || source === 'npm' || source === 'git') {
2584
2704
  this.logStage('Preparing application');
2585
2705
  if (source === 'docker') {
@@ -2591,124 +2711,52 @@ export default class Install extends Command {
2591
2711
  printInfo('Application image ready.');
2592
2712
  }
2593
2713
  if (!parsed['prepare-only']) {
2594
- await this.runInstallHookIfNeeded({
2595
- hookName: 'beforeAppInstall',
2596
- envName,
2597
- source,
2598
- appResults,
2599
- downloadResults,
2600
- dbResults,
2601
- rootResults,
2602
- envAddResults,
2603
- defaultApiHost,
2604
- });
2605
- dockerAppPlan = await this.installDockerApp({
2606
- envName,
2607
- dockerNetworkName,
2608
- dockerContainerPrefix,
2609
- appResults,
2610
- downloadResults,
2611
- dbResults,
2612
- rootResults,
2613
- builtinDbPlan,
2614
- force: parsed.force,
2615
- commandStdio,
2616
- });
2617
- appResults.appKey = dockerAppPlan.appKey;
2618
- appResults.timeZone = dockerAppPlan.timeZone;
2714
+ shouldStartApp = true;
2619
2715
  }
2620
2716
  }
2621
2717
  else if (source === 'npm' || source === 'git') {
2622
- const localSource = source === 'npm' ? 'npm' : 'git';
2623
- const projectRoot = parsed['skip-download'] || parsed['prepare-only']
2624
- ? Install.resolveLocalProjectRoot({
2625
- envName,
2626
- appResults,
2627
- downloadResults,
2628
- })
2629
- : await this.downloadLocalApp({
2718
+ if (!parsed['skip-download'] && !parsed['prepare-only']) {
2719
+ await this.downloadLocalApp({
2630
2720
  envName,
2631
2721
  appResults,
2632
2722
  downloadResults,
2633
2723
  verbose: parsed.verbose,
2634
2724
  });
2635
- if (!parsed['skip-download'] && !parsed['prepare-only']) {
2636
2725
  printInfo('Application files ready.');
2637
2726
  }
2638
2727
  if (!parsed['prepare-only']) {
2639
- await this.runInstallHookIfNeeded({
2640
- hookName: 'beforeAppInstall',
2641
- envName,
2642
- source,
2643
- appResults,
2644
- downloadResults,
2645
- dbResults,
2646
- rootResults,
2647
- envAddResults,
2648
- projectRoot,
2649
- defaultApiHost,
2650
- });
2651
- localAppPlan = await this.startLocalApp({
2652
- envName,
2653
- source: localSource,
2654
- projectRoot,
2655
- appResults,
2656
- dbResults,
2657
- rootResults,
2658
- commandStdio,
2659
- });
2660
- appResults.appKey = localAppPlan.appKey;
2661
- appResults.timeZone = localAppPlan.timeZone;
2728
+ shouldStartApp = true;
2662
2729
  }
2663
2730
  }
2664
2731
  }
2665
2732
  else {
2666
2733
  this.logDetail('Skipped app download and install.');
2667
2734
  }
2668
- if (dockerAppPlan || localAppPlan) {
2669
- this.logStage('Starting NocoBase');
2670
- await this.waitForAppHealthCheck(Install.resolveApiBaseUrl({
2671
- appResults,
2672
- envAddResults,
2673
- defaultApiHost,
2674
- }), {
2675
- containerName: dockerAppPlan?.containerName,
2676
- verbose: parsed.verbose,
2677
- });
2678
- const displayApiBaseUrl = Install.resolveApiBaseUrl({
2679
- appResults,
2680
- envAddResults,
2681
- defaultApiHost,
2682
- });
2683
- printInfo(`NocoBase is ready at ${formatInstallDisplayUrl(displayApiBaseUrl)}`);
2684
- appResults.setupState = 'installed';
2685
- await this.runInstallHookIfNeeded({
2686
- hookName: 'afterAppStart',
2735
+ if (shouldStartApp || builtinDbPlan) {
2736
+ await this.saveInstalledEnv({
2687
2737
  envName,
2688
- source,
2689
2738
  appResults,
2690
2739
  downloadResults,
2691
2740
  dbResults,
2692
2741
  rootResults,
2693
2742
  envAddResults,
2694
- projectRoot: localAppPlan?.projectRoot,
2695
- defaultApiHost,
2696
2743
  });
2697
2744
  }
2698
- if (dockerAppPlan || localAppPlan || builtinDbPlan) {
2699
- await this.saveInstalledEnv({
2745
+ if (shouldStartApp) {
2746
+ await this.prepareInitialPortalIfNeeded({
2700
2747
  envName,
2701
2748
  appResults,
2702
- downloadResults,
2703
- dbResults,
2704
- rootResults,
2705
- envAddResults,
2749
+ verbose: parsed.verbose,
2706
2750
  });
2707
2751
  }
2752
+ if (shouldStartApp) {
2753
+ this.logStage('Starting NocoBase');
2754
+ await this.config.runCommand('app:start', this.buildAppStartArgv({ envName, verbose: parsed.verbose }));
2755
+ }
2708
2756
  await this.syncInstalledEnvConnection({
2709
2757
  envName,
2710
2758
  envAddResults,
2711
- appReady: Boolean(dockerAppPlan || localAppPlan),
2759
+ appReady: shouldStartApp,
2712
2760
  skipAuth: Boolean(parsed['skip-auth']),
2713
2761
  });
2714
2762
  if (!parsed['prepare-only']) {
@@ -2717,7 +2765,7 @@ export default class Install extends Command {
2717
2765
  if (parsed['prepare-only']) {
2718
2766
  printInfo(`Preparation complete for "${envName}". Activate the license, then run \`nb app start --env ${envName}\`.`);
2719
2767
  }
2720
- else if (!dockerAppPlan && !localAppPlan) {
2768
+ else if (!shouldStartApp) {
2721
2769
  printInfo(`Install config for "${envName}" has been saved.`);
2722
2770
  }
2723
2771
  }