@nocobase/cli 2.1.0-beta.46 → 2.1.0-beta.47

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/dist/commands/app/destroy.js +3 -3
  2. package/dist/commands/proxy/caddy/current.js +17 -0
  3. package/dist/commands/proxy/caddy/generate.js +69 -0
  4. package/dist/commands/proxy/caddy/index.js +28 -0
  5. package/dist/commands/proxy/caddy/info.js +31 -0
  6. package/dist/commands/proxy/caddy/reload.js +28 -0
  7. package/dist/commands/proxy/caddy/restart.js +28 -0
  8. package/dist/commands/proxy/caddy/start.js +30 -0
  9. package/dist/commands/proxy/caddy/status.js +19 -0
  10. package/dist/commands/proxy/caddy/stop.js +30 -0
  11. package/dist/commands/proxy/caddy/use.js +26 -0
  12. package/dist/commands/proxy/index.js +28 -0
  13. package/dist/commands/proxy/nginx/current.js +18 -0
  14. package/dist/commands/proxy/nginx/generate.js +68 -0
  15. package/dist/commands/proxy/nginx/index.js +28 -0
  16. package/dist/commands/proxy/nginx/info.js +34 -0
  17. package/dist/commands/proxy/nginx/reload.js +28 -0
  18. package/dist/commands/proxy/nginx/restart.js +28 -0
  19. package/dist/commands/proxy/nginx/start.js +30 -0
  20. package/dist/commands/proxy/nginx/status.js +19 -0
  21. package/dist/commands/proxy/nginx/stop.js +30 -0
  22. package/dist/commands/proxy/nginx/use.js +31 -0
  23. package/dist/commands/revision/create.js +31 -2
  24. package/dist/lib/auth-store.js +8 -0
  25. package/dist/lib/cli-config.js +73 -1
  26. package/dist/lib/env-proxy.js +105 -75
  27. package/dist/lib/proxy-caddy.js +274 -0
  28. package/dist/lib/proxy-nginx.js +330 -0
  29. package/dist/locale/en-US.json +2 -2
  30. package/dist/locale/zh-CN.json +2 -2
  31. package/package.json +2 -2
  32. package/dist/commands/env/proxy/caddy.js +0 -28
  33. package/dist/commands/env/proxy/index.js +0 -353
  34. package/dist/commands/env/proxy/nginx.js +0 -28
@@ -0,0 +1,274 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { dockerContainerExists, dockerContainerIsRunning, startDockerContainer, stopDockerContainer, } from './app-runtime.js';
12
+ import { CADDY_PROXY_DRIVER_OPTIONS, DEFAULT_CADDY_PROXY_DRIVER, getCliConfigValue, normalizeCaddyProxyDriver, resolveDockerContainerPrefix, setCliConfigValue, } from './cli-config.js';
13
+ import { resolveCliHomeRoot } from './cli-home.js';
14
+ import { applyEnvProxyAppEntryOptions, buildEnvProxyCaddyBundle, buildEnvProxyMainConfig, mapProxyPathFromCliRoot, resolveEnvProxyMainOutputPath, } from './env-proxy.js';
15
+ import { run } from './run-npm.js';
16
+ const DOCKER_CADDY_PROXY_CONTAINER_SUFFIX = 'caddy-proxy';
17
+ const DOCKER_CADDY_PROXY_IMAGE = 'caddy:latest';
18
+ const DOCKER_CADDY_PROXY_RUNTIME_ROOT = '/apps';
19
+ const DOCKER_CADDY_PROXY_CONF_DESTINATION = '/etc/caddy/Caddyfile';
20
+ async function readOptionalTextFile(filePath) {
21
+ try {
22
+ return await readFile(filePath, 'utf8');
23
+ }
24
+ catch (error) {
25
+ const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
26
+ if (code === 'ENOENT') {
27
+ return undefined;
28
+ }
29
+ throw error;
30
+ }
31
+ }
32
+ export function isCaddyProxyDriver(value) {
33
+ return CADDY_PROXY_DRIVER_OPTIONS.includes(value);
34
+ }
35
+ export async function getCaddyProxyDriver() {
36
+ const configured = await getCliConfigValue('proxy.caddy-driver');
37
+ return normalizeCaddyProxyDriver(configured) ?? DEFAULT_CADDY_PROXY_DRIVER;
38
+ }
39
+ export async function setCaddyProxyDriver(driver) {
40
+ const normalized = await setCliConfigValue('proxy.caddy-driver', driver);
41
+ return normalizeCaddyProxyDriver(normalized) ?? DEFAULT_CADDY_PROXY_DRIVER;
42
+ }
43
+ export async function resolveCaddyProxyContainerName() {
44
+ const prefix = await resolveDockerContainerPrefix();
45
+ return `${prefix}-${DOCKER_CADDY_PROXY_CONTAINER_SUFFIX}`;
46
+ }
47
+ export function resolveCaddyProxyImage() {
48
+ return DOCKER_CADDY_PROXY_IMAGE;
49
+ }
50
+ export function resolveCaddyProxyRuntimeRoot(driver, cliRoot) {
51
+ return driver === 'docker' ? DOCKER_CADDY_PROXY_RUNTIME_ROOT : cliRoot;
52
+ }
53
+ export function resolveCaddyProxyUpstreamHost(driver) {
54
+ return driver === 'docker' ? 'host.docker.internal' : '127.0.0.1';
55
+ }
56
+ export async function resolveCaddyProxyRuntimeContext(options) {
57
+ const driver = await getCaddyProxyDriver();
58
+ const cliRoot = String(options?.cliRoot ?? process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
59
+ return {
60
+ driver,
61
+ runtimeCliRoot: resolveCaddyProxyRuntimeRoot(driver, cliRoot),
62
+ upstreamHost: resolveCaddyProxyUpstreamHost(driver),
63
+ };
64
+ }
65
+ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext) {
66
+ const bundle = await buildEnvProxyCaddyBundle(runtime, {
67
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
68
+ upstreamHost: runtimeContext.upstreamHost,
69
+ });
70
+ const currentAppConfigContent = await readOptionalTextFile(bundle.appConfigPath);
71
+ const nextAppConfigContent = applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'caddy', appEntryOptions);
72
+ const status = currentAppConfigContent ? 'updated' : 'created';
73
+ await Promise.all([mkdir(bundle.entryDir, { recursive: true }), mkdir(bundle.publicDir, { recursive: true })]);
74
+ await Promise.all([
75
+ writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
76
+ writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
77
+ writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
78
+ writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
79
+ ]);
80
+ return {
81
+ bundle,
82
+ status,
83
+ };
84
+ }
85
+ function resolveLocalCaddyPidFilePath() {
86
+ return path.join(path.dirname(resolveEnvProxyMainOutputPath({ provider: 'caddy' })), 'caddy.pid');
87
+ }
88
+ async function isLocalCaddyRunning() {
89
+ const pidText = await readOptionalTextFile(resolveLocalCaddyPidFilePath());
90
+ const pid = Number.parseInt(String(pidText ?? '').trim(), 10);
91
+ if (!Number.isInteger(pid) || pid <= 0) {
92
+ return false;
93
+ }
94
+ try {
95
+ process.kill(pid, 0);
96
+ return true;
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ async function ensureCaddyProxyMainConfig(runtimeContext) {
103
+ const mainConfigPath = resolveEnvProxyMainOutputPath({ provider: 'caddy' });
104
+ const mainConfigContent = await buildEnvProxyMainConfig({
105
+ provider: 'caddy',
106
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
107
+ });
108
+ await mkdir(path.dirname(mainConfigPath), { recursive: true });
109
+ await writeFile(mainConfigPath, mainConfigContent, 'utf8');
110
+ return mainConfigPath;
111
+ }
112
+ async function startLocalCaddyProxy(runtimeContext) {
113
+ const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
114
+ const pidFilePath = resolveLocalCaddyPidFilePath();
115
+ if (await isLocalCaddyRunning()) {
116
+ return 'already-running';
117
+ }
118
+ await run('caddy', ['start', '--config', mainConfigPath, '--adapter', 'caddyfile', '--pidfile', pidFilePath], {
119
+ errorName: 'caddy start',
120
+ stdio: 'ignore',
121
+ });
122
+ return 'started';
123
+ }
124
+ async function stopLocalCaddyProxy() {
125
+ const pidFilePath = resolveLocalCaddyPidFilePath();
126
+ const pidText = await readOptionalTextFile(pidFilePath);
127
+ const pid = Number.parseInt(String(pidText ?? '').trim(), 10);
128
+ if (!Number.isInteger(pid) || pid <= 0) {
129
+ return 'already-stopped';
130
+ }
131
+ try {
132
+ process.kill(pid, 'SIGTERM');
133
+ return 'stopped';
134
+ }
135
+ catch {
136
+ return 'already-stopped';
137
+ }
138
+ }
139
+ async function reloadLocalCaddyProxy(runtimeContext) {
140
+ const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
141
+ if (!(await isLocalCaddyRunning())) {
142
+ throw new Error('Local caddy proxy is not running. Start it first with `nb proxy caddy start`.');
143
+ }
144
+ await run('caddy', ['reload', '--config', mainConfigPath, '--adapter', 'caddyfile'], {
145
+ errorName: 'caddy reload',
146
+ stdio: 'ignore',
147
+ });
148
+ return 'reloaded';
149
+ }
150
+ async function ensureDockerCaddyProxyContainer(runtimeContext) {
151
+ const containerName = await resolveCaddyProxyContainerName();
152
+ if (await dockerContainerExists(containerName)) {
153
+ return;
154
+ }
155
+ const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
156
+ const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
157
+ await run('docker', [
158
+ 'run',
159
+ '-d',
160
+ '--name',
161
+ containerName,
162
+ '--add-host',
163
+ 'host.docker.internal:host-gateway',
164
+ '-p',
165
+ '80:80',
166
+ '-v',
167
+ `${hostCliRoot}:${DOCKER_CADDY_PROXY_RUNTIME_ROOT}`,
168
+ '-v',
169
+ `${mainConfigPath}:${DOCKER_CADDY_PROXY_CONF_DESTINATION}:ro`,
170
+ DOCKER_CADDY_PROXY_IMAGE,
171
+ ], {
172
+ errorName: 'docker run',
173
+ stdio: 'ignore',
174
+ });
175
+ }
176
+ async function startDockerCaddyProxy(runtimeContext) {
177
+ const containerName = await resolveCaddyProxyContainerName();
178
+ await ensureCaddyProxyMainConfig(runtimeContext);
179
+ if (await dockerContainerExists(containerName)) {
180
+ const state = await startDockerContainer(containerName, { stdio: 'ignore' });
181
+ return state === 'already-running' ? 'already-running' : 'started';
182
+ }
183
+ await ensureDockerCaddyProxyContainer(runtimeContext);
184
+ return 'started';
185
+ }
186
+ async function stopDockerCaddyProxy() {
187
+ const containerName = await resolveCaddyProxyContainerName();
188
+ if (!(await dockerContainerExists(containerName))) {
189
+ return 'already-stopped';
190
+ }
191
+ return await stopDockerContainer(containerName, { stdio: 'ignore' });
192
+ }
193
+ async function reloadDockerCaddyProxy(runtimeContext) {
194
+ const containerName = await resolveCaddyProxyContainerName();
195
+ await ensureCaddyProxyMainConfig(runtimeContext);
196
+ if (!(await dockerContainerIsRunning(containerName))) {
197
+ throw new Error('Docker caddy proxy is not running. Start it first with `nb proxy caddy start`.');
198
+ }
199
+ await run('docker', ['exec', containerName, 'caddy', 'reload', '--config', DOCKER_CADDY_PROXY_CONF_DESTINATION, '--adapter', 'caddyfile'], {
200
+ errorName: 'docker exec caddy reload',
201
+ stdio: 'ignore',
202
+ });
203
+ return 'reloaded';
204
+ }
205
+ export async function startCaddyProxy(runtimeContext) {
206
+ return runtimeContext.driver === 'docker'
207
+ ? await startDockerCaddyProxy(runtimeContext)
208
+ : await startLocalCaddyProxy(runtimeContext);
209
+ }
210
+ export async function stopCaddyProxy(runtimeContext) {
211
+ return runtimeContext.driver === 'docker' ? await stopDockerCaddyProxy() : await stopLocalCaddyProxy();
212
+ }
213
+ export async function reloadCaddyProxy(runtimeContext) {
214
+ return runtimeContext.driver === 'docker'
215
+ ? await reloadDockerCaddyProxy(runtimeContext)
216
+ : await reloadLocalCaddyProxy(runtimeContext);
217
+ }
218
+ export async function restartCaddyProxy(runtimeContext) {
219
+ await stopCaddyProxy(runtimeContext);
220
+ await startCaddyProxy(runtimeContext);
221
+ return 'restarted';
222
+ }
223
+ export async function getCaddyProxyStatus(runtimeContext) {
224
+ const configFile = await mapProxyPathFromCliRoot(resolveEnvProxyMainOutputPath({ provider: 'caddy' }), {
225
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
226
+ });
227
+ if (runtimeContext.driver === 'docker') {
228
+ const containerName = await resolveCaddyProxyContainerName();
229
+ return {
230
+ driver: runtimeContext.driver,
231
+ state: (await dockerContainerIsRunning(containerName)) ? 'running' : 'stopped',
232
+ configFile,
233
+ runtimeRoot: runtimeContext.runtimeCliRoot,
234
+ upstreamHost: runtimeContext.upstreamHost,
235
+ containerName,
236
+ image: resolveCaddyProxyImage(),
237
+ };
238
+ }
239
+ return {
240
+ driver: runtimeContext.driver,
241
+ state: (await isLocalCaddyRunning()) ? 'running' : 'stopped',
242
+ configFile,
243
+ runtimeRoot: runtimeContext.runtimeCliRoot,
244
+ upstreamHost: runtimeContext.upstreamHost,
245
+ caddyBinary: await getCliConfigValue('bin.caddy'),
246
+ };
247
+ }
248
+ export function formatCaddyProxyInfoLines(info) {
249
+ const lines = [
250
+ `driver: ${info.driver}`,
251
+ `configFile: ${info.configFile}`,
252
+ `runtimeRoot: ${info.runtimeRoot}`,
253
+ `upstreamHost:${info.upstreamHost}`,
254
+ ];
255
+ if (info.driver === 'local') {
256
+ lines.push(`caddyBin: ${info.caddyBinary}`);
257
+ }
258
+ else {
259
+ lines.push(`container: ${info.containerName}`);
260
+ lines.push(`image: ${info.image}`);
261
+ }
262
+ return lines;
263
+ }
264
+ export function formatCaddyProxyStatusLines(status) {
265
+ const lines = [`driver: ${status.driver}`, `status: ${status.state}`, `config: ${status.configFile}`];
266
+ if (status.driver === 'local') {
267
+ lines.push(`caddy: ${status.caddyBinary}`);
268
+ }
269
+ else {
270
+ lines.push(`container: ${status.containerName}`);
271
+ lines.push(`image: ${status.image}`);
272
+ }
273
+ return lines;
274
+ }
@@ -0,0 +1,330 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { dockerContainerExists, dockerContainerIsRunning, startDockerContainer, stopDockerContainer, } from './app-runtime.js';
12
+ import { DEFAULT_NGINX_PROXY_DRIVER, getCliConfigValue, NGINX_PROXY_DRIVER_OPTIONS, normalizeNginxProxyDriver, resolveDockerContainerPrefix, setCliConfigValue, } from './cli-config.js';
13
+ 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';
16
+ const DOCKER_NGINX_PROXY_CONTAINER_SUFFIX = 'nginx-proxy';
17
+ const DOCKER_NGINX_PROXY_IMAGE = 'nginx:latest';
18
+ const DOCKER_NGINX_PROXY_RUNTIME_ROOT = '/apps';
19
+ const DOCKER_NGINX_PROXY_CONF_DESTINATION = '/etc/nginx/conf.d/default.conf';
20
+ async function readOptionalTextFile(filePath) {
21
+ try {
22
+ return await readFile(filePath, 'utf8');
23
+ }
24
+ catch (error) {
25
+ const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
26
+ if (code === 'ENOENT') {
27
+ return undefined;
28
+ }
29
+ throw error;
30
+ }
31
+ }
32
+ export function isNginxProxyDriver(value) {
33
+ return NGINX_PROXY_DRIVER_OPTIONS.includes(value);
34
+ }
35
+ export async function getNginxProxyDriver() {
36
+ const configured = await getCliConfigValue('proxy.nginx-driver');
37
+ return normalizeNginxProxyDriver(configured) ?? DEFAULT_NGINX_PROXY_DRIVER;
38
+ }
39
+ export async function setNginxProxyDriver(driver) {
40
+ const normalized = await setCliConfigValue('proxy.nginx-driver', driver);
41
+ return normalizeNginxProxyDriver(normalized) ?? DEFAULT_NGINX_PROXY_DRIVER;
42
+ }
43
+ export async function resolveNginxProxyContainerName() {
44
+ const prefix = await resolveDockerContainerPrefix();
45
+ return `${prefix}-${DOCKER_NGINX_PROXY_CONTAINER_SUFFIX}`;
46
+ }
47
+ export function resolveNginxProxyImage() {
48
+ return DOCKER_NGINX_PROXY_IMAGE;
49
+ }
50
+ export function resolveNginxProxyRuntimeRoot(driver, cliRoot) {
51
+ return driver === 'docker' ? DOCKER_NGINX_PROXY_RUNTIME_ROOT : cliRoot;
52
+ }
53
+ export function resolveNginxProxyUpstreamHost(driver) {
54
+ return driver === 'docker' ? 'host.docker.internal' : '127.0.0.1';
55
+ }
56
+ export async function resolveNginxProxyRuntimeContext(options) {
57
+ const driver = await getNginxProxyDriver();
58
+ const cliRoot = String(options?.cliRoot ?? process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
59
+ return {
60
+ driver,
61
+ runtimeCliRoot: resolveNginxProxyRuntimeRoot(driver, cliRoot),
62
+ upstreamHost: resolveNginxProxyUpstreamHost(driver),
63
+ };
64
+ }
65
+ function buildNginxManagedBlockMissingMessage(appConfigPath) {
66
+ return (`The editable nginx app entry config at ${appConfigPath} does not contain the NocoBase managed block. ` +
67
+ 'Restore the managed block or delete the file and regenerate the proxy config.');
68
+ }
69
+ export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext) {
70
+ const bundle = await buildEnvProxyNginxBundle(runtime, {
71
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
72
+ upstreamHost: runtimeContext.upstreamHost,
73
+ });
74
+ const managedConfigBlock = extractManagedNginxConfigBlock(bundle.appConfigContent);
75
+ if (!managedConfigBlock) {
76
+ throw new Error('Failed to render the managed nginx config block.');
77
+ }
78
+ const currentAppConfigContent = await readOptionalTextFile(bundle.appConfigPath);
79
+ let nextAppConfigContent = applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'nginx', appEntryOptions);
80
+ let status = 'created';
81
+ if (currentAppConfigContent) {
82
+ if (!appConfigHasManagedNginxBlock(currentAppConfigContent)) {
83
+ throw new Error(buildNginxManagedBlockMissingMessage(bundle.appConfigPath));
84
+ }
85
+ nextAppConfigContent = applyEnvProxyAppEntryOptions(replaceManagedNginxConfigBlock(currentAppConfigContent, managedConfigBlock), 'nginx', appEntryOptions);
86
+ status = 'updated';
87
+ }
88
+ await Promise.all([mkdir(bundle.entryDir, { recursive: true }), mkdir(bundle.publicDir, { recursive: true })]);
89
+ await Promise.all([
90
+ writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
91
+ writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
92
+ writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
93
+ writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
94
+ syncEnvProxyNginxSnippets(),
95
+ ]);
96
+ return {
97
+ bundle,
98
+ status,
99
+ };
100
+ }
101
+ export function normalizeProxyListenPort(value) {
102
+ const normalized = value?.trim() || undefined;
103
+ if (!normalized || !/^\d+$/.test(normalized)) {
104
+ return undefined;
105
+ }
106
+ const port = Number(normalized);
107
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
108
+ return undefined;
109
+ }
110
+ return normalized;
111
+ }
112
+ export function resolveProxyAppEntryOptions(flags) {
113
+ return {
114
+ host: flags.host?.trim() || undefined,
115
+ port: normalizeProxyListenPort(flags.port?.trim()),
116
+ };
117
+ }
118
+ export function formatNginxProxyInfoLines(info) {
119
+ const lines = [
120
+ `driver: ${info.driver}`,
121
+ `configFile: ${info.configFile}`,
122
+ `snippetsDir: ${info.snippetsDir}`,
123
+ `runtimeRoot: ${info.runtimeRoot}`,
124
+ `upstreamHost:${info.upstreamHost}`,
125
+ ];
126
+ if (info.driver === 'local') {
127
+ lines.push(`nginxBin: ${info.nginxBinary}`);
128
+ }
129
+ else {
130
+ lines.push(`container: ${info.containerName}`);
131
+ lines.push(`image: ${info.image}`);
132
+ }
133
+ return lines;
134
+ }
135
+ function parseNginxPidPathFromVersionOutput(output) {
136
+ const match = output.match(/--pid-path=(?:"([^"]+)"|'([^']+)'|([^\s]+))/);
137
+ const pidPath = String(match?.[1] ?? match?.[2] ?? match?.[3] ?? '').trim();
138
+ if (!pidPath) {
139
+ throw new Error('Failed to detect the nginx pid path from `nginx -V`.');
140
+ }
141
+ return pidPath;
142
+ }
143
+ async function captureNginxVersionOutput() {
144
+ let stdout = '';
145
+ let stderr = '';
146
+ await run('nginx', ['-V'], {
147
+ errorName: 'nginx -V',
148
+ stdio: 'pipe',
149
+ onStdout: (chunk) => {
150
+ stdout += chunk;
151
+ },
152
+ onStderr: (chunk) => {
153
+ stderr += chunk;
154
+ },
155
+ });
156
+ return `${stdout}${stderr}`.trim();
157
+ }
158
+ async function resolveLocalNginxPidPath() {
159
+ return parseNginxPidPathFromVersionOutput(await captureNginxVersionOutput());
160
+ }
161
+ async function isLocalNginxRunning() {
162
+ const pidPath = await resolveLocalNginxPidPath();
163
+ const pidText = await readOptionalTextFile(pidPath);
164
+ const pid = Number.parseInt(String(pidText ?? '').trim(), 10);
165
+ if (!Number.isInteger(pid) || pid <= 0) {
166
+ return false;
167
+ }
168
+ try {
169
+ process.kill(pid, 0);
170
+ return true;
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ }
176
+ async function ensureNginxProxyMainConfig(runtimeContext) {
177
+ const mainConfigPath = resolveEnvProxyMainOutputPath({ provider: 'nginx' });
178
+ const mainConfigContent = await buildEnvProxyMainConfig({
179
+ provider: 'nginx',
180
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
181
+ });
182
+ await mkdir(path.dirname(mainConfigPath), { recursive: true });
183
+ await writeFile(mainConfigPath, mainConfigContent, 'utf8');
184
+ await syncEnvProxyNginxSnippets();
185
+ return mainConfigPath;
186
+ }
187
+ async function startLocalNginxProxy(runtimeContext) {
188
+ await ensureNginxProxyMainConfig(runtimeContext);
189
+ await installEnvProxyProvider('nginx', { runtimeCliRoot: runtimeContext.runtimeCliRoot });
190
+ if (await isLocalNginxRunning()) {
191
+ return 'already-running';
192
+ }
193
+ await run('nginx', [], {
194
+ errorName: 'nginx',
195
+ stdio: 'ignore',
196
+ });
197
+ return 'started';
198
+ }
199
+ async function stopLocalNginxProxy() {
200
+ if (!(await isLocalNginxRunning())) {
201
+ return 'already-stopped';
202
+ }
203
+ await run('nginx', ['-s', 'stop'], {
204
+ errorName: 'nginx -s stop',
205
+ stdio: 'ignore',
206
+ });
207
+ return 'stopped';
208
+ }
209
+ async function reloadLocalNginxProxy(runtimeContext) {
210
+ await ensureNginxProxyMainConfig(runtimeContext);
211
+ await installEnvProxyProvider('nginx', { runtimeCliRoot: runtimeContext.runtimeCliRoot });
212
+ if (!(await isLocalNginxRunning())) {
213
+ throw new Error('Local nginx is not running. Start it first with `nb proxy nginx start`.');
214
+ }
215
+ await reloadEnvProxyProvider('nginx', { runtimeCliRoot: runtimeContext.runtimeCliRoot });
216
+ return 'reloaded';
217
+ }
218
+ async function ensureDockerNginxProxyContainer(runtimeContext) {
219
+ const containerName = await resolveNginxProxyContainerName();
220
+ if (await dockerContainerExists(containerName)) {
221
+ return;
222
+ }
223
+ const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
224
+ const mainConfigPath = await ensureNginxProxyMainConfig(runtimeContext);
225
+ await run('docker', [
226
+ 'run',
227
+ '-d',
228
+ '--name',
229
+ containerName,
230
+ '--add-host',
231
+ 'host.docker.internal:host-gateway',
232
+ '-p',
233
+ '80:80',
234
+ '-v',
235
+ `${hostCliRoot}:${DOCKER_NGINX_PROXY_RUNTIME_ROOT}`,
236
+ '-v',
237
+ `${mainConfigPath}:${DOCKER_NGINX_PROXY_CONF_DESTINATION}:ro`,
238
+ DOCKER_NGINX_PROXY_IMAGE,
239
+ ], {
240
+ errorName: 'docker run',
241
+ stdio: 'ignore',
242
+ });
243
+ }
244
+ async function startDockerNginxProxy(runtimeContext) {
245
+ const containerName = await resolveNginxProxyContainerName();
246
+ await ensureNginxProxyMainConfig(runtimeContext);
247
+ if (await dockerContainerExists(containerName)) {
248
+ const state = await startDockerContainer(containerName, { stdio: 'ignore' });
249
+ return state === 'already-running' ? 'already-running' : 'started';
250
+ }
251
+ await ensureDockerNginxProxyContainer(runtimeContext);
252
+ return 'started';
253
+ }
254
+ async function stopDockerNginxProxy() {
255
+ const containerName = await resolveNginxProxyContainerName();
256
+ if (!(await dockerContainerExists(containerName))) {
257
+ return 'already-stopped';
258
+ }
259
+ return await stopDockerContainer(containerName, { stdio: 'ignore' });
260
+ }
261
+ async function reloadDockerNginxProxy(runtimeContext) {
262
+ const containerName = await resolveNginxProxyContainerName();
263
+ await ensureNginxProxyMainConfig(runtimeContext);
264
+ if (!(await dockerContainerIsRunning(containerName))) {
265
+ throw new Error('Docker nginx proxy is not running. Start it first with `nb proxy nginx start`.');
266
+ }
267
+ await run('docker', ['exec', containerName, 'nginx', '-s', 'reload'], {
268
+ errorName: 'docker exec nginx -s reload',
269
+ stdio: 'ignore',
270
+ });
271
+ return 'reloaded';
272
+ }
273
+ export async function startNginxProxy(runtimeContext) {
274
+ return runtimeContext.driver === 'docker'
275
+ ? await startDockerNginxProxy(runtimeContext)
276
+ : await startLocalNginxProxy(runtimeContext);
277
+ }
278
+ export async function stopNginxProxy(runtimeContext) {
279
+ return runtimeContext.driver === 'docker' ? await stopDockerNginxProxy() : await stopLocalNginxProxy();
280
+ }
281
+ export async function reloadNginxProxy(runtimeContext) {
282
+ return runtimeContext.driver === 'docker'
283
+ ? await reloadDockerNginxProxy(runtimeContext)
284
+ : await reloadLocalNginxProxy(runtimeContext);
285
+ }
286
+ export async function restartNginxProxy(runtimeContext) {
287
+ await stopNginxProxy(runtimeContext);
288
+ await startNginxProxy(runtimeContext);
289
+ return 'restarted';
290
+ }
291
+ export async function getNginxProxyStatus(runtimeContext) {
292
+ const configFile = await mapProxyPathFromCliRoot(resolveEnvProxyMainOutputPath({ provider: 'nginx' }), {
293
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
294
+ });
295
+ if (runtimeContext.driver === 'docker') {
296
+ const containerName = await resolveNginxProxyContainerName();
297
+ return {
298
+ driver: runtimeContext.driver,
299
+ state: (await dockerContainerIsRunning(containerName)) ? 'running' : 'stopped',
300
+ configFile,
301
+ runtimeRoot: runtimeContext.runtimeCliRoot,
302
+ upstreamHost: runtimeContext.upstreamHost,
303
+ containerName,
304
+ image: resolveNginxProxyImage(),
305
+ };
306
+ }
307
+ return {
308
+ driver: runtimeContext.driver,
309
+ state: (await isLocalNginxRunning()) ? 'running' : 'stopped',
310
+ configFile,
311
+ runtimeRoot: runtimeContext.runtimeCliRoot,
312
+ upstreamHost: runtimeContext.upstreamHost,
313
+ nginxBinary: await getCliConfigValue('bin.nginx'),
314
+ };
315
+ }
316
+ export function formatNginxProxyStatusLines(status) {
317
+ const lines = [
318
+ `driver: ${status.driver}`,
319
+ `status: ${status.state}`,
320
+ `config: ${status.configFile}`,
321
+ ];
322
+ if (status.driver === 'local') {
323
+ lines.push(`nginx: ${status.nginxBinary}`);
324
+ }
325
+ else {
326
+ lines.push(`container: ${status.containerName}`);
327
+ lines.push(`image: ${status.image}`);
328
+ }
329
+ return lines;
330
+ }
@@ -149,7 +149,7 @@
149
149
  "appEntryMissingInclude": "The editable app entry config at {{appConfigPath}} does not reference {{generatedConfigPath}}. Add the managed generated-config reference back into the editable app entry and rerun the command.",
150
150
  "nginxOutputUnsupported": "The nginx provider does not support `--output`. It writes `app.conf`, `public/index-v1.html`, `public/index-v2.html`, and `nocobase.conf` into `~/.nocobase/proxy/nginx/<env>/`.",
151
151
  "caddyOutputUnsupported": "The caddy provider does not support `--output`. It writes `app.caddy`, `generated.caddy`, `public/index-v1.html`, `public/index-v2.html`, and `nocobase.caddy` into `~/.nocobase/proxy/caddy/<env>/`.",
152
- "nginxAppEntryMissingManagedBlock": "The editable nginx app entry config at {{appConfigPath}} does not contain the NocoBase managed block. Restore the managed block or delete the file and rerun `nb env proxy`."
152
+ "nginxAppEntryMissingManagedBlock": "The editable nginx app entry config at {{appConfigPath}} does not contain the NocoBase managed block. Restore the managed block or delete the file and rerun `nb proxy nginx generate`."
153
153
  }
154
154
  },
155
155
  "shared": {
@@ -320,7 +320,7 @@
320
320
  "placeholder": "local"
321
321
  },
322
322
  "lang": {
323
- "message": "Which language would you like to use?"
323
+ "message": "Which language would you like to use for the app?"
324
324
  },
325
325
  "appPath": {
326
326
  "message": "Where should this app be stored? (relative to {{root}})",
@@ -149,7 +149,7 @@
149
149
  "appEntryMissingInclude": "可编辑入口配置 {{appConfigPath}} 没有引用 {{generatedConfigPath}}。请把托管的 generated 配置引用补回入口配置中,然后重新执行该命令。",
150
150
  "nginxOutputUnsupported": "nginx provider 不支持 `--output`。它会把 `app.conf`、`public/index-v1.html`、`public/index-v2.html` 和 `nocobase.conf` 写入 `~/.nocobase/proxy/nginx/<env>/`。",
151
151
  "caddyOutputUnsupported": "caddy provider 不支持 `--output`。它会把 `app.caddy`、`generated.caddy`、`public/index-v1.html`、`public/index-v2.html` 和 `nocobase.caddy` 写入 `~/.nocobase/proxy/caddy/<env>/`。",
152
- "nginxAppEntryMissingManagedBlock": "可编辑的 nginx 入口配置 {{appConfigPath}} 不包含 NocoBase 的托管区块。请恢复这个托管区块,或删除该文件后重新执行 `nb env proxy`。"
152
+ "nginxAppEntryMissingManagedBlock": "可编辑的 nginx 入口配置 {{appConfigPath}} 不包含 NocoBase 的托管区块。请恢复这个托管区块,或删除该文件后重新执行 `nb proxy nginx generate`。"
153
153
  }
154
154
  },
155
155
  "shared": {
@@ -320,7 +320,7 @@
320
320
  "placeholder": "local"
321
321
  },
322
322
  "lang": {
323
- "message": "你想使用哪种语言?"
323
+ "message": "你希望应用使用哪种语言?"
324
324
  },
325
325
  "appPath": {
326
326
  "message": "应用要放到哪里?(相对路径基于 {{root}})",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.1.0-beta.46",
3
+ "version": "2.1.0-beta.47",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -138,5 +138,5 @@
138
138
  "type": "git",
139
139
  "url": "git+https://github.com/nocobase/nocobase.git"
140
140
  },
141
- "gitHead": "91fd3ab238a5ff58735bbfca04a8cb05d233b0af"
141
+ "gitHead": "bf8fc3790e3494c901b4d22861c5471a0d27c464"
142
142
  }
@@ -1,28 +0,0 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
- import { Command } from '@oclif/core';
10
- import { createEnvProxyCaddyFlags, ENV_PROXY_NAME_ARG, runEnvProxyCommand } from './index.js';
11
- export default class EnvProxyCaddy extends Command {
12
- static summary = 'Generate Caddy proxy files for one managed env';
13
- static examples = [
14
- '<%= config.bin %> <%= command.id %>',
15
- '<%= config.bin %> <%= command.id %> --env app1',
16
- '<%= config.bin %> <%= command.id %> --env app1 --host a.local.nocobase.com --port 8080',
17
- '<%= config.bin %> <%= command.id %> --env app1 --install --reload',
18
- '<%= config.bin %> <%= command.id %> --env app1 --print',
19
- ];
20
- static args = {
21
- name: ENV_PROXY_NAME_ARG,
22
- };
23
- static flags = createEnvProxyCaddyFlags();
24
- async run() {
25
- const { args, flags } = await this.parse(EnvProxyCaddy);
26
- await runEnvProxyCommand(this, args, flags, 'caddy');
27
- }
28
- }