@nocobase/cli 2.1.0-beta.36 → 2.1.0-beta.37

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.
@@ -7,27 +7,26 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
- import { spawn } from 'node:child_process';
11
10
  import crypto from 'node:crypto';
12
- import { mkdir } from 'node:fs/promises';
11
+ import { access, mkdir } from 'node:fs/promises';
13
12
  import path from 'node:path';
14
13
  import { exit } from 'node:process';
15
14
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
16
- import { applyCliLocale, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
15
+ import { applyCliLocale, localeText, resolveCliLocale, translateCli } from "../lib/cli-locale.js";
17
16
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
18
- import { defaultDockerContainerPrefix, defaultDockerNetworkName, } from '../lib/app-runtime.js';
19
- import { resolveDockerContainerPrefix, resolveDockerNetworkName, } from '../lib/cli-config.js';
20
- import { DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../lib/docker-image.js";
17
+ import { defaultDockerContainerPrefix, defaultDockerNetworkName } from '../lib/app-runtime.js';
18
+ import { resolveDockerContainerPrefix, resolveDockerNetworkName } from '../lib/cli-config.js';
19
+ import { DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../lib/docker-image.js";
21
20
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
22
- import { validateExternalDbConfig } from "../lib/db-connection-check.js";
21
+ import { validateExternalDbConfig, validateMysqlLowerCaseTableNamesCompatibility } from "../lib/db-connection-check.js";
23
22
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
24
- import { run, runNocoBaseCommand } from '../lib/run-npm.js';
25
- import { printInfo, printStage, printVerbose, printWarning, setVerboseMode, } from '../lib/ui.js';
23
+ import { commandOutput, commandSucceeds, run, runNocoBaseCommand } from '../lib/run-npm.js';
24
+ import { printInfo, printStage, printVerbose, printWarning, setVerboseMode } from '../lib/ui.js';
26
25
  import { omitKeys, upperFirst } from "../lib/object-utils.js";
27
26
  import { getEnv, setCurrentEnv, upsertEnv } from '../lib/auth-store.js';
28
27
  import { buildStoredEnvConfig } from '../lib/env-config.js';
29
- import { resolveDockerEnvFileArg, } from "../lib/docker-env-file.js";
30
- import Download, { defaultDockerRegistryForLang, } from './download.js';
28
+ import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
29
+ import Download, { defaultDockerRegistryForLang } from './download.js';
31
30
  import EnvAdd from "./env/add.js";
32
31
  const DEFAULT_INSTALL_ENV_NAME = 'local';
33
32
  const DEFAULT_INSTALL_LANG = 'en-US';
@@ -139,6 +138,9 @@ const installText = (key, values) => localeText(`commands.install.${key}`, value
139
138
  function formatDeferredAuthMessage(envName, authType) {
140
139
  const normalizedAuthType = String(authType ?? '').trim();
141
140
  const nextStep = `Authentication was skipped for env "${envName}". Run \`nb env auth ${envName}\` to finish setup.`;
141
+ if (normalizedAuthType === 'basic') {
142
+ return `${nextStep} You will be prompted for a username and password.`;
143
+ }
142
144
  if (normalizedAuthType === 'token') {
143
145
  return `${nextStep} You will be prompted for an access token.`;
144
146
  }
@@ -154,9 +156,7 @@ function isInstallDbDialect(value) {
154
156
  return INSTALL_DB_DIALECTS.includes(value);
155
157
  }
156
158
  function downloadVersionPromptValue(version) {
157
- return version === 'latest' || version === 'beta' || version === 'alpha'
158
- ? version
159
- : 'other';
159
+ return version === 'latest' || version === 'beta' || version === 'alpha' ? version : 'other';
160
160
  }
161
161
  function supportsBuiltinDbDialect(value) {
162
162
  const dialect = String(value ?? '').trim();
@@ -171,22 +171,16 @@ function defaultBuiltinDbImageForDialect(value) {
171
171
  const defaults = resolveCliLocale(process.env.NB_LOCALE) === 'zh-CN'
172
172
  ? DEFAULT_INSTALL_BUILTIN_DB_IMAGES_ZH_CN
173
173
  : DEFAULT_INSTALL_BUILTIN_DB_IMAGES;
174
- return supportsBuiltinDbDialect(dialect)
175
- ? defaults[dialect]
176
- : defaults.postgres;
174
+ return supportsBuiltinDbDialect(dialect) ? defaults[dialect] : defaults.postgres;
177
175
  }
178
176
  function defaultDbDatabaseForDialect(value) {
179
- return String(value ?? '').trim() === 'kingbase'
180
- ? 'kingbase'
181
- : DEFAULT_INSTALL_DB_DATABASE;
177
+ return String(value ?? '').trim() === 'kingbase' ? 'kingbase' : DEFAULT_INSTALL_DB_DATABASE;
182
178
  }
183
179
  function defaultDbHostForBuiltinDb(values) {
184
- return Boolean(values.builtinDb)
185
- ? DEFAULT_INSTALL_BUILTIN_DB_HOST
186
- : DEFAULT_INSTALL_DB_HOST;
180
+ return values.builtinDb ? DEFAULT_INSTALL_BUILTIN_DB_HOST : DEFAULT_INSTALL_DB_HOST;
187
181
  }
188
182
  function validateBuiltinDbEnabled(value, values) {
189
- if (!Boolean(value)) {
183
+ if (!value) {
190
184
  return undefined;
191
185
  }
192
186
  const dialect = String(values.dbDialect ?? 'postgres').trim() || 'postgres';
@@ -203,7 +197,14 @@ async function validateExternalDbPromptField(value, values) {
203
197
  if (typeof value === 'string' && value.trim() === '') {
204
198
  return undefined;
205
199
  }
206
- return await validateExternalDbConfig(values);
200
+ const connectionError = await validateExternalDbConfig(values);
201
+ if (connectionError) {
202
+ return connectionError;
203
+ }
204
+ if (!Object.prototype.hasOwnProperty.call(values, 'dbUnderscored')) {
205
+ return undefined;
206
+ }
207
+ return await validateMysqlLowerCaseTableNamesCompatibility(values);
207
208
  }
208
209
  function defaultInstallAppRootPath(envName) {
209
210
  const name = String(envName ?? DEFAULT_INSTALL_ENV_NAME).trim() || DEFAULT_INSTALL_ENV_NAME;
@@ -222,54 +223,6 @@ function pickPresetKeys(source, keys) {
222
223
  }
223
224
  return out;
224
225
  }
225
- async function commandSucceeds(command, args, options) {
226
- return await new Promise((resolve) => {
227
- const child = spawn(command, args, {
228
- cwd: options?.cwd,
229
- env: {
230
- ...process.env,
231
- ...options?.env,
232
- },
233
- stdio: 'ignore',
234
- });
235
- child.once('error', () => resolve(false));
236
- child.once('close', (code) => resolve(code === 0));
237
- });
238
- }
239
- async function commandOutput(command, args, options) {
240
- return await new Promise((resolve, reject) => {
241
- const child = spawn(command, args, {
242
- cwd: options?.cwd,
243
- env: {
244
- ...process.env,
245
- ...options?.env,
246
- },
247
- stdio: ['ignore', 'pipe', 'pipe'],
248
- });
249
- let stdout = '';
250
- let stderr = '';
251
- child.stdout.setEncoding('utf8');
252
- child.stderr.setEncoding('utf8');
253
- child.stdout.on('data', (chunk) => {
254
- stdout += chunk;
255
- });
256
- child.stderr.on('data', (chunk) => {
257
- stderr += chunk;
258
- });
259
- child.once('error', reject);
260
- child.once('close', (code, signal) => {
261
- if (code === 0) {
262
- resolve(stdout);
263
- return;
264
- }
265
- if (signal) {
266
- reject(new Error(`${command} exited due to signal ${signal}`));
267
- return;
268
- }
269
- reject(new Error(`${command} exited with code ${code}: ${stderr.trim()}`));
270
- });
271
- });
272
- }
273
226
  function optionalEnvString(value) {
274
227
  const text = String(value ?? '').trim();
275
228
  return text || undefined;
@@ -324,7 +277,7 @@ export default class Install extends Command {
324
277
  '<%= config.bin %> <%= command.id %> --env app1 --root-nickname "Super Admin"',
325
278
  '<%= config.bin %> <%= command.id %> --env myenv --app-root-path=./myenv/source/ --storage-path=./myenv/storage/',
326
279
  '<%= config.bin %> <%= command.id %> --env dev -y --app-root-path=./dev/source/',
327
- '<%= config.bin %> <%= command.id %> --env dev -y --fetch-source --app-root-path=./dev/source/',
280
+ '<%= config.bin %> <%= command.id %> --env dev -y --skip-download --source git --app-root-path=./dev/source/',
328
281
  ];
329
282
  static flags = {
330
283
  yes: Flags.boolean({
@@ -359,14 +312,20 @@ export default class Install extends Command {
359
312
  }),
360
313
  'auth-type': Flags.string({
361
314
  char: 'a',
362
- description: 'Authentication: token (API key) or oauth (browser login via `nb env auth`)',
363
- options: ['token', 'oauth'],
315
+ description: 'Authentication: basic (username/password login), token (API key), or oauth (browser login via `nb env auth`)',
316
+ options: ['basic', 'token', 'oauth'],
364
317
  }),
365
318
  'access-token': Flags.string({
366
319
  char: 't',
367
320
  aliases: ['token'],
368
321
  description: 'API key or access token when using --auth-type token',
369
322
  }),
323
+ username: Flags.string({
324
+ description: 'Username when using --auth-type basic',
325
+ }),
326
+ password: Flags.string({
327
+ description: 'Password when using --auth-type basic',
328
+ }),
370
329
  'skip-auth': Flags.boolean({
371
330
  description: 'Save the env auth mode now and finish authentication later with `nb env auth`',
372
331
  default: false,
@@ -440,8 +399,8 @@ export default class Install extends Command {
440
399
  description: 'Use underscored database naming for the app',
441
400
  default: false,
442
401
  }),
443
- 'fetch-source': Flags.boolean({
444
- description: 'Download NocoBase app files or pull a Docker image before installing',
402
+ 'skip-download': Flags.boolean({
403
+ description: 'Skip the download step and reuse an existing local app directory or Docker image',
445
404
  default: false,
446
405
  }),
447
406
  ...omitKeys(Download.flags, ['yes']),
@@ -488,12 +447,6 @@ export default class Install extends Command {
488
447
  placeholder: installText('prompts.storagePath.placeholder'),
489
448
  initialValue: (values) => defaultInstallStoragePath(values.env ?? values.appName),
490
449
  },
491
- fetchSource: {
492
- type: 'boolean',
493
- message: installText('prompts.fetchSource.message'),
494
- initialValue: true,
495
- yesInitialValue: true,
496
- },
497
450
  };
498
451
  static dbPrompts = {
499
452
  dbDialect: {
@@ -521,8 +474,7 @@ export default class Install extends Command {
521
474
  message: installText('prompts.builtinDbImage.message'),
522
475
  placeholder: installText('prompts.builtinDbImage.placeholder'),
523
476
  initialValue: (values) => defaultBuiltinDbImageForDialect(values.dbDialect),
524
- hidden: (values) => !Boolean(values.builtinDb)
525
- || !supportsBuiltinDbDialect(values.dbDialect),
477
+ hidden: (values) => !values.builtinDb || !supportsBuiltinDbDialect(values.dbDialect),
526
478
  required: true,
527
479
  },
528
480
  dbHost: {
@@ -542,8 +494,7 @@ export default class Install extends Command {
542
494
  initialValue: (values) => defaultDbPortForDialect(values.dbDialect),
543
495
  required: true,
544
496
  validate: Install.validateDbPort,
545
- hidden: (values) => Boolean(values.builtinDb)
546
- && String(values.source ?? '').trim() === 'docker',
497
+ hidden: (values) => Boolean(values.builtinDb) && String(values.source ?? '').trim() === 'docker',
547
498
  },
548
499
  dbDatabase: {
549
500
  type: 'text',
@@ -568,6 +519,27 @@ export default class Install extends Command {
568
519
  required: true,
569
520
  validate: validateExternalDbPromptField,
570
521
  },
522
+ dbSchema: {
523
+ type: 'text',
524
+ message: installText('prompts.dbSchema.message'),
525
+ placeholder: installText('prompts.dbSchema.placeholder'),
526
+ hidden: (values) => String(values.dbDialect ?? '').trim() !== 'postgres',
527
+ },
528
+ dbTablePrefix: {
529
+ type: 'text',
530
+ message: installText('prompts.dbTablePrefix.message'),
531
+ placeholder: installText('prompts.dbTablePrefix.placeholder'),
532
+ },
533
+ dbUnderscored: {
534
+ type: 'boolean',
535
+ message: installText('prompts.dbUnderscored.message'),
536
+ initialValue: false,
537
+ yesInitialValue: false,
538
+ validate: (value, values) => validateExternalDbPromptField(value, {
539
+ ...values,
540
+ dbUnderscored: Boolean(value),
541
+ }),
542
+ },
571
543
  };
572
544
  static rootUserPrompts = {
573
545
  rootUsername: {
@@ -662,9 +634,8 @@ export default class Install extends Command {
662
634
  * Booleans with defaults are only preset when the user passed the flag on argv (see `download`).
663
635
  * Does not include `env` — use {@link buildEnvPresetValuesFromFlags} for {@link Install.envPrompts}.
664
636
  */
665
- static buildPresetValuesFromFlags(flags) {
637
+ static buildPresetValuesFromFlags(flags, argv = process.argv.slice(2)) {
666
638
  const preset = {};
667
- const argv = process.argv.slice(2);
668
639
  const apiBaseUrl = Install.toOptionalPromptString(flags['api-base-url']);
669
640
  if (apiBaseUrl) {
670
641
  preset.apiBaseUrl = apiBaseUrl;
@@ -687,6 +658,12 @@ export default class Install extends Command {
687
658
  if (flags['access-token'] !== undefined || flags.token !== undefined) {
688
659
  preset.accessToken = String(flags['access-token'] ?? flags.token ?? '');
689
660
  }
661
+ if (flags.username !== undefined) {
662
+ preset.username = String(flags.username ?? '').trim();
663
+ }
664
+ if (flags.password !== undefined) {
665
+ preset.password = String(flags.password ?? '');
666
+ }
690
667
  if (flags.lang !== undefined) {
691
668
  const v = String(flags.lang).trim();
692
669
  if (v) {
@@ -726,8 +703,15 @@ export default class Install extends Command {
726
703
  if (flags['root-nickname'] !== undefined) {
727
704
  preset.rootNickname = String(flags['root-nickname'] ?? '').trim();
728
705
  }
729
- if (argvHasToken(argv, ['--fetch-source'])) {
730
- preset.fetchSource = flags['fetch-source'];
706
+ if (argvHasToken(argv, ['--skip-download'])) {
707
+ preset.skipDownload = flags['skip-download'];
708
+ if (flags['skip-download']) {
709
+ preset.dockerSave = false;
710
+ preset.replace = false;
711
+ preset.devDependencies = false;
712
+ preset.build = false;
713
+ preset.buildDts = false;
714
+ }
731
715
  }
732
716
  if (argvHasToken(argv, ['--builtin-db', '--no-builtin-db'])) {
733
717
  preset.builtinDb = flags['builtin-db'];
@@ -789,18 +773,17 @@ export default class Install extends Command {
789
773
  }
790
774
  return preset;
791
775
  }
792
- static buildAppPresetValuesFromFlags(flags) {
793
- return pickPresetKeys(Install.buildPresetValuesFromFlags(flags), [
776
+ static buildAppPresetValuesFromFlags(flags, argv = process.argv.slice(2)) {
777
+ return pickPresetKeys(Install.buildPresetValuesFromFlags(flags, argv), [
794
778
  'lang',
795
779
  'force',
796
780
  'appRootPath',
797
781
  'appPort',
798
782
  'storagePath',
799
- 'fetchSource',
800
783
  ]);
801
784
  }
802
- static buildDbPresetValuesFromFlags(flags) {
803
- return pickPresetKeys(Install.buildPresetValuesFromFlags(flags), [
785
+ static buildDbPresetValuesFromFlags(flags, argv = process.argv.slice(2)) {
786
+ return pickPresetKeys(Install.buildPresetValuesFromFlags(flags, argv), [
804
787
  'builtinDb',
805
788
  'dbDialect',
806
789
  'builtinDbImage',
@@ -814,18 +797,20 @@ export default class Install extends Command {
814
797
  'dbUnderscored',
815
798
  ]);
816
799
  }
817
- static buildRootPresetValuesFromFlags(flags) {
818
- return pickPresetKeys(Install.buildPresetValuesFromFlags(flags), [
800
+ static buildRootPresetValuesFromFlags(flags, argv = process.argv.slice(2)) {
801
+ return pickPresetKeys(Install.buildPresetValuesFromFlags(flags, argv), [
819
802
  'rootUsername',
820
803
  'rootEmail',
821
804
  'rootPassword',
822
805
  'rootNickname',
823
806
  ]);
824
807
  }
825
- static buildEnvAddPresetValuesFromFlags(flags) {
826
- return pickPresetKeys(Install.buildPresetValuesFromFlags(flags), [
808
+ static buildEnvAddPresetValuesFromFlags(flags, argv = process.argv.slice(2)) {
809
+ return pickPresetKeys(Install.buildPresetValuesFromFlags(flags, argv), [
827
810
  'apiBaseUrl',
828
811
  'authType',
812
+ 'username',
813
+ 'password',
829
814
  'accessToken',
830
815
  ]);
831
816
  }
@@ -915,9 +900,13 @@ export default class Install extends Command {
915
900
  if (validationError) {
916
901
  throw new Error(validationError);
917
902
  }
903
+ const compatibilityError = await validateMysqlLowerCaseTableNamesCompatibility(dbResults);
904
+ if (compatibilityError) {
905
+ throw new Error(compatibilityError);
906
+ }
918
907
  }
919
908
  static async readResumePortValidationContext(values) {
920
- if (!Boolean(values.resume)) {
909
+ if (!values.resume) {
921
910
  return undefined;
922
911
  }
923
912
  const envName = Install.toOptionalPromptString(values.env);
@@ -948,8 +937,7 @@ export default class Install extends Command {
948
937
  }
949
938
  static async isResumeManagedPortReuse(params) {
950
939
  if (params.target === 'app') {
951
- if ((params.context.source === 'npm' || params.context.source === 'git')
952
- && params.context.appRootPath) {
940
+ if ((params.context.source === 'npm' || params.context.source === 'git') && params.context.appRootPath) {
953
941
  return await Install.isLocalPm2ProcessUsingPort(params.context.appRootPath, params.port);
954
942
  }
955
943
  const containerName = Install.buildDockerAppContainerName(params.context.envName, params.context.dockerContainerPrefix);
@@ -965,19 +953,13 @@ export default class Install extends Command {
965
953
  if (!containerName || !port) {
966
954
  return false;
967
955
  }
968
- const exists = await commandSucceeds('docker', [
969
- 'container',
970
- 'inspect',
971
- containerName,
972
- ]);
956
+ const exists = await commandSucceeds('docker', ['container', 'inspect', containerName]);
973
957
  if (!exists) {
974
958
  return false;
975
959
  }
976
960
  try {
977
961
  const output = await commandOutput('docker', ['port', containerName]);
978
- return output
979
- .split(/\r?\n/)
980
- .some((line) => line.includes(`:${port}`));
962
+ return output.split(/\r?\n/).some((line) => line.includes(`:${port}`));
981
963
  }
982
964
  catch {
983
965
  return false;
@@ -1033,29 +1015,20 @@ export default class Install extends Command {
1033
1015
  ...(appRootPath ? { appRootPath } : {}),
1034
1016
  ...(appPort ? { appPort } : {}),
1035
1017
  ...(storagePath ? { storagePath } : {}),
1036
- ...(source
1037
- ? { fetchSource: true }
1038
- : appRootPath
1039
- ? { fetchSource: false }
1040
- : {}),
1041
1018
  };
1042
1019
  const downloadPreset = {
1043
1020
  ...(source ? { source } : {}),
1044
1021
  ...(downloadVersion
1045
1022
  ? {
1046
1023
  version: downloadVersionPromptValue(downloadVersion),
1047
- ...(downloadVersionPromptValue(downloadVersion) === 'other'
1048
- ? { otherVersion: downloadVersion }
1049
- : {}),
1024
+ ...(downloadVersionPromptValue(downloadVersion) === 'other' ? { otherVersion: downloadVersion } : {}),
1050
1025
  }
1051
1026
  : {}),
1052
1027
  ...(dockerRegistry ? { dockerRegistry } : {}),
1053
1028
  ...(dockerPlatform ? { dockerPlatform } : {}),
1054
1029
  ...(gitUrl ? { gitUrl } : {}),
1055
1030
  ...(npmRegistry ? { npmRegistry } : {}),
1056
- ...(typeof config.devDependencies === 'boolean'
1057
- ? { devDependencies: config.devDependencies }
1058
- : {}),
1031
+ ...(typeof config.devDependencies === 'boolean' ? { devDependencies: config.devDependencies } : {}),
1059
1032
  ...(typeof config.build === 'boolean' ? { build: config.build } : {}),
1060
1033
  ...(typeof config.buildDts === 'boolean' ? { buildDts: config.buildDts } : {}),
1061
1034
  };
@@ -1085,6 +1058,13 @@ export default class Install extends Command {
1085
1058
  envAddPreset.accessToken = String(auth.accessToken);
1086
1059
  }
1087
1060
  }
1061
+ else if (savedAuthType === 'basic') {
1062
+ envAddPreset.authType = 'basic';
1063
+ const authUsername = Install.toOptionalPromptString(config.authUsername) ?? rootUsername;
1064
+ if (authUsername) {
1065
+ envAddPreset.username = authUsername;
1066
+ }
1067
+ }
1088
1068
  else if (savedAuthType === 'oauth') {
1089
1069
  envAddPreset.authType = 'oauth';
1090
1070
  }
@@ -1169,8 +1149,7 @@ export default class Install extends Command {
1169
1149
  }
1170
1150
  static shouldPublishBuiltinDbPortForValues(values) {
1171
1151
  const builtinDb = values.builtinDb === undefined ? true : Boolean(values.builtinDb);
1172
- return builtinDb
1173
- && Install.shouldPublishBuiltinDbPort(values.source);
1152
+ return builtinDb && Install.shouldPublishBuiltinDbPort(values.source);
1174
1153
  }
1175
1154
  static async buildDbPromptInitialValues(params) {
1176
1155
  if (params.flags['db-port'] !== undefined) {
@@ -1226,9 +1205,8 @@ export default class Install extends Command {
1226
1205
  * Resolve the effective preset `values` for the embedded download step.
1227
1206
  * Explicit download flags win; otherwise `-y` falls back to the docker + alpha quickstart path.
1228
1207
  */
1229
- static buildDownloadPresetValuesForInstall(flags, appResults, envName, yes) {
1208
+ static buildDownloadPresetValuesForInstall(flags, appResults, envName, yes, argv = process.argv.slice(2)) {
1230
1209
  const preset = {};
1231
- const argv = process.argv.slice(2);
1232
1210
  const appRoot = String(appResults.appRootPath ?? '').trim() || defaultInstallAppRootPath(envName);
1233
1211
  const lang = String(appResults.lang ?? DEFAULT_INSTALL_LANG).trim() || DEFAULT_INSTALL_LANG;
1234
1212
  preset.lang = lang;
@@ -1267,8 +1245,7 @@ export default class Install extends Command {
1267
1245
  }
1268
1246
  }
1269
1247
  if (flags['npm-registry'] !== undefined) {
1270
- preset.npmRegistry =
1271
- typeof flags['npm-registry'] === 'string' ? flags['npm-registry'] : '';
1248
+ preset.npmRegistry = typeof flags['npm-registry'] === 'string' ? flags['npm-registry'] : '';
1272
1249
  }
1273
1250
  if (flags.resume && !argvHasToken(argv, ['--replace', '-r'])) {
1274
1251
  preset.replace = true;
@@ -1312,15 +1289,11 @@ export default class Install extends Command {
1312
1289
  }
1313
1290
  static buildBuiltinDbContainerPrefix(containerPrefix) {
1314
1291
  const storedName = String(containerPrefix ?? '').trim();
1315
- return storedName
1316
- ? Install.sanitizeDockerResourceName(storedName)
1317
- : Install.defaultDockerContainerPrefix();
1292
+ return storedName ? Install.sanitizeDockerResourceName(storedName) : Install.defaultDockerContainerPrefix();
1318
1293
  }
1319
1294
  static buildManagedDockerNetworkName(networkName) {
1320
1295
  const storedName = String(networkName ?? '').trim();
1321
- return storedName
1322
- ? Install.sanitizeDockerResourceName(storedName)
1323
- : Install.defaultDockerNetworkName();
1296
+ return storedName ? Install.sanitizeDockerResourceName(storedName) : Install.defaultDockerNetworkName();
1324
1297
  }
1325
1298
  static buildBuiltinDbNetworkName(envName, networkName) {
1326
1299
  void envName;
@@ -1353,25 +1326,19 @@ export default class Install extends Command {
1353
1326
  }
1354
1327
  static buildBuiltinDbPlan(params) {
1355
1328
  const dbDialect = String(params.dbDialect ?? 'postgres').trim() || 'postgres';
1356
- const dbPort = String(params.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
1357
- || defaultDbPortForDialect(dbDialect);
1329
+ const dbPort = String(params.dbPort ?? defaultDbPortForDialect(dbDialect)).trim() || defaultDbPortForDialect(dbDialect);
1358
1330
  const defaultDbDatabase = defaultDbDatabaseForDialect(dbDialect);
1359
1331
  const networkName = Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
1360
1332
  const containerName = Install.buildBuiltinDbContainerName(params.envName, dbDialect, params.dockerContainerPrefix ?? params.workspaceName);
1361
1333
  const dbHostInput = String(params.dbHost ?? '').trim();
1362
1334
  const dbHost = Install.shouldPublishBuiltinDbPort(params.source)
1363
- ? (dbHostInput
1364
- && dbHostInput !== DEFAULT_INSTALL_BUILTIN_DB_HOST
1365
- && dbHostInput !== containerName
1335
+ ? dbHostInput && dbHostInput !== DEFAULT_INSTALL_BUILTIN_DB_HOST && dbHostInput !== containerName
1366
1336
  ? dbHostInput
1367
- : DEFAULT_INSTALL_DB_HOST)
1368
- : (dbHostInput
1369
- && dbHostInput !== DEFAULT_INSTALL_DB_HOST
1370
- && dbHostInput !== 'localhost'
1337
+ : DEFAULT_INSTALL_DB_HOST
1338
+ : dbHostInput && dbHostInput !== DEFAULT_INSTALL_DB_HOST && dbHostInput !== 'localhost'
1371
1339
  ? dbHostInput
1372
- : containerName);
1373
- const storagePath = resolveConfiguredEnvPath(params.storagePath)
1374
- ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1340
+ : containerName;
1341
+ const storagePath = resolveConfiguredEnvPath(params.storagePath) ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1375
1342
  if (dbDialect === 'postgres') {
1376
1343
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1377
1344
  const dataDir = path.resolve(storagePath, 'db', 'postgres');
@@ -1402,12 +1369,9 @@ export default class Install extends Command {
1402
1369
  dbDialect,
1403
1370
  dbHost,
1404
1371
  dbPort,
1405
- dbDatabase: String(params.dbDatabase ?? defaultDbDatabase).trim()
1406
- || defaultDbDatabase,
1407
- dbUser: String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim()
1408
- || DEFAULT_INSTALL_DB_USER,
1409
- dbPassword: String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD)
1410
- || DEFAULT_INSTALL_DB_PASSWORD,
1372
+ dbDatabase: String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase,
1373
+ dbUser: String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER,
1374
+ dbPassword: String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD,
1411
1375
  networkName,
1412
1376
  containerName,
1413
1377
  dataDir,
@@ -1593,11 +1557,7 @@ export default class Install extends Command {
1593
1557
  }
1594
1558
  }
1595
1559
  async dockerContainerExists(name) {
1596
- return await commandSucceeds('docker', [
1597
- 'container',
1598
- 'inspect',
1599
- name,
1600
- ]);
1560
+ return await commandSucceeds('docker', ['container', 'inspect', name]);
1601
1561
  }
1602
1562
  async removeDockerContainer(name) {
1603
1563
  await run('docker', ['rm', '-f', name], {
@@ -1647,8 +1607,7 @@ export default class Install extends Command {
1647
1607
  });
1648
1608
  }
1649
1609
  async startBuiltinDb(params) {
1650
- const storagePath = String(params.appResults.storagePath ?? '').trim()
1651
- || defaultInstallStoragePath(params.envName);
1610
+ const storagePath = String(params.appResults.storagePath ?? '').trim() || defaultInstallStoragePath(params.envName);
1652
1611
  const plan = Install.buildBuiltinDbPlan({
1653
1612
  envName: params.envName,
1654
1613
  workspaceName: params.workspaceName,
@@ -1686,23 +1645,20 @@ export default class Install extends Command {
1686
1645
  return plan;
1687
1646
  }
1688
1647
  static async buildDockerAppPlan(params) {
1689
- const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim()
1690
- || defaultDockerRegistryForLang(process.env.NB_LOCALE);
1648
+ const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim() ||
1649
+ defaultDockerRegistryForLang(process.env.NB_LOCALE);
1691
1650
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
1692
1651
  const imageRef = resolveDockerImageRef(dockerRegistry, version, {
1693
1652
  defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
1694
1653
  defaultVersion: DEFAULT_DOCKER_VERSION,
1695
1654
  });
1696
1655
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1697
- const storagePath = resolveConfiguredEnvPath(String(params.appResults.storagePath ?? '').trim()
1698
- || defaultInstallStoragePath(params.envName))
1699
- ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1656
+ const storagePath = resolveConfiguredEnvPath(String(params.appResults.storagePath ?? '').trim() || defaultInstallStoragePath(params.envName)) ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1700
1657
  const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
1701
1658
  const dbHost = String(params.dbResults.dbHost ?? DEFAULT_INSTALL_DB_HOST).trim() || DEFAULT_INSTALL_DB_HOST;
1702
- const dbPort = String(params.dbResults.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
1703
- || defaultDbPortForDialect(dbDialect);
1704
- const dbDatabase = String(params.dbResults.dbDatabase ?? DEFAULT_INSTALL_DB_DATABASE).trim()
1705
- || DEFAULT_INSTALL_DB_DATABASE;
1659
+ const dbPort = String(params.dbResults.dbPort ?? defaultDbPortForDialect(dbDialect)).trim() ||
1660
+ defaultDbPortForDialect(dbDialect);
1661
+ const dbDatabase = String(params.dbResults.dbDatabase ?? DEFAULT_INSTALL_DB_DATABASE).trim() || DEFAULT_INSTALL_DB_DATABASE;
1706
1662
  const dbUser = String(params.dbResults.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1707
1663
  const dbPassword = String(params.dbResults.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
1708
1664
  const dbSchema = optionalEnvString(params.dbResults.dbSchema);
@@ -1767,8 +1723,8 @@ export default class Install extends Command {
1767
1723
  return 'created';
1768
1724
  }
1769
1725
  async installDockerApp(params) {
1770
- const networkName = params.builtinDbPlan?.networkName
1771
- ?? Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
1726
+ const networkName = params.builtinDbPlan?.networkName ??
1727
+ Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
1772
1728
  await this.ensureDockerNetwork(networkName);
1773
1729
  const plan = await Install.buildDockerAppPlan({
1774
1730
  envName: params.envName,
@@ -1815,26 +1771,26 @@ export default class Install extends Command {
1815
1771
  Install.pushDownloadArgIfValue(argv, '--source', results.source);
1816
1772
  Install.pushDownloadArgIfValue(argv, '--version', downloadResultsValue(results, 'version'));
1817
1773
  Install.pushDownloadArgIfValue(argv, '--output-dir', source === 'npm' || source === 'git'
1818
- ? (resolveConfiguredEnvPath(results.outputDir)
1819
- ?? resolveConfiguredEnvPath(String(results.outputDir ?? '').trim() || defaultInstallAppRootPath(results.env)))
1774
+ ? resolveConfiguredEnvPath(results.outputDir) ??
1775
+ resolveConfiguredEnvPath(String(results.outputDir ?? '').trim() || defaultInstallAppRootPath(results.env))
1820
1776
  : results.outputDir);
1821
1777
  Install.pushDownloadArgIfValue(argv, '--git-url', results.gitUrl);
1822
1778
  Install.pushDownloadArgIfValue(argv, '--docker-registry', results.dockerRegistry);
1823
1779
  Install.pushDownloadArgIfValue(argv, '--docker-platform', results.dockerPlatform);
1824
1780
  Install.pushDownloadArgIfValue(argv, '--npm-registry', results.npmRegistry);
1825
- if (Boolean(results.replace)) {
1781
+ if (results.replace) {
1826
1782
  argv.push('--replace');
1827
1783
  }
1828
- if (Boolean(results.devDependencies)) {
1784
+ if (results.devDependencies) {
1829
1785
  argv.push('--dev-dependencies');
1830
1786
  }
1831
- if (Boolean(results.dockerSave)) {
1787
+ if (results.dockerSave) {
1832
1788
  argv.push('--docker-save');
1833
1789
  }
1834
- if (results.build !== undefined && !Boolean(results.build)) {
1790
+ if (results.build !== undefined && !results.build) {
1835
1791
  argv.push('--no-build');
1836
1792
  }
1837
- if (Boolean(results.buildDts)) {
1793
+ if (results.buildDts) {
1838
1794
  argv.push('--build-dts');
1839
1795
  }
1840
1796
  return argv;
@@ -1844,15 +1800,26 @@ export default class Install extends Command {
1844
1800
  if (projectRoot) {
1845
1801
  return projectRoot;
1846
1802
  }
1847
- const outputDir = String(params.downloadResults.outputDir ?? '').trim()
1848
- || String(params.appResults.appRootPath ?? '').trim()
1849
- || defaultInstallAppRootPath(params.envName);
1803
+ const outputDir = String(params.downloadResults.outputDir ?? '').trim() ||
1804
+ String(params.appResults.appRootPath ?? '').trim() ||
1805
+ defaultInstallAppRootPath(params.envName);
1850
1806
  return resolveConfiguredEnvPath(outputDir) ?? resolveEnvRelativePath(defaultInstallAppRootPath(params.envName));
1851
1807
  }
1852
1808
  static resolveLocalProjectConfigPath(params) {
1853
- return (String(params.downloadResults.outputDir ?? '').trim()
1854
- || String(params.appResults.appRootPath ?? '').trim()
1855
- || defaultInstallAppRootPath(params.envName));
1809
+ return (String(params.downloadResults.outputDir ?? '').trim() ||
1810
+ String(params.appResults.appRootPath ?? '').trim() ||
1811
+ defaultInstallAppRootPath(params.envName));
1812
+ }
1813
+ static buildSkipDownloadValues(envName, appResults) {
1814
+ const appRoot = String(appResults.appRootPath ?? '').trim() || defaultInstallAppRootPath(envName);
1815
+ return {
1816
+ outputDir: appRoot,
1817
+ replace: false,
1818
+ dockerSave: false,
1819
+ devDependencies: false,
1820
+ build: false,
1821
+ buildDts: false,
1822
+ };
1856
1823
  }
1857
1824
  commandStdio(verbose) {
1858
1825
  return verbose ? 'inherit' : 'ignore';
@@ -1862,7 +1829,41 @@ export default class Install extends Command {
1862
1829
  verbose: params.verbose,
1863
1830
  compactLog: true,
1864
1831
  });
1865
- return await this.config.runCommand('source:download', argv);
1832
+ return (await this.config.runCommand('source:download', argv));
1833
+ }
1834
+ async ensureSkippedDownloadInputsReady(params) {
1835
+ if (params.source === 'docker') {
1836
+ const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim() ||
1837
+ defaultDockerRegistryForLang(process.env.NB_LOCALE);
1838
+ const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
1839
+ const imageRef = resolveDockerImageRef(dockerRegistry, version, {
1840
+ defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
1841
+ defaultVersion: DEFAULT_DOCKER_VERSION,
1842
+ });
1843
+ const imageExists = await commandSucceeds('docker', ['image', 'inspect', imageRef]);
1844
+ if (!imageExists) {
1845
+ throw new Error(translateCli('commands.install.messages.skipDownloadDockerImageMissing', {
1846
+ imageRef,
1847
+ }));
1848
+ }
1849
+ return;
1850
+ }
1851
+ if (params.source === 'npm' || params.source === 'git') {
1852
+ const projectRoot = Install.resolveLocalProjectRoot({
1853
+ envName: params.envName,
1854
+ appResults: params.appResults,
1855
+ downloadResults: params.downloadResults,
1856
+ });
1857
+ try {
1858
+ await access(projectRoot);
1859
+ await access(path.join(projectRoot, 'package.json'));
1860
+ }
1861
+ catch {
1862
+ throw new Error(translateCli('commands.install.messages.skipDownloadLocalAppMissing', {
1863
+ projectRoot,
1864
+ }));
1865
+ }
1866
+ }
1866
1867
  }
1867
1868
  async downloadLocalApp(params) {
1868
1869
  const result = await this.downloadManagedSource({
@@ -1883,31 +1884,24 @@ export default class Install extends Command {
1883
1884
  return downloadedProjectRoot;
1884
1885
  }
1885
1886
  static buildLocalAppEnvVars(params) {
1886
- const configuredStoragePath = String(params.appResults.storagePath ?? '').trim()
1887
- || defaultInstallStoragePath(params.envName);
1888
- const storagePath = resolveConfiguredEnvPath(configuredStoragePath)
1889
- ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1890
- const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim()
1891
- || 'postgres';
1887
+ const configuredStoragePath = String(params.appResults.storagePath ?? '').trim() || defaultInstallStoragePath(params.envName);
1888
+ const storagePath = resolveConfiguredEnvPath(configuredStoragePath) ??
1889
+ resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1890
+ const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
1892
1891
  const appKey = crypto.randomBytes(32).toString('hex');
1893
1892
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
1894
1893
  const env = {
1895
1894
  STORAGE_PATH: storagePath,
1896
- APP_PORT: String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim()
1897
- || DEFAULT_INSTALL_APP_PORT,
1895
+ APP_PORT: String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT,
1898
1896
  APP_KEY: appKey,
1899
1897
  TZ: timeZone,
1900
1898
  DB_DIALECT: dbDialect,
1901
- DB_HOST: String(params.dbResults.dbHost ?? DEFAULT_INSTALL_DB_HOST).trim()
1902
- || DEFAULT_INSTALL_DB_HOST,
1903
- DB_PORT: String(params.dbResults.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
1904
- || defaultDbPortForDialect(dbDialect),
1905
- DB_DATABASE: String(params.dbResults.dbDatabase ?? DEFAULT_INSTALL_DB_DATABASE).trim()
1906
- || DEFAULT_INSTALL_DB_DATABASE,
1907
- DB_USER: String(params.dbResults.dbUser ?? DEFAULT_INSTALL_DB_USER).trim()
1908
- || DEFAULT_INSTALL_DB_USER,
1909
- DB_PASSWORD: String(params.dbResults.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD)
1910
- || DEFAULT_INSTALL_DB_PASSWORD,
1899
+ DB_HOST: String(params.dbResults.dbHost ?? DEFAULT_INSTALL_DB_HOST).trim() || DEFAULT_INSTALL_DB_HOST,
1900
+ DB_PORT: String(params.dbResults.dbPort ?? defaultDbPortForDialect(dbDialect)).trim() ||
1901
+ defaultDbPortForDialect(dbDialect),
1902
+ DB_DATABASE: String(params.dbResults.dbDatabase ?? DEFAULT_INSTALL_DB_DATABASE).trim() || DEFAULT_INSTALL_DB_DATABASE,
1903
+ DB_USER: String(params.dbResults.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER,
1904
+ DB_PASSWORD: String(params.dbResults.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD,
1911
1905
  ...Install.buildInitAppEnvVars({
1912
1906
  appResults: params.appResults,
1913
1907
  rootResults: params.rootResults,
@@ -1957,10 +1951,8 @@ export default class Install extends Command {
1957
1951
  };
1958
1952
  }
1959
1953
  static resolveApiBaseUrl(params) {
1960
- const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim()
1961
- || DEFAULT_INSTALL_APP_PORT;
1962
- return (String(params.envAddResults.apiBaseUrl ?? '').trim()
1963
- || `http://127.0.0.1:${appPort}/api`);
1954
+ const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1955
+ return String(params.envAddResults.apiBaseUrl ?? '').trim() || `http://127.0.0.1:${appPort}/api`;
1964
1956
  }
1965
1957
  static buildHealthCheckUrl(apiBaseUrl) {
1966
1958
  return `${apiBaseUrl.replace(/\/+$/, '')}/__health_check`;
@@ -1989,9 +1981,7 @@ export default class Install extends Command {
1989
1981
  const body = Install.formatHealthCheckMessage(text);
1990
1982
  return {
1991
1983
  ok: response.ok && text.trim().toLowerCase() === 'ok',
1992
- message: response.ok
1993
- ? `HTTP ${response.status}: ${body}`
1994
- : `HTTP ${response.status}: ${body}`,
1984
+ message: response.ok ? `HTTP ${response.status}: ${body}` : `HTTP ${response.status}: ${body}`,
1995
1985
  };
1996
1986
  }
1997
1987
  catch (error) {
@@ -2055,8 +2045,7 @@ export default class Install extends Command {
2055
2045
  if (!params.appReady) {
2056
2046
  return;
2057
2047
  }
2058
- const authType = String(params.envAddResults.authType ?? 'oauth').trim()
2059
- || 'oauth';
2048
+ const authType = String(params.envAddResults.authType ?? 'oauth').trim() || 'oauth';
2060
2049
  if (params.skipAuth) {
2061
2050
  printInfo(formatDeferredAuthMessage(params.envName, authType));
2062
2051
  return;
@@ -2064,23 +2053,34 @@ export default class Install extends Command {
2064
2053
  if (authType === 'oauth') {
2065
2054
  await this.config.runCommand('env:auth', [params.envName]);
2066
2055
  }
2056
+ else if (authType === 'basic') {
2057
+ const authArgv = [params.envName, '--auth-type', 'basic'];
2058
+ const username = String(params.envAddResults.username ?? '').trim();
2059
+ const password = String(params.envAddResults.password ?? '');
2060
+ if (username) {
2061
+ authArgv.push('--username', username);
2062
+ }
2063
+ if (password) {
2064
+ authArgv.push('--password', password);
2065
+ }
2066
+ await this.config.runCommand('env:auth', authArgv);
2067
+ }
2067
2068
  await this.config.runCommand('env:update', [params.envName]);
2068
2069
  }
2069
2070
  static buildSavedEnvConfig(params) {
2070
- const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim()
2071
- || DEFAULT_INSTALL_APP_PORT;
2072
- const storagePath = String(params.appResults.storagePath ?? '').trim()
2073
- || defaultInstallStoragePath(params.envName);
2071
+ const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
2072
+ const storagePath = String(params.appResults.storagePath ?? '').trim() || defaultInstallStoragePath(params.envName);
2074
2073
  const envFile = String(params.appResults.envFile ?? '').trim() || undefined;
2075
2074
  const apiBaseUrl = Install.resolveApiBaseUrl({
2076
2075
  appResults: params.appResults,
2077
2076
  envAddResults: params.envAddResults,
2078
2077
  });
2079
- const authType = String(params.envAddResults.authType ?? 'oauth').trim()
2080
- || 'oauth';
2078
+ const authType = String(params.envAddResults.authType ?? 'oauth').trim() || 'oauth';
2079
+ const authUsername = authType === 'basic' ? String(params.envAddResults.username ?? params.rootResults.rootUsername ?? '').trim() : '';
2081
2080
  return buildStoredEnvConfig({
2082
2081
  apiBaseUrl,
2083
2082
  authType,
2083
+ ...(authUsername ? { authUsername } : {}),
2084
2084
  accessToken: params.envAddResults.accessToken,
2085
2085
  source: downloadResultsValue(params.downloadResults, 'source'),
2086
2086
  downloadVersion: downloadResultsValue(params.downloadResults, 'version'),
@@ -2115,6 +2115,7 @@ export default class Install extends Command {
2115
2115
  });
2116
2116
  }
2117
2117
  async collectPromptResults(parsed, yes) {
2118
+ const commandArgv = this.argv ?? process.argv.slice(2);
2118
2119
  const resumePreset = await this.resolveResumePresetValues(parsed, yes);
2119
2120
  const envPreset = {
2120
2121
  ...(resumePreset?.envPreset ?? {}),
@@ -2130,7 +2131,7 @@ export default class Install extends Command {
2130
2131
  const envName = String(envResults.env ?? '').trim() || DEFAULT_INSTALL_ENV_NAME;
2131
2132
  const appPreset = {
2132
2133
  ...(resumePreset?.appPreset ?? {}),
2133
- ...Install.buildAppPresetValuesFromFlags(parsed),
2134
+ ...Install.buildAppPresetValuesFromFlags(parsed, commandArgv),
2134
2135
  };
2135
2136
  const appCatalog = Install.buildAppPromptsCatalog(envName, {
2136
2137
  resume: parsed.resume,
@@ -2140,56 +2141,58 @@ export default class Install extends Command {
2140
2141
  envName,
2141
2142
  flags: {
2142
2143
  ...parsed,
2143
- 'app-root-path': parsed['app-root-path']
2144
- ?? Install.toOptionalPromptString(appPreset.appRootPath),
2145
- 'app-port': parsed['app-port']
2146
- ?? Install.toOptionalPromptString(appPreset.appPort),
2147
- 'storage-path': parsed['storage-path']
2148
- ?? Install.toOptionalPromptString(appPreset.storagePath),
2144
+ 'app-root-path': parsed['app-root-path'] ?? Install.toOptionalPromptString(appPreset.appRootPath),
2145
+ 'app-port': parsed['app-port'] ?? Install.toOptionalPromptString(appPreset.appPort),
2146
+ 'storage-path': parsed['storage-path'] ?? Install.toOptionalPromptString(appPreset.storagePath),
2149
2147
  },
2150
2148
  }),
2151
2149
  values: appPreset,
2152
2150
  yesInitialValues: { resume: parsed.resume },
2153
2151
  yes,
2154
2152
  });
2155
- let downloadResults = {};
2156
- if (Boolean(appResults.fetchSource)) {
2157
- const downloadOpts = Install.buildDownloadPromptOptionsForInstall(appResults, envName);
2158
- downloadOpts.values = {
2159
- ...(resumePreset?.downloadPreset ?? {}),
2160
- ...downloadOpts.values,
2161
- ...Install.buildDownloadPresetValuesForInstall(parsed, appResults, envName, yes),
2162
- };
2163
- downloadOpts.yes = yes;
2164
- downloadResults = await runPromptCatalog(Download.prompts, downloadOpts);
2153
+ const downloadOpts = Install.buildDownloadPromptOptionsForInstall(appResults, envName);
2154
+ downloadOpts.values = {
2155
+ ...(resumePreset?.downloadPreset ?? {}),
2156
+ ...downloadOpts.values,
2157
+ ...Install.buildDownloadPresetValuesForInstall(parsed, appResults, envName, yes, commandArgv),
2158
+ ...(parsed['skip-download'] ? Install.buildSkipDownloadValues(envName, appResults) : {}),
2159
+ };
2160
+ downloadOpts.yes = yes;
2161
+ const downloadResults = await runPromptCatalog(Download.prompts, downloadOpts);
2162
+ if (parsed['skip-download']) {
2163
+ delete downloadResults.outputDir;
2164
+ delete downloadResults.replace;
2165
+ delete downloadResults.dockerSave;
2166
+ delete downloadResults.devDependencies;
2167
+ delete downloadResults.build;
2168
+ delete downloadResults.buildDts;
2165
2169
  }
2166
2170
  const dbPreset = {
2167
2171
  ...(resumePreset?.dbPreset ?? {}),
2168
- ...Install.buildDbPresetValuesFromFlags(parsed),
2172
+ ...Install.buildDbPresetValuesFromFlags(parsed, commandArgv),
2169
2173
  };
2170
2174
  const promptedDbResults = await runPromptCatalog(Install.buildDbPromptsCatalog(envName, downloadResults, {
2171
2175
  resume: parsed.resume,
2172
2176
  }), {
2173
2177
  initialValues: {
2174
2178
  ...downloadResults,
2175
- ...await Install.buildDbPromptInitialValues({
2179
+ ...(await Install.buildDbPromptInitialValues({
2176
2180
  flags: {
2177
2181
  ...parsed,
2178
- 'db-port': parsed['db-port']
2179
- ?? Install.toOptionalPromptString(dbPreset.dbPort),
2182
+ 'db-port': parsed['db-port'] ?? Install.toOptionalPromptString(dbPreset.dbPort),
2180
2183
  },
2181
2184
  downloadResults,
2182
2185
  dbPreset,
2183
- }),
2186
+ })),
2184
2187
  },
2185
2188
  values: dbPreset,
2186
2189
  yes,
2187
2190
  });
2188
2191
  const dbResults = {
2189
- ...promptedDbResults,
2190
2192
  ...pickPresetKeys(dbPreset, ['dbSchema', 'dbTablePrefix', 'dbUnderscored']),
2193
+ ...promptedDbResults,
2191
2194
  };
2192
- const rootPreset = Install.buildRootPresetValuesFromFlags(parsed);
2195
+ const rootPreset = Install.buildRootPresetValuesFromFlags(parsed, commandArgv);
2193
2196
  const rootResults = await runPromptCatalog(Install.rootUserPrompts, {
2194
2197
  initialValues: {},
2195
2198
  values: {
@@ -2199,18 +2202,44 @@ export default class Install extends Command {
2199
2202
  yes,
2200
2203
  });
2201
2204
  const envAddPromptsForInstall = this.buildEnvAddPromptsForInstall(parsed);
2202
- const envAddResults = await runPromptCatalog(envAddPromptsForInstall, {
2203
- initialValues: {
2204
- apiBaseUrl: `http://127.0.0.1:${appResults.appPort ?? DEFAULT_INSTALL_APP_PORT}/api`,
2205
- },
2205
+ const envAddResumePreset = resumePreset?.envAddPreset ?? {};
2206
+ const envAddFlagValues = Install.buildEnvAddPresetValuesFromFlags(parsed, commandArgv);
2207
+ const envAddPreset = {
2208
+ ...envAddResumePreset,
2209
+ ...envAddFlagValues,
2210
+ };
2211
+ const resolvedEnvAddAuthType = String(envAddPreset.authType ?? '').trim();
2212
+ const envAddInitialValues = {
2213
+ apiBaseUrl: `http://127.0.0.1:${appResults.appPort ?? DEFAULT_INSTALL_APP_PORT}/api`,
2214
+ ...envAddResumePreset,
2215
+ ...(!parsed['skip-auth'] && resolvedEnvAddAuthType === 'basic'
2216
+ ? {
2217
+ ...(!Object.prototype.hasOwnProperty.call(envAddResumePreset, 'username') &&
2218
+ !Object.prototype.hasOwnProperty.call(envAddFlagValues, 'username') &&
2219
+ rootResults.rootUsername !== undefined
2220
+ ? { username: String(rootResults.rootUsername).trim() }
2221
+ : {}),
2222
+ ...(!Object.prototype.hasOwnProperty.call(envAddFlagValues, 'password') &&
2223
+ rootResults.rootPassword !== undefined
2224
+ ? { password: String(rootResults.rootPassword ?? '') }
2225
+ : {}),
2226
+ }
2227
+ : {}),
2228
+ };
2229
+ const promptedEnvAddResults = await runPromptCatalog(envAddPromptsForInstall, {
2230
+ initialValues: envAddInitialValues,
2206
2231
  values: {
2207
2232
  name: envName,
2208
2233
  ...(parsed['skip-auth'] ? { skipAuth: true } : {}),
2209
- ...(resumePreset?.envAddPreset ?? {}),
2210
- ...Install.buildEnvAddPresetValuesFromFlags(parsed),
2234
+ ...envAddFlagValues,
2211
2235
  },
2212
2236
  yes,
2213
2237
  });
2238
+ const envAddResults = {
2239
+ ...pickPresetKeys(envAddInitialValues, ['username', 'password']),
2240
+ ...promptedEnvAddResults,
2241
+ ...pickPresetKeys(envAddFlagValues, ['username', 'password']),
2242
+ };
2214
2243
  return {
2215
2244
  envName,
2216
2245
  envResults,
@@ -2243,16 +2272,23 @@ export default class Install extends Command {
2243
2272
  : 'Resuming setup from the saved workspace config');
2244
2273
  }
2245
2274
  const promptResults = await this.collectPromptResults(parsed, flags.yes);
2246
- const { envName, appResults, downloadResults, dbResults, rootResults, envAddResults, } = promptResults;
2275
+ const { envName, appResults, downloadResults, dbResults, rootResults, envAddResults } = promptResults;
2247
2276
  const source = String(downloadResultsValue(downloadResults, 'source') ?? '').trim();
2248
- const usesDockerResources = Boolean(dbResults.builtinDb)
2249
- || (Boolean(appResults.fetchSource) && source === 'docker');
2277
+ const usesDockerResources = Boolean(dbResults.builtinDb) || source === 'docker';
2250
2278
  const dockerNetworkName = usesDockerResources
2251
2279
  ? await resolveDockerNetworkName({ scope: resolveDefaultConfigScope() })
2252
2280
  : undefined;
2253
2281
  const dockerContainerPrefix = usesDockerResources
2254
2282
  ? await resolveDockerContainerPrefix({ scope: resolveDefaultConfigScope() })
2255
2283
  : undefined;
2284
+ if (parsed['skip-download']) {
2285
+ await this.ensureSkippedDownloadInputsReady({
2286
+ source,
2287
+ envName,
2288
+ appResults,
2289
+ downloadResults,
2290
+ });
2291
+ }
2256
2292
  await Install.ensureExternalDbReadyForInstall(dbResults);
2257
2293
  if (!parsed.resume) {
2258
2294
  if (!parsed['skip-save-env-log']) {
@@ -2271,7 +2307,7 @@ export default class Install extends Command {
2271
2307
  }
2272
2308
  }
2273
2309
  let builtinDbPlan;
2274
- if (Boolean(dbResults.builtinDb)) {
2310
+ if (dbResults.builtinDb) {
2275
2311
  builtinDbPlan = await this.startBuiltinDb({
2276
2312
  envName,
2277
2313
  dockerNetworkName,
@@ -2291,14 +2327,16 @@ export default class Install extends Command {
2291
2327
  }
2292
2328
  let dockerAppPlan;
2293
2329
  let localAppPlan;
2294
- if (Boolean(appResults.fetchSource)) {
2330
+ if (source === 'docker' || source === 'npm' || source === 'git') {
2295
2331
  this.logStage('Preparing application');
2296
2332
  if (source === 'docker') {
2297
- await this.downloadManagedSource({
2298
- downloadResults,
2299
- verbose: parsed.verbose,
2300
- });
2301
- printInfo('Application image ready.');
2333
+ if (!parsed['skip-download']) {
2334
+ await this.downloadManagedSource({
2335
+ downloadResults,
2336
+ verbose: parsed.verbose,
2337
+ });
2338
+ printInfo('Application image ready.');
2339
+ }
2302
2340
  dockerAppPlan = await this.installDockerApp({
2303
2341
  envName,
2304
2342
  dockerNetworkName,
@@ -2316,13 +2354,21 @@ export default class Install extends Command {
2316
2354
  }
2317
2355
  else if (source === 'npm' || source === 'git') {
2318
2356
  const localSource = source === 'npm' ? 'npm' : 'git';
2319
- const projectRoot = await this.downloadLocalApp({
2320
- envName,
2321
- appResults,
2322
- downloadResults,
2323
- verbose: parsed.verbose,
2324
- });
2325
- printInfo('Application files ready.');
2357
+ const projectRoot = parsed['skip-download']
2358
+ ? Install.resolveLocalProjectRoot({
2359
+ envName,
2360
+ appResults,
2361
+ downloadResults,
2362
+ })
2363
+ : await this.downloadLocalApp({
2364
+ envName,
2365
+ appResults,
2366
+ downloadResults,
2367
+ verbose: parsed.verbose,
2368
+ });
2369
+ if (!parsed['skip-download']) {
2370
+ printInfo('Application files ready.');
2371
+ }
2326
2372
  localAppPlan = await this.startLocalApp({
2327
2373
  envName,
2328
2374
  source: localSource,