@nocobase/cli 2.2.0-beta.7 → 2.2.0-beta.9

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 (34) 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 +21 -0
  11. package/dist/commands/app/start.js +4 -1
  12. package/dist/commands/install.js +53 -138
  13. package/dist/commands/proxy/caddy/generate.js +93 -7
  14. package/dist/commands/proxy/nginx/generate.js +98 -7
  15. package/dist/commands/revision/create.js +1 -1
  16. package/dist/commands/self/update.js +2 -2
  17. package/dist/commands/source/download.js +16 -12
  18. package/dist/lib/app-managed-resources.js +3 -4
  19. package/dist/lib/auth-store.js +82 -6
  20. package/dist/lib/cli-config.js +71 -1
  21. package/dist/lib/docker-image.js +94 -6
  22. package/dist/lib/env-auth.js +291 -45
  23. package/dist/lib/env-config.js +5 -0
  24. package/dist/lib/env-proxy-config.js +48 -0
  25. package/dist/lib/env-proxy.js +164 -58
  26. package/dist/lib/prompt-validators.js +1 -1
  27. package/dist/lib/proxy-caddy.js +77 -9
  28. package/dist/lib/proxy-nginx.js +71 -11
  29. package/dist/lib/run-npm.js +4 -0
  30. package/dist/lib/self-manager.js +251 -46
  31. package/dist/lib/startup-update.js +1 -1
  32. package/dist/locale/en-US.json +2 -2
  33. package/dist/locale/zh-CN.json +2 -2
  34. package/package.json +3 -2
@@ -8,20 +8,51 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
11
- import { getCaddyProxyDriver, writeCaddyProxyBundle, resolveCaddyProxyRuntimeContext, } from '../../../lib/proxy-caddy.js';
11
+ import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
12
+ import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
13
+ import { getCaddyProxyDriver, writeManualCaddyProxyBundle, writeCaddyProxyBundle, resolveCaddyProxyRuntimeContext, } from '../../../lib/proxy-caddy.js';
12
14
  import { normalizeProxyListenPort } from '../../../lib/proxy-nginx.js';
13
15
  import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
14
16
  export default class ProxyCaddyGenerate extends Command {
15
17
  static summary = 'Generate caddy proxy files for one managed env';
16
18
  static examples = [
19
+ '<%= config.bin %> proxy caddy generate --host app1.example.com',
17
20
  '<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com',
18
21
  '<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com --port 8080',
22
+ '<%= config.bin %> proxy caddy generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
19
23
  ];
20
24
  static flags = {
21
25
  env: Flags.string({
22
26
  char: 'e',
23
- description: 'CLI env name to generate proxy files for',
24
- required: true,
27
+ description: 'CLI env name to generate proxy files for. Defaults to the current env when omitted',
28
+ }),
29
+ manual: Flags.boolean({
30
+ description: 'Generate proxy files from explicit runtime flags instead of a saved env',
31
+ default: false,
32
+ }),
33
+ name: Flags.string({
34
+ description: 'Output bundle name used under .nocobase/proxy/caddy in manual mode',
35
+ }),
36
+ 'storage-path': Flags.string({
37
+ description: 'Path to the NocoBase storage directory in manual mode',
38
+ }),
39
+ 'dist-root-path': Flags.string({
40
+ description: 'Path to the dist-client root directory used to generate index-v1.html and index-v2.html in manual mode',
41
+ }),
42
+ 'runtime-version': Flags.string({
43
+ description: 'Frontend runtime version under dist-root-path in manual mode',
44
+ }),
45
+ 'app-public-path': Flags.string({
46
+ description: 'Public base path served by the proxied app in manual mode. Defaults to /',
47
+ }),
48
+ 'upstream-host': Flags.string({
49
+ description: 'Upstream host used by caddy reverse_proxy in manual mode',
50
+ }),
51
+ 'upstream-port': Flags.string({
52
+ description: 'Upstream port used by caddy reverse_proxy in manual mode',
53
+ }),
54
+ 'cdn-base-url': Flags.string({
55
+ description: 'Client asset CDN base URL used when generating runtime HTML',
25
56
  }),
26
57
  host: Flags.string({
27
58
  description: 'Host exposed by the caddy site block, such as example.com or localhost',
@@ -35,9 +66,56 @@ export default class ProxyCaddyGenerate extends Command {
35
66
  const requestedEnv = flags.env?.trim() || undefined;
36
67
  const requestedPort = flags.port?.trim() || undefined;
37
68
  const normalizedPort = normalizeProxyListenPort(requestedPort);
69
+ const manual = Boolean(flags.manual);
38
70
  if (requestedPort && !normalizedPort) {
39
71
  this.error(`Invalid proxy entry port "${requestedPort}". Use an integer between 1 and 65535.`);
40
72
  }
73
+ if (manual && requestedEnv) {
74
+ this.error('`--manual` cannot be combined with `--env`.');
75
+ }
76
+ if (manual) {
77
+ const name = flags.name?.trim() || undefined;
78
+ const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
79
+ const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
80
+ const storagePath = flags['storage-path']?.trim() || undefined;
81
+ const distRootPath = flags['dist-root-path']?.trim() || undefined;
82
+ const runtimeVersion = flags['runtime-version']?.trim() || undefined;
83
+ if (requestedUpstreamPort && !upstreamPort) {
84
+ this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
85
+ }
86
+ if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
87
+ this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
88
+ }
89
+ const driver = await getCaddyProxyDriver();
90
+ const runtimeContext = await resolveCaddyProxyRuntimeContext();
91
+ announceTargetEnv(name);
92
+ startTask(`Generating caddy proxy config for env "${name}" with the ${driver} driver...`);
93
+ try {
94
+ const { bundle, status } = await writeManualCaddyProxyBundle({
95
+ name,
96
+ storagePath,
97
+ distRootPath,
98
+ runtimeVersion,
99
+ appPublicPath: flags['app-public-path']?.trim() || undefined,
100
+ upstreamHost: flags['upstream-host']?.trim() || undefined,
101
+ upstreamPort,
102
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
103
+ }, {
104
+ host: flags.host?.trim() || undefined,
105
+ port: normalizedPort,
106
+ }, runtimeContext, {
107
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
108
+ });
109
+ succeedTask(status === 'created'
110
+ ? `Saved caddy proxy files for env "${name}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
111
+ : `Saved caddy proxy files for env "${name}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
112
+ }
113
+ catch (error) {
114
+ failTask(`Failed to generate caddy proxy config for env "${name}".`);
115
+ this.error(error instanceof Error ? error.message : String(error));
116
+ }
117
+ return;
118
+ }
41
119
  const runtime = await resolveManagedAppRuntime(requestedEnv);
42
120
  if (!runtime) {
43
121
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
@@ -53,10 +131,18 @@ export default class ProxyCaddyGenerate extends Command {
53
131
  announceTargetEnv(runtime.envName);
54
132
  startTask(`Generating caddy proxy config for env "${runtime.envName}" with the ${driver} driver...`);
55
133
  try {
56
- const { bundle, status } = await writeCaddyProxyBundle(runtime, {
57
- host: flags.host?.trim() || undefined,
58
- port: normalizedPort,
59
- }, runtimeContext);
134
+ const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'caddy');
135
+ const appEntryOptions = {
136
+ host: flags.host?.trim() || savedAppEntryOptions?.host,
137
+ port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
138
+ };
139
+ const { bundle, status } = await writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext, {
140
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
141
+ });
142
+ await setEnvProxyEntry(runtime.envName, 'caddy', {
143
+ host: appEntryOptions.host,
144
+ port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
145
+ }, { scope: resolveDefaultConfigScope() });
60
146
  succeedTask(status === 'created'
61
147
  ? `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
62
148
  : `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
@@ -8,19 +8,54 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
11
- import { getNginxProxyDriver, normalizeProxyListenPort, resolveNginxProxyRuntimeContext, writeNginxProxyBundle, } from '../../../lib/proxy-nginx.js';
11
+ import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
12
+ import { getNginxProxyDriver, normalizeProxyListenPort, resolveNginxProxyRuntimeContext, writeManualNginxProxyBundle, writeNginxProxyBundle, } from '../../../lib/proxy-nginx.js';
13
+ import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
12
14
  import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
13
15
  export default class ProxyNginxGenerate extends Command {
14
16
  static summary = 'Generate nginx proxy files for one managed env';
15
17
  static examples = [
18
+ '<%= config.bin %> proxy nginx generate --host app1.example.com',
16
19
  '<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com',
17
20
  '<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com --port 8080',
21
+ '<%= config.bin %> proxy nginx generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
18
22
  ];
19
23
  static flags = {
20
24
  env: Flags.string({
21
25
  char: 'e',
22
- description: 'CLI env name to generate proxy files for',
23
- required: true,
26
+ description: 'CLI env name to generate proxy files for. Defaults to the current env when omitted',
27
+ }),
28
+ manual: Flags.boolean({
29
+ description: 'Generate proxy files from explicit runtime flags instead of a saved env',
30
+ default: false,
31
+ }),
32
+ name: Flags.string({
33
+ description: 'Output bundle name used under .nocobase/proxy/nginx in manual mode',
34
+ }),
35
+ 'storage-path': Flags.string({
36
+ description: 'Path to the NocoBase storage directory in manual mode',
37
+ }),
38
+ 'dist-root-path': Flags.string({
39
+ description: 'Path to the dist-client root directory used to generate index-v1.html and index-v2.html in manual mode',
40
+ }),
41
+ 'runtime-version': Flags.string({
42
+ description: 'Frontend runtime version under dist-root-path in manual mode',
43
+ }),
44
+ 'app-public-path': Flags.string({
45
+ description: 'Public base path served by the proxied app in manual mode. Defaults to /',
46
+ }),
47
+ 'upstream-host': Flags.string({
48
+ description: 'Upstream host used by nginx proxy_pass in manual mode',
49
+ }),
50
+ 'upstream-port': Flags.string({
51
+ description: 'Upstream port used by nginx proxy_pass in manual mode',
52
+ }),
53
+ 'cdn-base-url': Flags.string({
54
+ description: 'Client asset CDN base URL used when generating runtime HTML',
55
+ }),
56
+ force: Flags.boolean({
57
+ description: 'Overwrite existing app.conf even when the managed block is missing',
58
+ default: false,
24
59
  }),
25
60
  host: Flags.string({
26
61
  description: 'Host exposed by the nginx entry config, such as example.com or localhost',
@@ -34,9 +69,56 @@ export default class ProxyNginxGenerate extends Command {
34
69
  const requestedEnv = flags.env?.trim() || undefined;
35
70
  const requestedPort = flags.port?.trim() || undefined;
36
71
  const normalizedPort = normalizeProxyListenPort(requestedPort);
72
+ const manual = Boolean(flags.manual);
37
73
  if (requestedPort && !normalizedPort) {
38
74
  this.error(`Invalid proxy entry port "${requestedPort}". Use an integer between 1 and 65535.`);
39
75
  }
76
+ if (manual && requestedEnv) {
77
+ this.error('`--manual` cannot be combined with `--env`.');
78
+ }
79
+ if (manual) {
80
+ const name = flags.name?.trim() || undefined;
81
+ const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
82
+ const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
83
+ const storagePath = flags['storage-path']?.trim() || undefined;
84
+ const distRootPath = flags['dist-root-path']?.trim() || undefined;
85
+ const runtimeVersion = flags['runtime-version']?.trim() || undefined;
86
+ if (requestedUpstreamPort && !upstreamPort) {
87
+ this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
88
+ }
89
+ if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
90
+ this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
91
+ }
92
+ const driver = await getNginxProxyDriver();
93
+ const runtimeContext = await resolveNginxProxyRuntimeContext();
94
+ announceTargetEnv(name);
95
+ startTask(`Generating nginx proxy config for env "${name}" with the ${driver} driver...`);
96
+ try {
97
+ const { bundle, status } = await writeManualNginxProxyBundle({
98
+ name,
99
+ storagePath,
100
+ distRootPath,
101
+ runtimeVersion,
102
+ appPublicPath: flags['app-public-path']?.trim() || undefined,
103
+ upstreamHost: flags['upstream-host']?.trim() || undefined,
104
+ upstreamPort,
105
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
106
+ }, {
107
+ host: flags.host?.trim() || undefined,
108
+ port: normalizedPort,
109
+ }, runtimeContext, {
110
+ force: flags.force,
111
+ });
112
+ succeedTask(status === 'created'
113
+ ? `Saved nginx proxy files for env "${name}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
114
+ : `Saved nginx proxy files for env "${name}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
115
+ }
116
+ catch (error) {
117
+ failTask(`Failed to generate nginx proxy config for env "${name}".`);
118
+ this.error(error instanceof Error ? error.message : String(error));
119
+ }
120
+ return;
121
+ }
40
122
  const runtime = await resolveManagedAppRuntime(requestedEnv);
41
123
  if (!runtime) {
42
124
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
@@ -52,10 +134,19 @@ export default class ProxyNginxGenerate extends Command {
52
134
  announceTargetEnv(runtime.envName);
53
135
  startTask(`Generating nginx proxy config for env "${runtime.envName}" with the ${driver} driver...`);
54
136
  try {
55
- const { bundle, status } = await writeNginxProxyBundle(runtime, {
56
- host: flags.host?.trim() || undefined,
57
- port: normalizedPort,
58
- }, runtimeContext);
137
+ const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'nginx');
138
+ const appEntryOptions = {
139
+ host: flags.host?.trim() || savedAppEntryOptions?.host,
140
+ port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
141
+ };
142
+ const { bundle, status } = await writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext, {
143
+ cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
144
+ force: flags.force,
145
+ });
146
+ await setEnvProxyEntry(runtime.envName, 'nginx', {
147
+ host: appEntryOptions.host,
148
+ port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
149
+ }, { scope: resolveDefaultConfigScope() });
59
150
  succeedTask(status === 'created'
60
151
  ? `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
61
152
  : `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
@@ -100,7 +100,7 @@ export default class RevisionCreate extends Command {
100
100
  path: '/app:publishEvent',
101
101
  body: {
102
102
  plugin: 'plugin-version-control',
103
- command: 'revision:create',
103
+ command: 'revision.create',
104
104
  payload: {
105
105
  description,
106
106
  },
@@ -26,7 +26,7 @@ function formatSkillsUpdateMessage(result, verbose) {
26
26
  }
27
27
  export default class SelfUpdate extends Command {
28
28
  static summary = 'Update the globally installed NocoBase CLI';
29
- static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm install.';
29
+ static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm, pnpm, or yarn install.';
30
30
  static examples = [
31
31
  '<%= config.bin %> <%= command.id %>',
32
32
  '<%= config.bin %> <%= command.id %> --yes',
@@ -77,7 +77,7 @@ export default class SelfUpdate extends Command {
77
77
  message: flags.skills
78
78
  ? `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion} and refresh the globally installed NocoBase AI coding skills?`
79
79
  : `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
80
- default: false,
80
+ default: true,
81
81
  });
82
82
  }
83
83
  catch {
@@ -12,9 +12,10 @@ import path from 'node:path';
12
12
  import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
13
13
  import { runPromptCatalog, } from "../../lib/prompt-catalog.js";
14
14
  import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, resolveCliLocale, translateCli, } from "../../lib/cli-locale.js";
15
- import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveDockerImageRef, } from "../../lib/docker-image.js";
15
+ import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveOfficialDockerRegistry, resolveDockerImageRef, } from "../../lib/docker-image.js";
16
16
  import { getEnv } from '../../lib/auth-store.js';
17
17
  import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
18
+ import { getCliConfigValue } from '../../lib/cli-config.js';
18
19
  import { buildBeforeDependencyInstallHookContext, runBeforeDependencyInstallHook, } from '../../lib/hook-script.js';
19
20
  import { run } from "../../lib/run-npm.js";
20
21
  import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
@@ -450,9 +451,9 @@ export default class SourceDownload extends Command {
450
451
  }
451
452
  return outputAbs;
452
453
  }
453
- dockerTarPath(flags, outputAbs) {
454
+ async dockerTarPath(flags, outputAbs) {
454
455
  const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
455
- defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
456
+ defaultRegistry: await this.resolveConfiguredDockerRegistryDefault(),
456
457
  defaultVersion: 'latest',
457
458
  });
458
459
  const safeBase = imageRef.replace(/[\\/:]/g, '-');
@@ -462,9 +463,11 @@ export default class SourceDownload extends Command {
462
463
  * Defaults for prompts only. Keys present in **`preset`** are omitted so `runPromptCatalog` uses
463
464
  * **`values`** (preset) alone for those steps — no duplicate prefill for skipped prompts.
464
465
  */
465
- buildInitialValuesFromParsed(flags, preset) {
466
+ async resolveConfiguredDockerRegistryDefault() {
467
+ return resolveOfficialDockerRegistry(await getCliConfigValue('nb-image-registry'));
468
+ }
469
+ buildInitialValuesFromParsed(flags, preset, defaultDockerRegistry) {
466
470
  const initialValues = {};
467
- const localeDefaultDockerRegistry = defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE);
468
471
  const source = flags.source?.trim();
469
472
  if (source) {
470
473
  initialValues.source = source;
@@ -488,7 +491,7 @@ export default class SourceDownload extends Command {
488
491
  initialValues.dockerRegistry = String(flags['docker-registry'] ?? '').trim();
489
492
  }
490
493
  else {
491
- initialValues.dockerRegistry = localeDefaultDockerRegistry;
494
+ initialValues.dockerRegistry = defaultDockerRegistry;
492
495
  }
493
496
  initialValues.dockerPlatform = normalizeDockerPlatform(flags['docker-platform']);
494
497
  initialValues.dockerSave = flags['docker-save'];
@@ -569,7 +572,7 @@ export default class SourceDownload extends Command {
569
572
  }
570
573
  return flags.build;
571
574
  }
572
- mapCatalogResultsToResolved(results, flags) {
575
+ mapCatalogResultsToResolved(results, flags, defaultDockerRegistry) {
573
576
  const source = String(results.source);
574
577
  const version = resolveVersionFromResults(results, flags.version) || 'latest';
575
578
  const devDependencies = source === 'npm' ? Boolean(results.devDependencies) : undefined;
@@ -583,7 +586,7 @@ export default class SourceDownload extends Command {
583
586
  const dockerRegistry = source === 'docker'
584
587
  ? results.dockerRegistry !== undefined
585
588
  ? String(results.dockerRegistry).trim() || undefined
586
- : flags['docker-registry']?.trim() || defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE)
589
+ : flags['docker-registry']?.trim() || defaultDockerRegistry
587
590
  : undefined;
588
591
  const dockerPlatform = source === 'docker'
589
592
  ? normalizeDockerPlatform(results.dockerPlatform !== undefined ? results.dockerPlatform : flags['docker-platform'])
@@ -628,7 +631,8 @@ export default class SourceDownload extends Command {
628
631
  this.error('Download source is required in non-interactive mode. Use --source npm, --source git, or --source docker.');
629
632
  }
630
633
  const presetValues = this.buildPresetValuesFromFlags(flags);
631
- const initialValues = this.buildInitialValuesFromParsed(flags, presetValues);
634
+ const defaultDockerRegistry = await this.resolveConfiguredDockerRegistryDefault();
635
+ const initialValues = this.buildInitialValuesFromParsed(flags, presetValues, defaultDockerRegistry);
632
636
  const results = await runPromptCatalog(SourceDownload.prompts, {
633
637
  initialValues,
634
638
  values: presetValues,
@@ -654,7 +658,7 @@ export default class SourceDownload extends Command {
654
658
  if (flags['docker-save'] && source !== 'docker') {
655
659
  this.error('--docker-save is only available when --source docker is selected.');
656
660
  }
657
- return this.mapCatalogResultsToResolved(results, flags);
661
+ return this.mapCatalogResultsToResolved(results, flags, defaultDockerRegistry);
658
662
  }
659
663
  npmRegistryUrl(flags) {
660
664
  const url = flags['npm-registry']?.trim();
@@ -784,7 +788,7 @@ export default class SourceDownload extends Command {
784
788
  }
785
789
  async downloadFromDocker(flags) {
786
790
  const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
787
- defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
791
+ defaultRegistry: await this.resolveConfiguredDockerRegistryDefault(),
788
792
  defaultVersion: 'latest',
789
793
  });
790
794
  const platform = dockerPlatformArg(flags['docker-platform']);
@@ -811,7 +815,7 @@ export default class SourceDownload extends Command {
811
815
  await fsp.rm(outAbs, { recursive: true, force: true });
812
816
  }
813
817
  await fsp.mkdir(outAbs, { recursive: true });
814
- const tarPath = this.dockerTarPath(flags, outAbs);
818
+ const tarPath = await this.dockerTarPath(flags, outAbs);
815
819
  this.log(`Saving Docker image tarball to ${tarPath}`);
816
820
  await this.runExternalCommand('docker', ['save', '-o', tarPath, imageRef], {
817
821
  errorName: 'docker save',
@@ -14,7 +14,7 @@ import { dockerContainerExists, managedAppLifecycleEnvVars, runLocalNocoBaseComm
14
14
  import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin-db.js';
15
15
  import { resolveConfiguredStoragePath } from './env-paths.js';
16
16
  import { resolveDockerEnvFileArg } from "./docker-env-file.js";
17
- import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
17
+ import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageContainerPort, resolveDockerImageRef, } from "./docker-image.js";
18
18
  import { resolveHookScriptPath } from './hook-script.js';
19
19
  import { commandSucceeds, ensureDockerDaemonRunning, run } from './run-npm.js';
20
20
  import Install from '../commands/install.js';
@@ -192,6 +192,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
192
192
  defaultRegistry: DEFAULT_DOCKER_REGISTRY,
193
193
  defaultVersion: DEFAULT_DOCKER_VERSION,
194
194
  });
195
+ const containerPort = resolveDockerImageContainerPort(imageRef);
195
196
  const missing = [];
196
197
  if (!storagePath) {
197
198
  missing.push('storagePath');
@@ -225,8 +226,6 @@ export async function buildSavedDockerRunArgs(runtime, options) {
225
226
  '-d',
226
227
  '--name',
227
228
  runtime.containerName,
228
- '--restart',
229
- 'always',
230
229
  '--network',
231
230
  runtime.workspaceName,
232
231
  ];
@@ -235,7 +234,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
235
234
  args.push('--platform', dockerPlatform);
236
235
  }
237
236
  if (appPort) {
238
- args.push('-p', `${appPort}:80`);
237
+ args.push('-p', `${appPort}:${containerPort}`);
239
238
  }
240
239
  if (envFile) {
241
240
  args.push('--env-file', envFile);
@@ -11,6 +11,7 @@ import path from 'node:path';
11
11
  import { resolveAppPublicPath } from './app-public-path.js';
12
12
  import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
13
13
  import { normalizeCliLocale } from './cli-locale.js';
14
+ import { normalizeEnvProxyConfig, normalizeEnvProxyProviderConfig, } from './env-proxy-config.js';
14
15
  import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
15
16
  import { ENV_CONFIG_SCHEMA_VERSION, normalizeEnvConfigSchemaVersion } from './env-config.js';
16
17
  import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
@@ -81,12 +82,14 @@ function normalizeEnvConfigEntry(entry) {
81
82
  const normalizedKind = resolveEnvKind(entry);
82
83
  const apiBaseUrl = readEnvApiBaseUrl(entry);
83
84
  const schemaVersion = normalizeEnvConfigSchemaVersion(entry.schemaVersion);
85
+ const proxy = normalizeEnvProxyConfig(entry.proxy);
84
86
  return {
85
87
  ...rest,
86
88
  ...(schemaVersion ? { schemaVersion } : {}),
87
89
  ...(normalizedKind ? { kind: normalizedKind } : {}),
88
90
  ...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
89
91
  ...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
92
+ ...(proxy ? { proxy } : {}),
90
93
  };
91
94
  }
92
95
  function normalizeAuthConfig(config) {
@@ -99,6 +102,17 @@ function normalizeAuthConfig(config) {
99
102
  ? settings.log.retentionDays
100
103
  : undefined;
101
104
  const logEnabled = typeof settings.log?.enabled === 'boolean' ? settings.log.enabled : undefined;
105
+ const hasBinSettings = settings.bin?.docker ||
106
+ settings.bin?.caddy ||
107
+ settings.bin?.git ||
108
+ settings.bin?.nginx ||
109
+ settings.bin?.pnpm ||
110
+ settings.bin?.yarn;
111
+ const hasProxySettings = settings.proxy?.nbCliRoot ||
112
+ settings.proxy?.caddyDriver ||
113
+ settings.proxy?.nginxDriver ||
114
+ settings.proxy?.upstreamHost ||
115
+ settings.proxy?.host;
102
116
  return {
103
117
  name: config.name || config.dockerResourcePrefix,
104
118
  settings: {
@@ -114,31 +128,35 @@ function normalizeAuthConfig(config) {
114
128
  ...(updatePolicy ? { update: { policy: updatePolicy } } : {}),
115
129
  ...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
116
130
  ...(settings.docker?.network || settings.docker?.containerPrefix
131
+ || settings.docker?.nbImageRegistry || settings.docker?.nbImageVariant
117
132
  ? {
118
133
  docker: {
119
134
  ...(settings.docker?.network ? { network: normalizeOptionalString(settings.docker.network) } : {}),
120
135
  ...(settings.docker?.containerPrefix
121
136
  ? { containerPrefix: normalizeOptionalString(settings.docker.containerPrefix) }
122
137
  : {}),
138
+ ...(settings.docker?.nbImageRegistry
139
+ ? { nbImageRegistry: normalizeOptionalString(settings.docker.nbImageRegistry) }
140
+ : {}),
141
+ ...(settings.docker?.nbImageVariant
142
+ ? { nbImageVariant: normalizeOptionalString(settings.docker.nbImageVariant) }
143
+ : {}),
123
144
  },
124
145
  }
125
146
  : {}),
126
- ...(settings.bin?.docker || settings.bin?.caddy || settings.bin?.git || settings.bin?.nginx || settings.bin?.yarn
147
+ ...(hasBinSettings
127
148
  ? {
128
149
  bin: {
129
150
  ...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
130
151
  ...(settings.bin?.caddy ? { caddy: normalizeOptionalString(settings.bin.caddy) } : {}),
131
152
  ...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
132
153
  ...(settings.bin?.nginx ? { nginx: normalizeOptionalString(settings.bin.nginx) } : {}),
154
+ ...(settings.bin?.pnpm ? { pnpm: normalizeOptionalString(settings.bin.pnpm) } : {}),
133
155
  ...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
134
156
  },
135
157
  }
136
158
  : {}),
137
- ...(settings.proxy?.nbCliRoot ||
138
- settings.proxy?.caddyDriver ||
139
- settings.proxy?.nginxDriver ||
140
- settings.proxy?.upstreamHost ||
141
- settings.proxy?.host
159
+ ...(hasProxySettings
142
160
  ? {
143
161
  proxy: {
144
162
  ...(settings.proxy?.nbCliRoot ? { nbCliRoot: normalizeOptionalString(settings.proxy.nbCliRoot) } : {}),
@@ -501,6 +519,64 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
501
519
  };
502
520
  await saveAuthConfig(config, options);
503
521
  }
522
+ export function resolveEnvProxyEntry(config, provider) {
523
+ const proxy = normalizeEnvProxyConfig(config?.proxy);
524
+ const resolved = {
525
+ ...(proxy?.host ? { host: proxy.host } : {}),
526
+ ...(proxy?.port !== undefined ? { port: proxy.port } : {}),
527
+ ...((provider === 'nginx' ? proxy?.nginx : proxy?.caddy) ?? {}),
528
+ };
529
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
530
+ }
531
+ export async function setEnvProxyEntry(envName, provider, entry, options = {}) {
532
+ await writeEnv(envName, (previous) => {
533
+ const currentProxy = normalizeEnvProxyConfig(previous?.proxy) ?? {};
534
+ const nextEntry = normalizeEnvProxyProviderConfig(entry);
535
+ const nextProxy = { ...currentProxy };
536
+ if (nextEntry && 'host' in nextEntry) {
537
+ const host = normalizeOptionalString(nextEntry.host);
538
+ if (host) {
539
+ nextProxy.host = host;
540
+ }
541
+ else {
542
+ delete nextProxy.host;
543
+ }
544
+ }
545
+ if (nextEntry && 'port' in nextEntry) {
546
+ const portValue = nextEntry.port;
547
+ const port = typeof portValue === 'number' && Number.isInteger(portValue) && portValue >= 1 && portValue <= 65535
548
+ ? portValue
549
+ : undefined;
550
+ if (port !== undefined) {
551
+ nextProxy.port = port;
552
+ }
553
+ else {
554
+ delete nextProxy.port;
555
+ }
556
+ }
557
+ const providerConfig = nextEntry && Object.keys(nextEntry).some((key) => key !== 'host' && key !== 'port')
558
+ ? Object.fromEntries(Object.entries(nextEntry).filter(([key]) => key !== 'host' && key !== 'port'))
559
+ : undefined;
560
+ if (provider === 'nginx') {
561
+ if (providerConfig && Object.keys(providerConfig).length > 0) {
562
+ nextProxy.nginx = providerConfig;
563
+ }
564
+ else {
565
+ delete nextProxy.nginx;
566
+ }
567
+ }
568
+ else if (providerConfig && Object.keys(providerConfig).length > 0) {
569
+ nextProxy.caddy = providerConfig;
570
+ }
571
+ else {
572
+ delete nextProxy.caddy;
573
+ }
574
+ return {
575
+ ...previous,
576
+ proxy: nextProxy,
577
+ };
578
+ }, options);
579
+ }
504
580
  export async function clearEnvRootSetup(envName, options = {}) {
505
581
  const config = await loadExactAuthConfig(options);
506
582
  const current = config.envs[envName];