@nocobase/cli 2.1.0-alpha.46 → 2.1.0-alpha.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.
- package/LICENSE +201 -0
- package/README.md +63 -380
- package/assets/env-proxy/nginx/app.conf.tpl +23 -0
- package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
- package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
- package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
- package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
- package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
- package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
- package/bin/run.js +40 -3
- package/dist/commands/app/autostart/disable.js +55 -0
- package/dist/commands/app/autostart/enable.js +55 -0
- package/dist/commands/app/autostart/list.js +37 -0
- package/dist/commands/app/autostart/run.js +84 -0
- package/dist/commands/app/autostart/shared.js +49 -0
- package/dist/commands/app/destroy.js +21 -12
- package/dist/commands/app/down.js +2 -2
- package/dist/commands/app/logs.js +2 -1
- package/dist/commands/app/restart.js +89 -24
- package/dist/commands/app/shared.js +3 -2
- package/dist/commands/app/start.js +142 -38
- package/dist/commands/app/stop.js +31 -2
- package/dist/commands/app/upgrade.js +9 -5
- package/dist/commands/config/delete.js +6 -1
- package/dist/commands/config/get.js +6 -1
- package/dist/commands/config/set.js +7 -2
- package/dist/commands/env/add.js +24 -44
- package/dist/commands/env/info.js +3 -2
- package/dist/commands/env/proxy/caddy.js +28 -0
- package/dist/commands/env/proxy/index.js +353 -0
- package/dist/commands/env/proxy/nginx.js +28 -0
- package/dist/commands/env/remove.js +112 -22
- package/dist/commands/env/shared.js +17 -9
- package/dist/commands/env/update.js +385 -21
- package/dist/commands/init.js +350 -98
- package/dist/commands/install.js +218 -75
- package/dist/commands/license/activate.js +63 -244
- package/dist/commands/license/plugins/shared.js +64 -13
- package/dist/commands/plugin/import.js +108 -0
- package/dist/commands/revision/create.js +89 -0
- package/dist/commands/source/test.js +13 -14
- package/dist/lib/app-health.js +5 -5
- package/dist/lib/app-managed-resources.js +24 -8
- package/dist/lib/app-public-path.js +80 -0
- package/dist/lib/app-runtime.js +10 -1
- package/dist/lib/auth-store.js +119 -4
- package/dist/lib/cli-config.js +191 -3
- package/dist/lib/cli-entry-error.js +8 -0
- package/dist/lib/command-log.js +284 -0
- package/dist/lib/docker-env-file.js +10 -2
- package/dist/lib/env-auth.js +4 -1
- package/dist/lib/env-command-config.js +45 -0
- package/dist/lib/env-config.js +7 -2
- package/dist/lib/env-guard.js +9 -10
- package/dist/lib/env-paths.js +101 -0
- package/dist/lib/env-proxy.js +1295 -0
- package/dist/lib/inquirer.js +4 -5
- package/dist/lib/managed-env-file.js +98 -0
- package/dist/lib/plugin-import.js +279 -0
- package/dist/lib/prompt-catalog-core.js +3 -2
- package/dist/lib/prompt-validators.js +14 -0
- package/dist/lib/prompt-web-ui.js +8 -1
- package/dist/lib/run-npm.js +56 -12
- package/dist/lib/skills-manager.js +3 -1
- package/dist/lib/source-publish.js +18 -1
- package/dist/lib/ui.js +2 -1
- package/dist/locale/en-US.json +116 -22
- package/dist/locale/zh-CN.json +113 -19
- package/package.json +5 -7
- package/scripts/build.mjs +34 -0
- package/scripts/clean.mjs +9 -0
- package/tsconfig.json +19 -0
- package/LICENSE.txt +0 -107
- package/README.zh-CN.md +0 -355
package/dist/lib/cli-config.js
CHANGED
|
@@ -7,25 +7,40 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { loadExactAuthConfig, saveAuthConfig } from './auth-store.js';
|
|
10
|
-
import { resolveDefaultConfigScope } from './cli-home.js';
|
|
10
|
+
import { resolveCliHomeRoot, resolveDefaultConfigScope } from './cli-home.js';
|
|
11
11
|
import { CLI_LOCALE_FLAG_OPTIONS, normalizeCliLocale, resolveCliLocale } from './cli-locale.js';
|
|
12
12
|
export const DEFAULT_LICENSE_PKG_URL = 'https://pkg.nocobase.com/';
|
|
13
13
|
export const DEFAULT_DOCKER_NETWORK = 'nocobase';
|
|
14
14
|
export const DEFAULT_DOCKER_CONTAINER_PREFIX = 'nb';
|
|
15
15
|
export const DEFAULT_DOCKER_BIN = 'docker';
|
|
16
|
+
export const DEFAULT_CADDY_BIN = 'caddy';
|
|
16
17
|
export const DEFAULT_GIT_BIN = 'git';
|
|
18
|
+
export const DEFAULT_NGINX_BIN = 'nginx';
|
|
19
|
+
export const PROXY_PROVIDER_OPTIONS = ['nginx', 'caddy'];
|
|
20
|
+
export const DEFAULT_PROXY_PROVIDER = 'nginx';
|
|
21
|
+
export const DEFAULT_PROXY_HOST = '127.0.0.1';
|
|
17
22
|
export const DEFAULT_YARN_BIN = 'yarn';
|
|
23
|
+
export const DEFAULT_LOG_RETENTION_DAYS = 14;
|
|
24
|
+
export const DEFAULT_LOG_ENABLED = true;
|
|
18
25
|
export const CLI_UPDATE_POLICY_OPTIONS = ['prompt', 'auto', 'off'];
|
|
19
26
|
export const DEFAULT_UPDATE_POLICY = 'prompt';
|
|
20
27
|
export const SUPPORTED_CLI_CONFIG_KEYS = [
|
|
21
28
|
'locale',
|
|
29
|
+
'default-ui-host',
|
|
30
|
+
'default-api-host',
|
|
22
31
|
'update.policy',
|
|
23
32
|
'license.pkg-url',
|
|
24
33
|
'docker.network',
|
|
25
34
|
'docker.container-prefix',
|
|
26
35
|
'bin.docker',
|
|
36
|
+
'bin.caddy',
|
|
27
37
|
'bin.git',
|
|
38
|
+
'bin.nginx',
|
|
39
|
+
'proxy.nb-cli-root',
|
|
40
|
+
'proxy.upstream-host',
|
|
28
41
|
'bin.yarn',
|
|
42
|
+
'log.enabled',
|
|
43
|
+
'log.retention-days',
|
|
29
44
|
];
|
|
30
45
|
function trimValue(value) {
|
|
31
46
|
const text = String(value ?? '').trim();
|
|
@@ -52,19 +67,33 @@ export function normalizeCliUpdatePolicy(value) {
|
|
|
52
67
|
}
|
|
53
68
|
return CLI_UPDATE_POLICY_OPTIONS.includes(normalized) ? normalized : undefined;
|
|
54
69
|
}
|
|
70
|
+
export function normalizeProxyProvider(value) {
|
|
71
|
+
const normalized = trimValue(value);
|
|
72
|
+
if (!normalized) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return PROXY_PROVIDER_OPTIONS.includes(normalized) ? normalized : undefined;
|
|
76
|
+
}
|
|
55
77
|
function cloneSettings(config) {
|
|
56
78
|
return {
|
|
57
79
|
...(config.settings?.locale ? { locale: trimValue(config.settings.locale) } : {}),
|
|
80
|
+
init: config.settings?.init ? { ...config.settings.init } : undefined,
|
|
58
81
|
update: config.settings?.update ? { ...config.settings.update } : undefined,
|
|
59
82
|
license: config.settings?.license ? { ...config.settings.license } : undefined,
|
|
60
83
|
docker: config.settings?.docker ? { ...config.settings.docker } : undefined,
|
|
61
84
|
bin: config.settings?.bin ? { ...config.settings.bin } : undefined,
|
|
85
|
+
proxy: config.settings?.proxy ? { ...config.settings.proxy } : undefined,
|
|
86
|
+
log: config.settings?.log ? { ...config.settings.log } : undefined,
|
|
62
87
|
};
|
|
63
88
|
}
|
|
64
89
|
function pruneSettings(config) {
|
|
65
90
|
if (config.settings && !trimValue(config.settings.locale)) {
|
|
66
91
|
delete config.settings.locale;
|
|
67
92
|
}
|
|
93
|
+
const init = config.settings?.init;
|
|
94
|
+
if (init && !trimValue(init.defaultUiHost) && !trimValue(init.defaultApiHost)) {
|
|
95
|
+
delete config.settings?.init;
|
|
96
|
+
}
|
|
68
97
|
const update = config.settings?.update;
|
|
69
98
|
if (update && !normalizeCliUpdatePolicy(update.policy)) {
|
|
70
99
|
delete config.settings?.update;
|
|
@@ -78,15 +107,31 @@ function pruneSettings(config) {
|
|
|
78
107
|
delete config.settings?.docker;
|
|
79
108
|
}
|
|
80
109
|
const bin = config.settings?.bin;
|
|
81
|
-
if (bin &&
|
|
110
|
+
if (bin &&
|
|
111
|
+
!trimValue(bin.docker) &&
|
|
112
|
+
!trimValue(bin.caddy) &&
|
|
113
|
+
!trimValue(bin.git) &&
|
|
114
|
+
!trimValue(bin.nginx) &&
|
|
115
|
+
!trimValue(bin.yarn)) {
|
|
82
116
|
delete config.settings?.bin;
|
|
83
117
|
}
|
|
118
|
+
const proxy = config.settings?.proxy;
|
|
119
|
+
if (proxy && !trimValue(proxy.nbCliRoot) && !trimValue(proxy.upstreamHost)) {
|
|
120
|
+
delete config.settings?.proxy;
|
|
121
|
+
}
|
|
122
|
+
const log = config.settings?.log;
|
|
123
|
+
if (log && typeof log.enabled !== 'boolean' && (!Number.isInteger(log.retentionDays) || log.retentionDays < 0)) {
|
|
124
|
+
delete config.settings?.log;
|
|
125
|
+
}
|
|
84
126
|
if (config.settings &&
|
|
85
127
|
!config.settings.locale &&
|
|
128
|
+
!config.settings.init &&
|
|
86
129
|
!config.settings.update &&
|
|
87
130
|
!config.settings.license &&
|
|
88
131
|
!config.settings.docker &&
|
|
89
|
-
!config.settings.bin
|
|
132
|
+
!config.settings.bin &&
|
|
133
|
+
!config.settings.proxy &&
|
|
134
|
+
!config.settings.log) {
|
|
90
135
|
delete config.settings;
|
|
91
136
|
}
|
|
92
137
|
}
|
|
@@ -94,6 +139,10 @@ export function getExplicitCliConfigValue(config, key) {
|
|
|
94
139
|
switch (key) {
|
|
95
140
|
case 'locale':
|
|
96
141
|
return trimValue(config.settings?.locale);
|
|
142
|
+
case 'default-ui-host':
|
|
143
|
+
return trimValue(config.settings?.init?.defaultUiHost);
|
|
144
|
+
case 'default-api-host':
|
|
145
|
+
return trimValue(config.settings?.init?.defaultApiHost);
|
|
97
146
|
case 'update.policy':
|
|
98
147
|
return normalizeCliUpdatePolicy(config.settings?.update?.policy);
|
|
99
148
|
case 'license.pkg-url':
|
|
@@ -104,10 +153,24 @@ export function getExplicitCliConfigValue(config, key) {
|
|
|
104
153
|
return trimValue(config.settings?.docker?.containerPrefix);
|
|
105
154
|
case 'bin.docker':
|
|
106
155
|
return trimValue(config.settings?.bin?.docker);
|
|
156
|
+
case 'bin.caddy':
|
|
157
|
+
return trimValue(config.settings?.bin?.caddy);
|
|
107
158
|
case 'bin.git':
|
|
108
159
|
return trimValue(config.settings?.bin?.git);
|
|
160
|
+
case 'bin.nginx':
|
|
161
|
+
return trimValue(config.settings?.bin?.nginx);
|
|
162
|
+
case 'proxy.nb-cli-root':
|
|
163
|
+
return trimValue(config.settings?.proxy?.nbCliRoot);
|
|
164
|
+
case 'proxy.upstream-host':
|
|
165
|
+
return trimValue(config.settings?.proxy?.upstreamHost);
|
|
109
166
|
case 'bin.yarn':
|
|
110
167
|
return trimValue(config.settings?.bin?.yarn);
|
|
168
|
+
case 'log.enabled':
|
|
169
|
+
return typeof config.settings?.log?.enabled === 'boolean' ? String(config.settings?.log?.enabled) : undefined;
|
|
170
|
+
case 'log.retention-days':
|
|
171
|
+
return Number.isInteger(config.settings?.log?.retentionDays)
|
|
172
|
+
? String(config.settings?.log?.retentionDays)
|
|
173
|
+
: undefined;
|
|
111
174
|
}
|
|
112
175
|
}
|
|
113
176
|
export function getEffectiveCliConfigValue(config, key) {
|
|
@@ -118,6 +181,10 @@ export function getEffectiveCliConfigValue(config, key) {
|
|
|
118
181
|
switch (key) {
|
|
119
182
|
case 'locale':
|
|
120
183
|
return resolveCliLocale(undefined, { configuredLocale: trimValue(config.settings?.locale) });
|
|
184
|
+
case 'default-ui-host':
|
|
185
|
+
return '127.0.0.1';
|
|
186
|
+
case 'default-api-host':
|
|
187
|
+
return '127.0.0.1';
|
|
121
188
|
case 'update.policy':
|
|
122
189
|
return explicit ?? DEFAULT_UPDATE_POLICY;
|
|
123
190
|
case 'license.pkg-url':
|
|
@@ -128,10 +195,22 @@ export function getEffectiveCliConfigValue(config, key) {
|
|
|
128
195
|
return trimValue(config.name) || DEFAULT_DOCKER_CONTAINER_PREFIX;
|
|
129
196
|
case 'bin.docker':
|
|
130
197
|
return DEFAULT_DOCKER_BIN;
|
|
198
|
+
case 'bin.caddy':
|
|
199
|
+
return DEFAULT_CADDY_BIN;
|
|
131
200
|
case 'bin.git':
|
|
132
201
|
return DEFAULT_GIT_BIN;
|
|
202
|
+
case 'bin.nginx':
|
|
203
|
+
return DEFAULT_NGINX_BIN;
|
|
204
|
+
case 'proxy.nb-cli-root':
|
|
205
|
+
return explicit ?? resolveCliHomeRoot();
|
|
206
|
+
case 'proxy.upstream-host':
|
|
207
|
+
return explicit ?? DEFAULT_PROXY_HOST;
|
|
133
208
|
case 'bin.yarn':
|
|
134
209
|
return DEFAULT_YARN_BIN;
|
|
210
|
+
case 'log.enabled':
|
|
211
|
+
return explicit ?? String(DEFAULT_LOG_ENABLED);
|
|
212
|
+
case 'log.retention-days':
|
|
213
|
+
return explicit ?? String(DEFAULT_LOG_RETENTION_DAYS);
|
|
135
214
|
}
|
|
136
215
|
}
|
|
137
216
|
export function normalizeCliConfigValue(key, value) {
|
|
@@ -156,6 +235,19 @@ export function normalizeCliConfigValue(key, value) {
|
|
|
156
235
|
}
|
|
157
236
|
return policy;
|
|
158
237
|
}
|
|
238
|
+
if (key === 'log.retention-days') {
|
|
239
|
+
const retentionDays = Number.parseInt(normalized, 10);
|
|
240
|
+
if (!Number.isInteger(retentionDays) || retentionDays < 0) {
|
|
241
|
+
throw new Error(`Config key "${key}" must be a non-negative integer.`);
|
|
242
|
+
}
|
|
243
|
+
return String(retentionDays);
|
|
244
|
+
}
|
|
245
|
+
if (key === 'log.enabled') {
|
|
246
|
+
if (normalized !== 'true' && normalized !== 'false') {
|
|
247
|
+
throw new Error(`Config key "${key}" must be either "true" or "false".`);
|
|
248
|
+
}
|
|
249
|
+
return normalized;
|
|
250
|
+
}
|
|
159
251
|
return normalized;
|
|
160
252
|
}
|
|
161
253
|
export async function loadCliConfig(options = {}) {
|
|
@@ -185,6 +277,18 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
185
277
|
case 'locale':
|
|
186
278
|
config.settings.locale = normalized;
|
|
187
279
|
break;
|
|
280
|
+
case 'default-ui-host':
|
|
281
|
+
config.settings.init = {
|
|
282
|
+
...(config.settings.init ?? {}),
|
|
283
|
+
defaultUiHost: normalized,
|
|
284
|
+
};
|
|
285
|
+
break;
|
|
286
|
+
case 'default-api-host':
|
|
287
|
+
config.settings.init = {
|
|
288
|
+
...(config.settings.init ?? {}),
|
|
289
|
+
defaultApiHost: normalized,
|
|
290
|
+
};
|
|
291
|
+
break;
|
|
188
292
|
case 'update.policy':
|
|
189
293
|
config.settings.update = {
|
|
190
294
|
...(config.settings.update ?? {}),
|
|
@@ -215,18 +319,54 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
215
319
|
docker: normalized,
|
|
216
320
|
};
|
|
217
321
|
break;
|
|
322
|
+
case 'bin.caddy':
|
|
323
|
+
config.settings.bin = {
|
|
324
|
+
...(config.settings.bin ?? {}),
|
|
325
|
+
caddy: normalized,
|
|
326
|
+
};
|
|
327
|
+
break;
|
|
218
328
|
case 'bin.git':
|
|
219
329
|
config.settings.bin = {
|
|
220
330
|
...(config.settings.bin ?? {}),
|
|
221
331
|
git: normalized,
|
|
222
332
|
};
|
|
223
333
|
break;
|
|
334
|
+
case 'bin.nginx':
|
|
335
|
+
config.settings.bin = {
|
|
336
|
+
...(config.settings.bin ?? {}),
|
|
337
|
+
nginx: normalized,
|
|
338
|
+
};
|
|
339
|
+
break;
|
|
340
|
+
case 'proxy.nb-cli-root':
|
|
341
|
+
config.settings.proxy = {
|
|
342
|
+
...(config.settings.proxy ?? {}),
|
|
343
|
+
nbCliRoot: normalized,
|
|
344
|
+
};
|
|
345
|
+
break;
|
|
346
|
+
case 'proxy.upstream-host':
|
|
347
|
+
config.settings.proxy = {
|
|
348
|
+
...(config.settings.proxy ?? {}),
|
|
349
|
+
upstreamHost: normalized,
|
|
350
|
+
};
|
|
351
|
+
break;
|
|
224
352
|
case 'bin.yarn':
|
|
225
353
|
config.settings.bin = {
|
|
226
354
|
...(config.settings.bin ?? {}),
|
|
227
355
|
yarn: normalized,
|
|
228
356
|
};
|
|
229
357
|
break;
|
|
358
|
+
case 'log.enabled':
|
|
359
|
+
config.settings.log = {
|
|
360
|
+
...(config.settings.log ?? {}),
|
|
361
|
+
enabled: normalized === 'true',
|
|
362
|
+
};
|
|
363
|
+
break;
|
|
364
|
+
case 'log.retention-days':
|
|
365
|
+
config.settings.log = {
|
|
366
|
+
...(config.settings.log ?? {}),
|
|
367
|
+
retentionDays: Number.parseInt(normalized, 10),
|
|
368
|
+
};
|
|
369
|
+
break;
|
|
230
370
|
}
|
|
231
371
|
pruneSettings(config);
|
|
232
372
|
await saveAuthConfig(config, scope);
|
|
@@ -244,6 +384,16 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
244
384
|
case 'locale':
|
|
245
385
|
delete config.settings.locale;
|
|
246
386
|
break;
|
|
387
|
+
case 'default-ui-host':
|
|
388
|
+
if (config.settings.init) {
|
|
389
|
+
delete config.settings.init.defaultUiHost;
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
case 'default-api-host':
|
|
393
|
+
if (config.settings.init) {
|
|
394
|
+
delete config.settings.init.defaultApiHost;
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
247
397
|
case 'update.policy':
|
|
248
398
|
if (config.settings.update) {
|
|
249
399
|
delete config.settings.update.policy;
|
|
@@ -269,16 +419,46 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
269
419
|
delete config.settings.bin.docker;
|
|
270
420
|
}
|
|
271
421
|
break;
|
|
422
|
+
case 'bin.caddy':
|
|
423
|
+
if (config.settings.bin) {
|
|
424
|
+
delete config.settings.bin.caddy;
|
|
425
|
+
}
|
|
426
|
+
break;
|
|
272
427
|
case 'bin.git':
|
|
273
428
|
if (config.settings.bin) {
|
|
274
429
|
delete config.settings.bin.git;
|
|
275
430
|
}
|
|
276
431
|
break;
|
|
432
|
+
case 'bin.nginx':
|
|
433
|
+
if (config.settings.bin) {
|
|
434
|
+
delete config.settings.bin.nginx;
|
|
435
|
+
}
|
|
436
|
+
break;
|
|
437
|
+
case 'proxy.nb-cli-root':
|
|
438
|
+
if (config.settings.proxy) {
|
|
439
|
+
delete config.settings.proxy.nbCliRoot;
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
case 'proxy.upstream-host':
|
|
443
|
+
if (config.settings.proxy) {
|
|
444
|
+
delete config.settings.proxy.upstreamHost;
|
|
445
|
+
}
|
|
446
|
+
break;
|
|
277
447
|
case 'bin.yarn':
|
|
278
448
|
if (config.settings.bin) {
|
|
279
449
|
delete config.settings.bin.yarn;
|
|
280
450
|
}
|
|
281
451
|
break;
|
|
452
|
+
case 'log.enabled':
|
|
453
|
+
if (config.settings.log) {
|
|
454
|
+
delete config.settings.log.enabled;
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
case 'log.retention-days':
|
|
458
|
+
if (config.settings.log) {
|
|
459
|
+
delete config.settings.log.retentionDays;
|
|
460
|
+
}
|
|
461
|
+
break;
|
|
282
462
|
}
|
|
283
463
|
pruneSettings(config);
|
|
284
464
|
await saveAuthConfig(config, scope);
|
|
@@ -287,6 +467,12 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
287
467
|
export async function resolveDockerNetworkName(options = {}) {
|
|
288
468
|
return await getCliConfigValue('docker.network', options);
|
|
289
469
|
}
|
|
470
|
+
export async function resolveDefaultUiHost(options = {}) {
|
|
471
|
+
return await getCliConfigValue('default-ui-host', options);
|
|
472
|
+
}
|
|
473
|
+
export async function resolveDefaultApiHost(options = {}) {
|
|
474
|
+
return await getCliConfigValue('default-api-host', options);
|
|
475
|
+
}
|
|
290
476
|
export async function resolveDockerContainerPrefix(options = {}) {
|
|
291
477
|
return await getCliConfigValue('docker.container-prefix', options);
|
|
292
478
|
}
|
|
@@ -295,7 +481,9 @@ export async function resolveLicensePkgUrlFromConfig(options = {}) {
|
|
|
295
481
|
}
|
|
296
482
|
const CONFIGURABLE_COMMAND_KEYS = {
|
|
297
483
|
docker: 'bin.docker',
|
|
484
|
+
caddy: 'bin.caddy',
|
|
298
485
|
git: 'bin.git',
|
|
486
|
+
nginx: 'bin.nginx',
|
|
299
487
|
yarn: 'bin.yarn',
|
|
300
488
|
};
|
|
301
489
|
export function isConfigurableCommandName(value) {
|
|
@@ -42,3 +42,11 @@ export function formatCliEntryError(error, argv) {
|
|
|
42
42
|
`If \`${attemptedCommand}\` should be a runtime command from your NocoBase app, check whether the connected app exposes it, then retry the command.`,
|
|
43
43
|
].join('\n');
|
|
44
44
|
}
|
|
45
|
+
export function appendDiagnosticLogPath(message, logFile) {
|
|
46
|
+
const normalizedMessage = String(message ?? '').trim();
|
|
47
|
+
const normalizedLogFile = String(logFile ?? '').trim();
|
|
48
|
+
if (!normalizedLogFile) {
|
|
49
|
+
return normalizedMessage;
|
|
50
|
+
}
|
|
51
|
+
return [normalizedMessage, `Diagnostic log: ${normalizedLogFile}`].filter(Boolean).join('\n\n');
|
|
52
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
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 fs from 'node:fs';
|
|
10
|
+
import fsp from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { resolveCliHomeDir } from './cli-home.js';
|
|
13
|
+
import { DEFAULT_LOG_ENABLED, DEFAULT_LOG_RETENTION_DAYS, getCliConfigValue } from './cli-config.js';
|
|
14
|
+
const ACTIVE_LOG_FILE_ENV = 'NB_CLI_ACTIVE_LOG_FILE';
|
|
15
|
+
const ACTIVE_META_FILE_ENV = 'NB_CLI_ACTIVE_META_FILE';
|
|
16
|
+
const LOG_DIR_ENV = 'NB_CLI_LOG_DIR';
|
|
17
|
+
const LOG_DISABLED_ENV = 'NB_CLI_LOG_DISABLED';
|
|
18
|
+
const ANSI_ESCAPE_PATTERN = new RegExp(String.raw `\u001B\[[0-?]*[ -/]*[@-~]`, 'g');
|
|
19
|
+
const CLEANUP_STATE_FILE = '.cleanup-state.json';
|
|
20
|
+
const CLEANUP_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
21
|
+
function isTruthyEnvFlag(value) {
|
|
22
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
23
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
24
|
+
}
|
|
25
|
+
function pad(value) {
|
|
26
|
+
return String(value).padStart(2, '0');
|
|
27
|
+
}
|
|
28
|
+
function formatDateParts(date) {
|
|
29
|
+
const year = date.getFullYear();
|
|
30
|
+
const month = pad(date.getMonth() + 1);
|
|
31
|
+
const day = pad(date.getDate());
|
|
32
|
+
const hour = pad(date.getHours());
|
|
33
|
+
const minute = pad(date.getMinutes());
|
|
34
|
+
const second = pad(date.getSeconds());
|
|
35
|
+
return {
|
|
36
|
+
dayKey: `${year}-${month}-${day}`,
|
|
37
|
+
timestampKey: `${year}${month}${day}-${hour}${minute}${second}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function sanitizeFileNameSegment(value) {
|
|
41
|
+
const normalized = value
|
|
42
|
+
.trim()
|
|
43
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
44
|
+
.replace(/-+/g, '-')
|
|
45
|
+
.replace(/^-|-$/g, '');
|
|
46
|
+
return normalized || 'command';
|
|
47
|
+
}
|
|
48
|
+
function resolveSessionDirName(sessionId) {
|
|
49
|
+
const trimmed = String(sessionId ?? '').trim();
|
|
50
|
+
if (!trimmed) {
|
|
51
|
+
return 'no-session';
|
|
52
|
+
}
|
|
53
|
+
return sanitizeFileNameSegment(trimmed);
|
|
54
|
+
}
|
|
55
|
+
export function stripAnsi(value) {
|
|
56
|
+
return value.replace(ANSI_ESCAPE_PATTERN, '');
|
|
57
|
+
}
|
|
58
|
+
export function shouldEnableCommandLog(env = process.env) {
|
|
59
|
+
return !isTruthyEnvFlag(env[LOG_DISABLED_ENV]);
|
|
60
|
+
}
|
|
61
|
+
export function resolveCommandLogDir(env = process.env) {
|
|
62
|
+
if (!shouldEnableCommandLog(env)) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const configured = String(env[LOG_DIR_ENV] ?? '').trim();
|
|
66
|
+
if (configured) {
|
|
67
|
+
return path.resolve(configured);
|
|
68
|
+
}
|
|
69
|
+
return path.join(resolveCliHomeDir(), 'logs');
|
|
70
|
+
}
|
|
71
|
+
export function getActiveCommandLogFile(env = process.env) {
|
|
72
|
+
const value = String(env[ACTIVE_LOG_FILE_ENV] ?? '').trim();
|
|
73
|
+
return value || undefined;
|
|
74
|
+
}
|
|
75
|
+
function readActiveCommandMetaFile(env = process.env) {
|
|
76
|
+
const value = String(env[ACTIVE_META_FILE_ENV] ?? '').trim();
|
|
77
|
+
return value || undefined;
|
|
78
|
+
}
|
|
79
|
+
export function hasActiveCommandLog(env = process.env) {
|
|
80
|
+
return Boolean(getActiveCommandLogFile(env));
|
|
81
|
+
}
|
|
82
|
+
export function sanitizeArgv(argv) {
|
|
83
|
+
const sensitiveFlags = new Set(['--token', '--access-token', '--password', '--secret']);
|
|
84
|
+
const sanitized = [];
|
|
85
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
86
|
+
const token = argv[index];
|
|
87
|
+
if (sensitiveFlags.has(token)) {
|
|
88
|
+
sanitized.push(token);
|
|
89
|
+
if (index + 1 < argv.length) {
|
|
90
|
+
sanitized.push('***');
|
|
91
|
+
index += 1;
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const matched = token.match(/^(--(?:token|access-token|password|secret))=(.*)$/);
|
|
96
|
+
if (matched) {
|
|
97
|
+
sanitized.push(`${matched[1]}=***`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
sanitized.push(token);
|
|
101
|
+
}
|
|
102
|
+
return sanitized;
|
|
103
|
+
}
|
|
104
|
+
export function getCommandLogCommandId(argv) {
|
|
105
|
+
const positional = argv.filter((token) => token && !token.startsWith('-'));
|
|
106
|
+
if (positional.length === 0) {
|
|
107
|
+
return 'command';
|
|
108
|
+
}
|
|
109
|
+
const commandPath = positional[0] === 'api' ? positional.slice(0, 3) : positional.slice(0, 2);
|
|
110
|
+
return sanitizeFileNameSegment(commandPath.join('-'));
|
|
111
|
+
}
|
|
112
|
+
function parseDateDirName(name) {
|
|
113
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(name)) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const parsed = new Date(`${name}T00:00:00.000Z`);
|
|
117
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
118
|
+
}
|
|
119
|
+
async function readCleanupState(filePath) {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(await fsp.readFile(filePath, 'utf8'));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function writeCleanupState(filePath, state) {
|
|
128
|
+
await fsp.writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
129
|
+
}
|
|
130
|
+
async function resolveCommandLogRetentionDays() {
|
|
131
|
+
const configured = await getCliConfigValue('log.retention-days').catch(() => String(DEFAULT_LOG_RETENTION_DAYS));
|
|
132
|
+
const parsed = Number.parseInt(String(configured).trim(), 10);
|
|
133
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
134
|
+
return DEFAULT_LOG_RETENTION_DAYS;
|
|
135
|
+
}
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
async function resolveCommandLogEnabled() {
|
|
139
|
+
const configured = await getCliConfigValue('log.enabled').catch(() => String(DEFAULT_LOG_ENABLED));
|
|
140
|
+
const normalized = String(configured).trim().toLowerCase();
|
|
141
|
+
if (normalized === 'false') {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (normalized === 'true') {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return DEFAULT_LOG_ENABLED;
|
|
148
|
+
}
|
|
149
|
+
async function cleanupCommandLogs(rootDir, retentionDays, now = new Date()) {
|
|
150
|
+
if (retentionDays === 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const entries = await fsp.readdir(rootDir, { withFileTypes: true }).catch(() => []);
|
|
154
|
+
const cutoff = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
155
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - retentionDays + 1);
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (!entry.isDirectory()) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const dirDate = parseDateDirName(entry.name);
|
|
161
|
+
if (!dirDate || dirDate >= cutoff) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
await fsp.rm(path.join(rootDir, entry.name), { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function maybeCleanupCommandLogs(rootDir, env = process.env) {
|
|
168
|
+
await fsp.mkdir(rootDir, { recursive: true });
|
|
169
|
+
const stateFile = path.join(rootDir, CLEANUP_STATE_FILE);
|
|
170
|
+
const state = await readCleanupState(stateFile);
|
|
171
|
+
const lastCleanupAt = Date.parse(String(state.lastCleanupAt ?? ''));
|
|
172
|
+
if (!Number.isNaN(lastCleanupAt) && Date.now() - lastCleanupAt < CLEANUP_INTERVAL_MS) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const retentionDays = await resolveCommandLogRetentionDays();
|
|
176
|
+
await cleanupCommandLogs(rootDir, retentionDays);
|
|
177
|
+
await writeCleanupState(stateFile, { lastCleanupAt: new Date().toISOString() });
|
|
178
|
+
}
|
|
179
|
+
async function writeMetaFile(filePath, meta) {
|
|
180
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
181
|
+
await fsp.writeFile(filePath, `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
|
|
182
|
+
}
|
|
183
|
+
function writeMetaFileSync(filePath, meta) {
|
|
184
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
185
|
+
fs.writeFileSync(filePath, `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
|
|
186
|
+
}
|
|
187
|
+
export async function initCommandLogSession(options) {
|
|
188
|
+
const env = options.env ?? process.env;
|
|
189
|
+
if (!shouldEnableCommandLog(env)) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
const enabled = await resolveCommandLogEnabled().catch(() => DEFAULT_LOG_ENABLED);
|
|
193
|
+
if (!enabled) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const rootDir = resolveCommandLogDir(env);
|
|
197
|
+
if (!rootDir) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
await maybeCleanupCommandLogs(rootDir, env).catch(() => undefined);
|
|
201
|
+
const startedAt = options.startedAt ?? new Date();
|
|
202
|
+
const { dayKey, timestampKey } = formatDateParts(startedAt);
|
|
203
|
+
const commandId = getCommandLogCommandId(options.argv);
|
|
204
|
+
const fileStem = `${timestampKey}-${commandId}-${options.pid ?? process.pid}`;
|
|
205
|
+
const sessionDirName = resolveSessionDirName(options.sessionId);
|
|
206
|
+
const logDir = path.join(rootDir, dayKey, sessionDirName);
|
|
207
|
+
const logFile = path.join(logDir, `${fileStem}.log`);
|
|
208
|
+
const metaFile = path.join(logDir, `${fileStem}.meta.json`);
|
|
209
|
+
const meta = {
|
|
210
|
+
command: commandId,
|
|
211
|
+
argv: sanitizeArgv(options.argv),
|
|
212
|
+
cwd: options.cwd,
|
|
213
|
+
pid: options.pid ?? process.pid,
|
|
214
|
+
ppid: options.ppid ?? process.ppid,
|
|
215
|
+
sessionId: options.sessionId,
|
|
216
|
+
startedAt: startedAt.toISOString(),
|
|
217
|
+
logFile,
|
|
218
|
+
cliVersion: options.cliVersion,
|
|
219
|
+
nodeVersion: options.nodeVersion,
|
|
220
|
+
platform: options.platform,
|
|
221
|
+
interactive: options.interactive,
|
|
222
|
+
verbose: options.verbose,
|
|
223
|
+
};
|
|
224
|
+
await fsp.mkdir(logDir, { recursive: true });
|
|
225
|
+
await fsp.writeFile(logFile, '', 'utf8');
|
|
226
|
+
await writeMetaFile(metaFile, meta);
|
|
227
|
+
env[ACTIVE_LOG_FILE_ENV] = logFile;
|
|
228
|
+
env[ACTIVE_META_FILE_ENV] = metaFile;
|
|
229
|
+
return {
|
|
230
|
+
logFile,
|
|
231
|
+
metaFile,
|
|
232
|
+
meta,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
export function finalizeCommandLogSessionSync(session, options = {}) {
|
|
236
|
+
if (!session) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const endedAt = options.endedAt ?? new Date();
|
|
240
|
+
const startedAtMs = Date.parse(session.meta.startedAt);
|
|
241
|
+
const nextMeta = {
|
|
242
|
+
...session.meta,
|
|
243
|
+
endedAt: endedAt.toISOString(),
|
|
244
|
+
durationMs: Number.isNaN(startedAtMs) ? undefined : Math.max(0, endedAt.getTime() - startedAtMs),
|
|
245
|
+
exitCode: options.exitCode ?? session.meta.exitCode,
|
|
246
|
+
signal: options.signal ?? session.meta.signal,
|
|
247
|
+
errorMessage: options.errorMessage ?? session.meta.errorMessage,
|
|
248
|
+
};
|
|
249
|
+
writeMetaFileSync(session.metaFile, nextMeta);
|
|
250
|
+
session.meta = nextMeta;
|
|
251
|
+
}
|
|
252
|
+
export function appendCommandLogChunk(chunk, env = process.env) {
|
|
253
|
+
const logFile = getActiveCommandLogFile(env);
|
|
254
|
+
if (!logFile) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
|
|
258
|
+
if (!text) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
fs.appendFileSync(logFile, stripAnsi(text), 'utf8');
|
|
262
|
+
}
|
|
263
|
+
export function installCommandLogWriteHooks(env = process.env) {
|
|
264
|
+
if (!hasActiveCommandLog(env)) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
268
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
269
|
+
process.stdout.write = ((chunk, encoding, callback) => {
|
|
270
|
+
appendCommandLogChunk(chunk, env);
|
|
271
|
+
return originalStdoutWrite(chunk, encoding, callback);
|
|
272
|
+
});
|
|
273
|
+
process.stderr.write = ((chunk, encoding, callback) => {
|
|
274
|
+
appendCommandLogChunk(chunk, env);
|
|
275
|
+
return originalStderrWrite(chunk, encoding, callback);
|
|
276
|
+
});
|
|
277
|
+
return () => {
|
|
278
|
+
process.stdout.write = originalStdoutWrite;
|
|
279
|
+
process.stderr.write = originalStderrWrite;
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
export function getActiveCommandMetaFile(env = process.env) {
|
|
283
|
+
return readActiveCommandMetaFile(env);
|
|
284
|
+
}
|
|
@@ -8,15 +8,23 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { access } from 'node:fs/promises';
|
|
10
10
|
import { resolveConfiguredEnvPath } from './cli-home.js';
|
|
11
|
+
import { inferConfiguredAppPathFromLegacyConfig } from './env-paths.js';
|
|
11
12
|
function trimValue(value) {
|
|
12
13
|
const text = String(value ?? '').trim();
|
|
13
14
|
return text || undefined;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
function appendDotEnvPath(basePath) {
|
|
17
|
+
return `${basePath.replace(/[\\/]+$/, '')}/.env`;
|
|
18
|
+
}
|
|
19
|
+
export function defaultDockerEnvFilePath(envName, config) {
|
|
20
|
+
const appPath = inferConfiguredAppPathFromLegacyConfig(config ?? {});
|
|
21
|
+
if (appPath) {
|
|
22
|
+
return appendDotEnvPath(appPath);
|
|
23
|
+
}
|
|
16
24
|
return `${envName}/.env`;
|
|
17
25
|
}
|
|
18
26
|
export function resolveConfiguredDockerEnvFilePath(envName, config) {
|
|
19
|
-
return trimValue(config?.envFile) || defaultDockerEnvFilePath(envName);
|
|
27
|
+
return trimValue(config?.envFile) || defaultDockerEnvFilePath(envName, config);
|
|
20
28
|
}
|
|
21
29
|
export function hasExplicitDockerEnvFile(config) {
|
|
22
30
|
return Boolean(trimValue(config?.envFile));
|