@nocobase/cli 2.2.0-beta.8 → 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.
@@ -9,14 +9,17 @@
9
9
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
10
  import path from 'node:path';
11
11
  import { dockerContainerExists, dockerContainerIsRunning, startDockerContainer, stopDockerContainer, } from './app-runtime.js';
12
+ import { loadAuthConfig } from './auth-store.js';
12
13
  import { CADDY_PROXY_DRIVER_OPTIONS, DEFAULT_CADDY_PROXY_DRIVER, getCliConfigValue, normalizeCaddyProxyDriver, resolveDockerContainerPrefix, setCliConfigValue, } from './cli-config.js';
13
14
  import { resolveCliHomeRoot } from './cli-home.js';
14
- import { applyEnvProxyAppEntryOptions, buildEnvProxyCaddyBundle, buildEnvProxyMainConfig, mapProxyPathFromCliRoot, resolveEnvProxyMainOutputPath, } from './env-proxy.js';
15
- import { run } from './run-npm.js';
15
+ import { applyEnvProxyAppEntryOptions, buildManualEnvProxyCaddyBundle, buildEnvProxyCaddyBundle, buildEnvProxyMainConfig, mapProxyPathFromCliRoot, resolveEnvProxyMainOutputPath, } from './env-proxy.js';
16
+ import { normalizeEnvProxyConfig } from './env-proxy-config.js';
17
+ import { commandOutput, run } from './run-npm.js';
16
18
  const DOCKER_CADDY_PROXY_CONTAINER_SUFFIX = 'caddy-proxy';
17
19
  const DOCKER_CADDY_PROXY_IMAGE = 'caddy:latest';
18
20
  const DOCKER_CADDY_PROXY_RUNTIME_ROOT = '/apps';
19
21
  const DOCKER_CADDY_PROXY_CONF_DESTINATION = '/etc/caddy/Caddyfile';
22
+ const DEFAULT_DOCKER_CADDY_PROXY_PUBLISHED_PORTS = [80, 443];
20
23
  async function readOptionalTextFile(filePath) {
21
24
  try {
22
25
  return await readFile(filePath, 'utf8');
@@ -62,8 +65,9 @@ export async function resolveCaddyProxyRuntimeContext(options) {
62
65
  upstreamHost: resolveCaddyProxyUpstreamHost(driver),
63
66
  };
64
67
  }
65
- export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext) {
68
+ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext, options) {
66
69
  const bundle = await buildEnvProxyCaddyBundle(runtime, {
70
+ cdnBaseUrl: options?.cdnBaseUrl,
67
71
  runtimeCliRoot: runtimeContext.runtimeCliRoot,
68
72
  upstreamHost: runtimeContext.upstreamHost,
69
73
  });
@@ -82,6 +86,27 @@ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeCon
82
86
  status,
83
87
  };
84
88
  }
89
+ export async function writeManualCaddyProxyBundle(input, appEntryOptions, runtimeContext, options) {
90
+ const bundle = await buildManualEnvProxyCaddyBundle(input, {
91
+ cdnBaseUrl: options?.cdnBaseUrl,
92
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
93
+ upstreamHost: input.upstreamHost || runtimeContext.upstreamHost,
94
+ });
95
+ const currentAppConfigContent = await readOptionalTextFile(bundle.appConfigPath);
96
+ const nextAppConfigContent = applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'caddy', appEntryOptions);
97
+ const status = currentAppConfigContent ? 'updated' : 'created';
98
+ await Promise.all([mkdir(bundle.entryDir, { recursive: true }), mkdir(bundle.publicDir, { recursive: true })]);
99
+ await Promise.all([
100
+ writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
101
+ writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
102
+ writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
103
+ writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
104
+ ]);
105
+ return {
106
+ bundle,
107
+ status,
108
+ };
109
+ }
85
110
  function resolveLocalCaddyPidFilePath() {
86
111
  return path.join(path.dirname(resolveEnvProxyMainOutputPath({ provider: 'caddy' })), 'caddy.pid');
87
112
  }
@@ -149,11 +174,16 @@ async function reloadLocalCaddyProxy(runtimeContext) {
149
174
  }
150
175
  async function ensureDockerCaddyProxyContainer(runtimeContext) {
151
176
  const containerName = await resolveCaddyProxyContainerName();
177
+ const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
178
+ const publishedPorts = await resolveDockerCaddyPublishedPorts();
152
179
  if (await dockerContainerExists(containerName)) {
153
- return;
180
+ if (await dockerCaddyProxyContainerMatchesPublishedPorts(containerName, publishedPorts)) {
181
+ return;
182
+ }
183
+ await removeDockerCaddyProxyContainer(containerName);
154
184
  }
155
185
  const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
156
- const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
186
+ const dockerPortArgs = publishedPorts.flatMap((port) => ['-p', `${port}:${port}`]);
157
187
  await run('docker', [
158
188
  'run',
159
189
  '-d',
@@ -161,8 +191,7 @@ async function ensureDockerCaddyProxyContainer(runtimeContext) {
161
191
  containerName,
162
192
  '--add-host',
163
193
  'host.docker.internal:host-gateway',
164
- '-p',
165
- '80:80',
194
+ ...dockerPortArgs,
166
195
  '-v',
167
196
  `${hostCliRoot}:${DOCKER_CADDY_PROXY_RUNTIME_ROOT}`,
168
197
  '-v',
@@ -173,14 +202,53 @@ async function ensureDockerCaddyProxyContainer(runtimeContext) {
173
202
  stdio: 'ignore',
174
203
  });
175
204
  }
205
+ async function resolveDockerCaddyPublishedPorts() {
206
+ const config = await loadAuthConfig();
207
+ const ports = new Set(DEFAULT_DOCKER_CADDY_PROXY_PUBLISHED_PORTS);
208
+ for (const envConfig of Object.values(config.envs)) {
209
+ const port = normalizeEnvProxyConfig(envConfig.proxy)?.port;
210
+ if (port !== undefined) {
211
+ ports.add(port);
212
+ }
213
+ }
214
+ return Array.from(ports).sort((left, right) => left - right);
215
+ }
216
+ async function readDockerCaddyPublishedPorts(containerName) {
217
+ const output = await commandOutput('docker', ['inspect', '--format', '{{json .HostConfig.PortBindings}}', containerName], { errorName: 'docker inspect' });
218
+ const parsed = JSON.parse(output.trim() || '{}');
219
+ const ports = new Set();
220
+ for (const bindings of Object.values(parsed)) {
221
+ if (!Array.isArray(bindings)) {
222
+ continue;
223
+ }
224
+ for (const binding of bindings) {
225
+ const port = Number.parseInt(String(binding?.HostPort ?? '').trim(), 10);
226
+ if (Number.isInteger(port) && port > 0) {
227
+ ports.add(port);
228
+ }
229
+ }
230
+ }
231
+ return Array.from(ports).sort((left, right) => left - right);
232
+ }
233
+ async function dockerCaddyProxyContainerMatchesPublishedPorts(containerName, expectedPorts) {
234
+ const currentPorts = await readDockerCaddyPublishedPorts(containerName);
235
+ return currentPorts.length === expectedPorts.length && currentPorts.every((port, index) => port === expectedPorts[index]);
236
+ }
237
+ async function removeDockerCaddyProxyContainer(containerName) {
238
+ await run('docker', ['rm', '-f', containerName], {
239
+ errorName: 'docker rm',
240
+ stdio: 'ignore',
241
+ });
242
+ }
176
243
  async function startDockerCaddyProxy(runtimeContext) {
177
244
  const containerName = await resolveCaddyProxyContainerName();
178
245
  await ensureCaddyProxyMainConfig(runtimeContext);
179
- if (await dockerContainerExists(containerName)) {
246
+ const existedBeforeEnsure = await dockerContainerExists(containerName);
247
+ await ensureDockerCaddyProxyContainer(runtimeContext);
248
+ if (existedBeforeEnsure && (await dockerContainerExists(containerName))) {
180
249
  const state = await startDockerContainer(containerName, { stdio: 'ignore' });
181
250
  return state === 'already-running' ? 'already-running' : 'started';
182
251
  }
183
- await ensureDockerCaddyProxyContainer(runtimeContext);
184
252
  return 'started';
185
253
  }
186
254
  async function stopDockerCaddyProxy() {
@@ -9,14 +9,17 @@
9
9
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
10
  import path from 'node:path';
11
11
  import { dockerContainerExists, dockerContainerIsRunning, startDockerContainer, stopDockerContainer, } from './app-runtime.js';
12
+ import { loadAuthConfig } from './auth-store.js';
12
13
  import { DEFAULT_NGINX_PROXY_DRIVER, getCliConfigValue, NGINX_PROXY_DRIVER_OPTIONS, normalizeNginxProxyDriver, resolveDockerContainerPrefix, setCliConfigValue, } from './cli-config.js';
13
14
  import { resolveCliHomeRoot } from './cli-home.js';
14
- import { applyEnvProxyAppEntryOptions, appConfigHasManagedNginxBlock, buildEnvProxyMainConfig, buildEnvProxyNginxBundle, extractManagedNginxConfigBlock, installEnvProxyProvider, mapProxyPathFromCliRoot, reloadEnvProxyProvider, resolveEnvProxyMainOutputPath, replaceManagedNginxConfigBlock, syncEnvProxyNginxSnippets, } from './env-proxy.js';
15
- import { run } from './run-npm.js';
15
+ import { applyEnvProxyAppEntryOptions, appConfigHasManagedNginxBlock, buildManualEnvProxyNginxBundle, buildEnvProxyMainConfig, buildEnvProxyNginxBundle, extractManagedNginxConfigBlock, installEnvProxyProvider, mapProxyPathFromCliRoot, reloadEnvProxyProvider, resolveEnvProxyMainOutputPath, replaceManagedNginxConfigBlock, syncEnvProxyNginxSnippets, } from './env-proxy.js';
16
+ import { normalizeEnvProxyConfig } from './env-proxy-config.js';
17
+ import { commandOutput, run } from './run-npm.js';
16
18
  const DOCKER_NGINX_PROXY_CONTAINER_SUFFIX = 'nginx-proxy';
17
19
  const DOCKER_NGINX_PROXY_IMAGE = 'nginx:latest';
18
20
  const DOCKER_NGINX_PROXY_RUNTIME_ROOT = '/apps';
19
21
  const DOCKER_NGINX_PROXY_CONF_DESTINATION = '/etc/nginx/conf.d/default.conf';
22
+ const DEFAULT_DOCKER_NGINX_PROXY_PUBLISHED_PORTS = [80, 443];
20
23
  async function readOptionalTextFile(filePath) {
21
24
  try {
22
25
  return await readFile(filePath, 'utf8');
@@ -66,11 +69,23 @@ function buildNginxManagedBlockMissingMessage(appConfigPath) {
66
69
  return (`The editable nginx app entry config at ${appConfigPath} does not contain the NocoBase managed block. ` +
67
70
  'Restore the managed block or delete the file and regenerate the proxy config.');
68
71
  }
69
- export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext) {
72
+ export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext, options) {
70
73
  const bundle = await buildEnvProxyNginxBundle(runtime, {
74
+ cdnBaseUrl: options?.cdnBaseUrl,
71
75
  runtimeCliRoot: runtimeContext.runtimeCliRoot,
72
76
  upstreamHost: runtimeContext.upstreamHost,
73
77
  });
78
+ return await writeResolvedNginxProxyBundle(bundle, appEntryOptions, options);
79
+ }
80
+ export async function writeManualNginxProxyBundle(input, appEntryOptions, runtimeContext, options) {
81
+ const bundle = await buildManualEnvProxyNginxBundle(input, {
82
+ cdnBaseUrl: options?.cdnBaseUrl,
83
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
84
+ upstreamHost: runtimeContext.upstreamHost,
85
+ });
86
+ return await writeResolvedNginxProxyBundle(bundle, appEntryOptions, options);
87
+ }
88
+ async function writeResolvedNginxProxyBundle(bundle, appEntryOptions, options) {
74
89
  const managedConfigBlock = extractManagedNginxConfigBlock(bundle.appConfigContent);
75
90
  if (!managedConfigBlock) {
76
91
  throw new Error('Failed to render the managed nginx config block.');
@@ -79,10 +94,12 @@ export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeCon
79
94
  let nextAppConfigContent = applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'nginx', appEntryOptions);
80
95
  let status = 'created';
81
96
  if (currentAppConfigContent) {
82
- if (!appConfigHasManagedNginxBlock(currentAppConfigContent)) {
97
+ if (!appConfigHasManagedNginxBlock(currentAppConfigContent) && !options?.force) {
83
98
  throw new Error(buildNginxManagedBlockMissingMessage(bundle.appConfigPath));
84
99
  }
85
- nextAppConfigContent = applyEnvProxyAppEntryOptions(replaceManagedNginxConfigBlock(currentAppConfigContent, managedConfigBlock), 'nginx', appEntryOptions);
100
+ nextAppConfigContent = appConfigHasManagedNginxBlock(currentAppConfigContent)
101
+ ? applyEnvProxyAppEntryOptions(replaceManagedNginxConfigBlock(currentAppConfigContent, managedConfigBlock), 'nginx', appEntryOptions)
102
+ : applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'nginx', appEntryOptions);
86
103
  status = 'updated';
87
104
  }
88
105
  await Promise.all([mkdir(bundle.entryDir, { recursive: true }), mkdir(bundle.publicDir, { recursive: true })]);
@@ -217,11 +234,16 @@ async function reloadLocalNginxProxy(runtimeContext) {
217
234
  }
218
235
  async function ensureDockerNginxProxyContainer(runtimeContext) {
219
236
  const containerName = await resolveNginxProxyContainerName();
237
+ const mainConfigPath = await ensureNginxProxyMainConfig(runtimeContext);
238
+ const publishedPorts = await resolveDockerNginxPublishedPorts();
220
239
  if (await dockerContainerExists(containerName)) {
221
- return;
240
+ if (await dockerNginxProxyContainerMatchesPublishedPorts(containerName, publishedPorts)) {
241
+ return;
242
+ }
243
+ await removeDockerNginxProxyContainer(containerName);
222
244
  }
223
245
  const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
224
- const mainConfigPath = await ensureNginxProxyMainConfig(runtimeContext);
246
+ const dockerPortArgs = publishedPorts.flatMap((port) => ['-p', `${port}:${port}`]);
225
247
  await run('docker', [
226
248
  'run',
227
249
  '-d',
@@ -229,8 +251,7 @@ async function ensureDockerNginxProxyContainer(runtimeContext) {
229
251
  containerName,
230
252
  '--add-host',
231
253
  'host.docker.internal:host-gateway',
232
- '-p',
233
- '80:80',
254
+ ...dockerPortArgs,
234
255
  '-v',
235
256
  `${hostCliRoot}:${DOCKER_NGINX_PROXY_RUNTIME_ROOT}`,
236
257
  '-v',
@@ -241,14 +262,53 @@ async function ensureDockerNginxProxyContainer(runtimeContext) {
241
262
  stdio: 'ignore',
242
263
  });
243
264
  }
265
+ async function resolveDockerNginxPublishedPorts() {
266
+ const config = await loadAuthConfig();
267
+ const ports = new Set(DEFAULT_DOCKER_NGINX_PROXY_PUBLISHED_PORTS);
268
+ for (const envConfig of Object.values(config.envs)) {
269
+ const port = normalizeEnvProxyConfig(envConfig.proxy)?.port;
270
+ if (port !== undefined) {
271
+ ports.add(port);
272
+ }
273
+ }
274
+ return Array.from(ports).sort((left, right) => left - right);
275
+ }
276
+ async function readDockerNginxPublishedPorts(containerName) {
277
+ const output = await commandOutput('docker', ['inspect', '--format', '{{json .HostConfig.PortBindings}}', containerName], { errorName: 'docker inspect' });
278
+ const parsed = JSON.parse(output.trim() || '{}');
279
+ const ports = new Set();
280
+ for (const bindings of Object.values(parsed)) {
281
+ if (!Array.isArray(bindings)) {
282
+ continue;
283
+ }
284
+ for (const binding of bindings) {
285
+ const port = Number.parseInt(String(binding?.HostPort ?? '').trim(), 10);
286
+ if (Number.isInteger(port) && port > 0) {
287
+ ports.add(port);
288
+ }
289
+ }
290
+ }
291
+ return Array.from(ports).sort((left, right) => left - right);
292
+ }
293
+ async function dockerNginxProxyContainerMatchesPublishedPorts(containerName, expectedPorts) {
294
+ const currentPorts = await readDockerNginxPublishedPorts(containerName);
295
+ return currentPorts.length === expectedPorts.length && currentPorts.every((port, index) => port === expectedPorts[index]);
296
+ }
297
+ async function removeDockerNginxProxyContainer(containerName) {
298
+ await run('docker', ['rm', '-f', containerName], {
299
+ errorName: 'docker rm',
300
+ stdio: 'ignore',
301
+ });
302
+ }
244
303
  async function startDockerNginxProxy(runtimeContext) {
245
304
  const containerName = await resolveNginxProxyContainerName();
246
305
  await ensureNginxProxyMainConfig(runtimeContext);
247
- if (await dockerContainerExists(containerName)) {
306
+ const existedBeforeEnsure = await dockerContainerExists(containerName);
307
+ await ensureDockerNginxProxyContainer(runtimeContext);
308
+ if (existedBeforeEnsure && (await dockerContainerExists(containerName))) {
248
309
  const state = await startDockerContainer(containerName, { stdio: 'ignore' });
249
310
  return state === 'already-running' ? 'already-running' : 'started';
250
311
  }
251
- await ensureDockerNginxProxyContainer(runtimeContext);
252
312
  return 'started';
253
313
  }
254
314
  async function stopDockerNginxProxy() {
@@ -263,7 +263,7 @@
263
263
  "version": {
264
264
  "message": "Which version would you like to use?",
265
265
  "latestLabel": "latest",
266
- "latestHint": "Stable release. Best for production use and the most predictable experience. Currently unavailable.",
266
+ "latestHint": "Stable release. Best for production use and the most predictable experience.",
267
267
  "betaLabel": "beta",
268
268
  "betaHint": "Preview release. Good for trying upcoming features before general release.",
269
269
  "alphaLabel": "alpha",
@@ -263,7 +263,7 @@
263
263
  "version": {
264
264
  "message": "你想使用哪个版本?",
265
265
  "latestLabel": "latest",
266
- "latestHint": "稳定版。适合生产环境和希望获得稳定体验的场景。当前暂不可用。",
266
+ "latestHint": "稳定版。适合生产环境和希望获得稳定体验的场景。",
267
267
  "betaLabel": "beta",
268
268
  "betaHint": "测试版。包含即将发布的新功能,适合提前体验和反馈。",
269
269
  "alphaLabel": "alpha",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.2.0-beta.8",
3
+ "version": "2.2.0-beta.9",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -13,6 +13,7 @@
13
13
  "author": "",
14
14
  "license": "Apache-2.0",
15
15
  "files": [
16
+ "assets",
16
17
  "bin",
17
18
  "dist",
18
19
  "nocobase-ctl.config.json"
@@ -143,5 +144,5 @@
143
144
  "type": "git",
144
145
  "url": "git+https://github.com/nocobase/nocobase.git"
145
146
  },
146
- "gitHead": "fa2502c1e9faf6d74b3f51b42dbc6546638d46af"
147
+ "gitHead": "60e3d7abbaa0c7cead76f71a4f3d5eedb6b8acdb"
147
148
  }