@nocobase/cli 2.1.0-beta.8 → 2.2.0-alpha.1
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/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.cmd +3 -0
- package/bin/run.js +145 -0
- package/bin/session-env.js +39 -0
- package/dist/commands/api/resource/create.js +15 -0
- package/dist/commands/api/resource/destroy.js +15 -0
- package/dist/commands/api/resource/get.js +15 -0
- package/dist/commands/api/resource/index.js +20 -0
- package/dist/commands/api/resource/list.js +16 -0
- package/dist/commands/api/resource/query.js +15 -0
- package/dist/commands/api/resource/update.js +15 -0
- 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 +234 -0
- package/dist/commands/app/down.js +71 -0
- package/dist/commands/app/logs.js +115 -0
- package/dist/commands/app/restart.js +229 -0
- package/dist/commands/app/shared.js +123 -0
- package/dist/commands/app/start.js +416 -0
- package/dist/commands/app/stop.js +183 -0
- package/dist/commands/app/upgrade.js +523 -0
- package/dist/commands/backup/create.js +147 -0
- package/dist/commands/backup/index.js +20 -0
- package/dist/commands/backup/restore.js +105 -0
- package/{src/cli.js → dist/commands/build.js} +4 -11
- package/dist/commands/config/delete.js +42 -0
- package/dist/commands/config/get.js +39 -0
- package/dist/commands/config/index.js +20 -0
- package/dist/commands/config/list.js +29 -0
- package/dist/commands/config/set.js +49 -0
- package/dist/commands/db/check.js +240 -0
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +47 -0
- package/dist/commands/db/shared.js +96 -0
- package/dist/commands/db/start.js +86 -0
- package/dist/commands/db/stop.js +71 -0
- package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
- package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
- package/dist/commands/download.js +13 -0
- package/dist/commands/env/add.js +406 -0
- package/dist/commands/env/auth.js +189 -0
- package/dist/commands/env/current.js +21 -0
- package/dist/commands/env/info.js +202 -0
- package/dist/commands/env/list.js +43 -0
- package/dist/commands/env/remove.js +174 -0
- package/dist/commands/env/shared.js +204 -0
- package/dist/commands/env/status.js +93 -0
- package/dist/commands/env/update.js +448 -0
- package/dist/commands/env/use.js +38 -0
- package/dist/commands/examples/prompts-stages.js +150 -0
- package/dist/commands/examples/prompts-test.js +181 -0
- package/dist/commands/init.js +1390 -0
- package/dist/commands/install.js +2609 -0
- package/dist/commands/license/activate.js +179 -0
- package/dist/commands/license/env.js +94 -0
- package/dist/commands/license/generate-id.js +108 -0
- package/dist/commands/license/id.js +70 -0
- package/dist/commands/license/index.js +20 -0
- package/dist/commands/license/plugins/clean.js +115 -0
- package/dist/commands/license/plugins/index.js +20 -0
- package/dist/commands/license/plugins/list.js +64 -0
- package/dist/commands/license/plugins/shared.js +382 -0
- package/dist/commands/license/plugins/sync.js +314 -0
- package/dist/commands/license/shared.js +423 -0
- package/dist/commands/license/status.js +64 -0
- package/dist/commands/logs.js +12 -0
- package/dist/commands/plugin/disable.js +86 -0
- package/dist/commands/plugin/enable.js +86 -0
- package/dist/commands/plugin/import.js +108 -0
- package/dist/commands/plugin/list.js +82 -0
- package/dist/commands/pm/disable.js +12 -0
- package/dist/commands/pm/enable.js +12 -0
- package/dist/commands/pm/list.js +12 -0
- package/dist/commands/proxy/caddy/current.js +17 -0
- package/dist/commands/proxy/caddy/generate.js +69 -0
- package/dist/commands/proxy/caddy/index.js +28 -0
- package/dist/commands/proxy/caddy/info.js +31 -0
- package/dist/commands/proxy/caddy/reload.js +30 -0
- package/dist/commands/proxy/caddy/restart.js +28 -0
- package/dist/commands/proxy/caddy/start.js +30 -0
- package/dist/commands/proxy/caddy/status.js +19 -0
- package/dist/commands/proxy/caddy/stop.js +30 -0
- package/dist/commands/proxy/caddy/use.js +26 -0
- package/dist/commands/proxy/index.js +28 -0
- package/dist/commands/proxy/nginx/current.js +18 -0
- package/dist/commands/proxy/nginx/generate.js +68 -0
- package/dist/commands/proxy/nginx/index.js +28 -0
- package/dist/commands/proxy/nginx/info.js +34 -0
- package/dist/commands/proxy/nginx/reload.js +30 -0
- package/dist/commands/proxy/nginx/restart.js +28 -0
- package/dist/commands/proxy/nginx/start.js +30 -0
- package/dist/commands/proxy/nginx/status.js +19 -0
- package/dist/commands/proxy/nginx/stop.js +30 -0
- package/dist/commands/proxy/nginx/use.js +31 -0
- package/dist/commands/restart.js +12 -0
- package/dist/commands/revision/create.js +118 -0
- package/dist/commands/scaffold/migration.js +38 -0
- package/dist/commands/scaffold/plugin.js +37 -0
- package/dist/commands/self/check.js +71 -0
- package/dist/commands/self/index.js +20 -0
- package/dist/commands/self/update.js +152 -0
- package/dist/commands/session/id.js +24 -0
- package/dist/commands/session/remove.js +57 -0
- package/dist/commands/session/setup.js +62 -0
- package/dist/commands/skills/check.js +69 -0
- package/dist/commands/skills/index.js +20 -0
- package/dist/commands/skills/install.js +80 -0
- package/dist/commands/skills/remove.js +80 -0
- package/dist/commands/skills/update.js +87 -0
- package/dist/commands/source/build.js +58 -0
- package/dist/commands/source/dev.js +182 -0
- package/dist/commands/source/download.js +884 -0
- package/dist/commands/source/publish.js +109 -0
- package/dist/commands/source/registry/logs.js +70 -0
- package/dist/commands/source/registry/start.js +57 -0
- package/dist/commands/source/registry/status.js +33 -0
- package/dist/commands/source/registry/stop.js +48 -0
- package/dist/commands/source/test.js +476 -0
- package/dist/commands/start.js +12 -0
- package/dist/commands/stop.js +12 -0
- package/dist/commands/test.js +12 -0
- package/dist/commands/upgrade.js +12 -0
- package/dist/commands/v1.js +210 -0
- package/dist/generated/command-registry.js +134 -0
- package/dist/help/runtime-help.js +23 -0
- package/dist/lib/api-client.js +335 -0
- package/dist/lib/api-command-compat.js +641 -0
- package/dist/lib/app-health.js +139 -0
- package/dist/lib/app-managed-resources.js +337 -0
- package/dist/lib/app-public-path.js +80 -0
- package/dist/lib/app-runtime.js +189 -0
- package/dist/lib/auth-store.js +528 -0
- package/dist/lib/backup.js +171 -0
- package/dist/lib/bootstrap.js +409 -0
- package/dist/lib/build-config.js +18 -0
- package/dist/lib/builtin-db.js +86 -0
- package/dist/lib/cli-config.js +569 -0
- package/dist/lib/cli-entry-error.js +52 -0
- package/dist/lib/cli-home.js +47 -0
- package/dist/lib/cli-locale.js +141 -0
- package/dist/lib/command-discovery.js +39 -0
- package/dist/lib/command-log.js +284 -0
- package/dist/lib/db-connection-check.js +219 -0
- package/dist/lib/docker-env-file.js +60 -0
- package/dist/lib/docker-image.js +37 -0
- package/dist/lib/docker-log-stream.js +45 -0
- package/dist/lib/env-auth.js +963 -0
- package/dist/lib/env-command-config.js +45 -0
- package/dist/lib/env-config.js +108 -0
- package/dist/lib/env-guard.js +61 -0
- package/dist/lib/env-paths.js +101 -0
- package/dist/lib/env-proxy.js +1325 -0
- package/dist/lib/generated-command.js +203 -0
- package/dist/lib/http-request.js +49 -0
- package/dist/lib/inquirer-theme.js +17 -0
- package/dist/lib/inquirer.js +243 -0
- package/dist/lib/managed-env-file.js +101 -0
- package/dist/lib/managed-init-env.js +32 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/object-utils.js +76 -0
- package/dist/lib/openapi.js +62 -0
- package/dist/lib/plugin-import.js +279 -0
- package/dist/lib/plugin-storage.js +64 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/prompt-catalog-core.js +186 -0
- package/dist/lib/prompt-catalog-terminal.js +374 -0
- package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
- package/dist/lib/prompt-validators.js +278 -0
- package/dist/lib/prompt-web-ui.js +2234 -0
- package/dist/lib/proxy-caddy.js +274 -0
- package/dist/lib/proxy-nginx.js +330 -0
- package/dist/lib/resource-command.js +357 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/run-npm.js +429 -0
- package/dist/lib/runtime-env-vars.js +32 -0
- package/dist/lib/runtime-generator.js +498 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/self-manager.js +301 -0
- package/dist/lib/session-id.js +17 -0
- package/dist/lib/session-integration.js +703 -0
- package/dist/lib/session-store.js +118 -0
- package/dist/lib/skills-manager.js +438 -0
- package/dist/lib/source-publish.js +326 -0
- package/dist/lib/source-registry.js +188 -0
- package/dist/lib/startup-update.js +309 -0
- package/dist/lib/ui.js +159 -0
- package/dist/locale/en-US.json +526 -0
- package/dist/locale/zh-CN.json +526 -0
- package/dist/post-processors/data-modeling.js +84 -0
- package/dist/post-processors/data-source-manager.js +138 -0
- package/dist/post-processors/index.js +19 -0
- package/nocobase-ctl.config.json +388 -0
- package/package.json +128 -24
- package/scripts/build.mjs +34 -0
- package/scripts/clean.mjs +9 -0
- package/tsconfig.json +19 -0
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -95
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -49
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -166
- package/src/commands/create-nginx-conf.js +0 -37
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -200
- package/src/commands/doc.js +0 -76
- package/src/commands/e2e.js +0 -265
- package/src/commands/global.js +0 -43
- package/src/commands/index.js +0 -45
- package/src/commands/instance-id.js +0 -47
- package/src/commands/locale/cronstrue.js +0 -122
- package/src/commands/locale/react-js-cron/en-US.json +0 -75
- package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
- package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
- package/src/commands/locale.js +0 -81
- package/src/commands/p-test.js +0 -88
- package/src/commands/perf.js +0 -63
- package/src/commands/pkg.js +0 -321
- package/src/commands/pm2.js +0 -37
- package/src/commands/postinstall.js +0 -88
- package/src/commands/start.js +0 -148
- package/src/commands/tar.js +0 -36
- package/src/commands/test-coverage.js +0 -55
- package/src/commands/test.js +0 -107
- package/src/commands/umi.js +0 -33
- package/src/commands/update-deps.js +0 -72
- package/src/commands/upgrade.js +0 -47
- package/src/commands/view-license-key.js +0 -44
- package/src/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -517
- package/templates/bundle-status.html +0 -338
- package/templates/create-app-package.json +0 -39
- package/templates/plugin/.npmignore.tpl +0 -2
- package/templates/plugin/README.md.tpl +0 -1
- package/templates/plugin/client.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -11
- package/templates/plugin/server.d.ts +0 -2
- package/templates/plugin/server.js +0 -1
- package/templates/plugin/src/client/client.d.ts +0 -249
- package/templates/plugin/src/client/index.tsx.tpl +0 -1
- package/templates/plugin/src/client/locale.ts +0 -21
- package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
- package/templates/plugin/src/index.ts +0 -2
- package/templates/plugin/src/locale/en-US.json +0 -1
- package/templates/plugin/src/locale/zh-CN.json +0 -1
- package/templates/plugin/src/server/collections/.gitkeep +0 -0
- package/templates/plugin/src/server/index.ts.tpl +0 -1
- package/templates/plugin/src/server/plugin.ts.tpl +0 -19
|
@@ -0,0 +1,139 @@
|
|
|
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 { buildLocalApiBaseUrl, buildLocalAppUrl } from './app-public-path.js';
|
|
10
|
+
import { startDockerLogFollower } from './docker-log-stream.js';
|
|
11
|
+
import { printInfo } from './ui.js';
|
|
12
|
+
const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
|
|
13
|
+
const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
|
|
14
|
+
const APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS = 5_000;
|
|
15
|
+
const APP_HEALTH_CHECK_PROGRESS_LOG_INTERVAL_MS = 10_000;
|
|
16
|
+
function trimValue(value) {
|
|
17
|
+
return String(value ?? '').trim();
|
|
18
|
+
}
|
|
19
|
+
function buildHealthCheckUrl(apiBaseUrl) {
|
|
20
|
+
return `${apiBaseUrl.replace(/\/+$/, '')}/__health_check`;
|
|
21
|
+
}
|
|
22
|
+
async function sleep(ms) {
|
|
23
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
24
|
+
}
|
|
25
|
+
async function requestAppHealthCheck(params) {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timeout = setTimeout(() => {
|
|
28
|
+
controller.abort();
|
|
29
|
+
}, params.requestTimeoutMs ?? APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS);
|
|
30
|
+
try {
|
|
31
|
+
const response = await (params.fetchImpl ?? fetch)(params.healthCheckUrl, {
|
|
32
|
+
method: 'GET',
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
});
|
|
35
|
+
const text = await response.text().catch(() => '');
|
|
36
|
+
const body = text.replace(/\s+/g, ' ').trim() || 'No response yet';
|
|
37
|
+
return {
|
|
38
|
+
ok: response.ok && text.trim().toLowerCase() === 'ok',
|
|
39
|
+
message: `HTTP ${response.status}: ${body}`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
message: `No response within ${Math.ceil((params.requestTimeoutMs ?? APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS) / 1000)}s`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
message: error instanceof Error ? error.message : String(error),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class AppHealthCheckError extends Error {
|
|
59
|
+
}
|
|
60
|
+
export function formatAppUrl(port, appPublicPath) {
|
|
61
|
+
return buildLocalAppUrl(port, appPublicPath);
|
|
62
|
+
}
|
|
63
|
+
export function resolveManagedAppApiBaseUrl(runtime, options) {
|
|
64
|
+
const override = trimValue(options?.portOverride);
|
|
65
|
+
if (override) {
|
|
66
|
+
return buildLocalApiBaseUrl(override, runtime.env.config?.appPublicPath);
|
|
67
|
+
}
|
|
68
|
+
const baseUrl = trimValue(runtime.env.baseUrl);
|
|
69
|
+
if (baseUrl) {
|
|
70
|
+
return baseUrl.replace(/\/+$/, '');
|
|
71
|
+
}
|
|
72
|
+
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
73
|
+
? ''
|
|
74
|
+
: trimValue(runtime.env.appPort);
|
|
75
|
+
return buildLocalApiBaseUrl(appPort, runtime.env.config?.appPublicPath);
|
|
76
|
+
}
|
|
77
|
+
export async function isAppReady(apiBaseUrl, options) {
|
|
78
|
+
const baseUrl = trimValue(apiBaseUrl);
|
|
79
|
+
if (!baseUrl) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const result = await requestAppHealthCheck({
|
|
83
|
+
healthCheckUrl: buildHealthCheckUrl(baseUrl),
|
|
84
|
+
fetchImpl: options?.fetchImpl,
|
|
85
|
+
requestTimeoutMs: options?.requestTimeoutMs,
|
|
86
|
+
});
|
|
87
|
+
return result.ok;
|
|
88
|
+
}
|
|
89
|
+
export async function waitForAppReady(params) {
|
|
90
|
+
const apiBaseUrl = trimValue(params.apiBaseUrl);
|
|
91
|
+
if (!apiBaseUrl) {
|
|
92
|
+
printInfo(`Skipping health check for "${params.envName}" because no local API URL is saved for this env.`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const healthCheckUrl = buildHealthCheckUrl(apiBaseUrl);
|
|
96
|
+
const startedAt = Date.now();
|
|
97
|
+
const timeoutMs = params.timeoutMs ?? APP_HEALTH_CHECK_TIMEOUT_MS;
|
|
98
|
+
const intervalMs = params.intervalMs ?? APP_HEALTH_CHECK_INTERVAL_MS;
|
|
99
|
+
const progressLogIntervalMs = Math.max(1, params.progressLogIntervalMs ?? APP_HEALTH_CHECK_PROGRESS_LOG_INTERVAL_MS);
|
|
100
|
+
let lastMessage = 'No response yet';
|
|
101
|
+
let nextProgressLogAt = startedAt + progressLogIntervalMs;
|
|
102
|
+
printInfo(`Waiting for NocoBase to become ready for "${params.envName}"...`);
|
|
103
|
+
const dockerLogFollower = params.verbose && params.containerName ? startDockerLogFollower(params.containerName) : undefined;
|
|
104
|
+
try {
|
|
105
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
106
|
+
const result = await requestAppHealthCheck({
|
|
107
|
+
healthCheckUrl,
|
|
108
|
+
fetchImpl: params.fetchImpl,
|
|
109
|
+
requestTimeoutMs: params.requestTimeoutMs,
|
|
110
|
+
});
|
|
111
|
+
if (result.ok) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
lastMessage = result.message;
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
if (now >= nextProgressLogAt) {
|
|
117
|
+
const elapsedSeconds = Math.max(1, Math.floor((now - startedAt) / 1000));
|
|
118
|
+
printInfo(`Still waiting for "${params.envName}"... (${elapsedSeconds}s elapsed)`);
|
|
119
|
+
while (nextProgressLogAt <= now) {
|
|
120
|
+
nextProgressLogAt += progressLogIntervalMs;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
124
|
+
if (remainingMs <= 0) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
await sleep(Math.min(intervalMs, remainingMs));
|
|
128
|
+
}
|
|
129
|
+
const hints = [
|
|
130
|
+
params.logHint,
|
|
131
|
+
params.containerName ? `docker logs ${params.containerName}` : undefined,
|
|
132
|
+
].filter(Boolean);
|
|
133
|
+
const hintText = hints.length > 0 ? ` ${hints.join(' ')}` : '';
|
|
134
|
+
throw new AppHealthCheckError(`NocoBase did not become ready in time for "${params.envName}". Expected \`${healthCheckUrl}\` to respond with \`ok\`, but the last status was: ${lastMessage}.${hintText}`);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await dockerLogFollower?.stop();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
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, readdir } from 'node:fs/promises';
|
|
10
|
+
import { resolveAppPublicPath } from './app-public-path.js';
|
|
11
|
+
import { dockerContainerExists, managedAppLifecycleEnvVars, runLocalNocoBaseCommand, startDockerContainer, } from './app-runtime.js';
|
|
12
|
+
import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin-db.js';
|
|
13
|
+
import { resolveConfiguredStoragePath } from './env-paths.js';
|
|
14
|
+
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
15
|
+
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
|
|
16
|
+
import { commandSucceeds, ensureDockerDaemonRunning, run } from './run-npm.js';
|
|
17
|
+
import Install from '../commands/install.js';
|
|
18
|
+
const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
|
|
19
|
+
function commandStdio(verbose) {
|
|
20
|
+
return verbose ? 'inherit' : 'ignore';
|
|
21
|
+
}
|
|
22
|
+
async function ensureDockerNetwork(networkName) {
|
|
23
|
+
await ensureDockerDaemonRunning('prepare Docker resources for this environment');
|
|
24
|
+
if (await commandSucceeds('docker', ['network', 'inspect', networkName])) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await run('docker', ['network', 'create', networkName], {
|
|
28
|
+
errorName: 'docker network create',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function localSourceLabel(source) {
|
|
32
|
+
return source === 'git' ? 'Git checkout' : 'npm app';
|
|
33
|
+
}
|
|
34
|
+
function trimValue(value) {
|
|
35
|
+
return String(value ?? '').trim();
|
|
36
|
+
}
|
|
37
|
+
function pushOptionalEnvArg(args, key, value) {
|
|
38
|
+
if (typeof value === 'string') {
|
|
39
|
+
if (!value) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
args.push('-e', `${key}=${value}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === 'boolean') {
|
|
46
|
+
args.push('-e', `${key}=${String(value)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function resolveDockerClientAssetsExtractEnabled(envValue) {
|
|
50
|
+
const text = trimValue(envValue).toLowerCase();
|
|
51
|
+
if (!text) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (['0', 'false', 'no', 'off'].includes(text)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
function normalizeDockerPlatform(value) {
|
|
60
|
+
const text = trimValue(value);
|
|
61
|
+
if (!text || text === 'auto') {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
if (text === 'linux/amd64' || text === 'linux/arm64') {
|
|
65
|
+
return text;
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
function formatBuiltinDbFailure(envName, message) {
|
|
70
|
+
return [
|
|
71
|
+
`Couldn't restore the built-in database for "${envName}".`,
|
|
72
|
+
'Check the saved database settings, local storage path, and Docker runtime, then try again.',
|
|
73
|
+
`Details: ${message}`,
|
|
74
|
+
].join('\n');
|
|
75
|
+
}
|
|
76
|
+
function formatLocalSourceRestoreFailure(envName, source, message) {
|
|
77
|
+
const sourceLabel = source === 'git' ? 'the saved Git checkout' : 'the saved npm app';
|
|
78
|
+
return [
|
|
79
|
+
`Couldn't restore NocoBase files for "${envName}".`,
|
|
80
|
+
`The CLI was not able to download ${sourceLabel} before starting the app again.`,
|
|
81
|
+
'Check the saved source settings for this env, then try again.',
|
|
82
|
+
`Details: ${message}`,
|
|
83
|
+
].join('\n');
|
|
84
|
+
}
|
|
85
|
+
function formatLocalPostinstallFailure(envName, message) {
|
|
86
|
+
return [
|
|
87
|
+
`Couldn't prepare NocoBase for "${envName}".`,
|
|
88
|
+
'The CLI was not able to run `nocobase-v1 postinstall` before starting the local app.',
|
|
89
|
+
'Check the local dependencies, storage path, and saved env settings, then try again.',
|
|
90
|
+
`Details: ${message}`,
|
|
91
|
+
].join('\n');
|
|
92
|
+
}
|
|
93
|
+
function formatSavedDockerSettingsIncomplete(envName, missing) {
|
|
94
|
+
return [
|
|
95
|
+
`Can't start NocoBase for "${envName}" yet.`,
|
|
96
|
+
`The saved Docker settings for this env are incomplete. Missing: ${missing.join(', ')}.`,
|
|
97
|
+
`Re-run \`nb init --ui --env ${envName}\` to refresh this env config, then try again.`,
|
|
98
|
+
].join('\n');
|
|
99
|
+
}
|
|
100
|
+
function formatDockerAppRecreateFailure(envName, message) {
|
|
101
|
+
return [
|
|
102
|
+
`Couldn't start NocoBase for "${envName}".`,
|
|
103
|
+
'The CLI was not able to recreate the saved Docker app container successfully.',
|
|
104
|
+
'Check the saved Docker image, container settings, and database connection, then try again.',
|
|
105
|
+
`Details: ${message}`,
|
|
106
|
+
].join('\n');
|
|
107
|
+
}
|
|
108
|
+
async function localProjectHasFiles(projectRoot) {
|
|
109
|
+
try {
|
|
110
|
+
const entries = await readdir(projectRoot);
|
|
111
|
+
return entries.length > 0;
|
|
112
|
+
}
|
|
113
|
+
catch (_error) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export async function buildSavedDockerRunArgs(runtime, options) {
|
|
118
|
+
const config = runtime.env.config ?? {};
|
|
119
|
+
const storagePath = trimValue(resolveConfiguredStoragePath(config));
|
|
120
|
+
const envFile = await resolveDockerEnvFileArg(runtime.envName, config);
|
|
121
|
+
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
122
|
+
? ''
|
|
123
|
+
: trimValue(runtime.env.appPort);
|
|
124
|
+
const appKey = trimValue(config.appKey);
|
|
125
|
+
const appPublicPath = trimValue(config.appPublicPath);
|
|
126
|
+
const timeZone = trimValue(config.timezone) || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
127
|
+
const builtinDbConnection = config.builtinDb ? deriveBuiltinDbConnection(runtime) : undefined;
|
|
128
|
+
const dbDialect = builtinDbConnection?.dbDialect || trimValue(config.dbDialect);
|
|
129
|
+
const dbHost = builtinDbConnection?.dbHost || trimValue(config.dbHost);
|
|
130
|
+
const dbPort = builtinDbConnection?.dbPort || trimValue(config.dbPort);
|
|
131
|
+
const dbDatabase = trimValue(config.dbDatabase);
|
|
132
|
+
const dbUser = trimValue(config.dbUser);
|
|
133
|
+
const dbPassword = trimValue(config.dbPassword);
|
|
134
|
+
const dbSchema = trimValue(config.dbSchema);
|
|
135
|
+
const dbTablePrefix = trimValue(config.dbTablePrefix);
|
|
136
|
+
const dbUnderscored = typeof config.dbUnderscored === 'boolean' ? config.dbUnderscored : undefined;
|
|
137
|
+
const extractClientAssets = resolveDockerClientAssetsExtractEnabled(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
|
|
138
|
+
const dockerRegistry = trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY;
|
|
139
|
+
const version = trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION;
|
|
140
|
+
const imageRef = resolveDockerImageRef(dockerRegistry, version, {
|
|
141
|
+
defaultRegistry: DEFAULT_DOCKER_REGISTRY,
|
|
142
|
+
defaultVersion: DEFAULT_DOCKER_VERSION,
|
|
143
|
+
});
|
|
144
|
+
const missing = [];
|
|
145
|
+
if (!storagePath) {
|
|
146
|
+
missing.push('storagePath');
|
|
147
|
+
}
|
|
148
|
+
if (!appKey) {
|
|
149
|
+
missing.push('appKey');
|
|
150
|
+
}
|
|
151
|
+
if (!dbDialect) {
|
|
152
|
+
missing.push('dbDialect');
|
|
153
|
+
}
|
|
154
|
+
if (!dbHost) {
|
|
155
|
+
missing.push('dbHost');
|
|
156
|
+
}
|
|
157
|
+
if (!dbPort) {
|
|
158
|
+
missing.push('dbPort');
|
|
159
|
+
}
|
|
160
|
+
if (!dbDatabase) {
|
|
161
|
+
missing.push('dbDatabase');
|
|
162
|
+
}
|
|
163
|
+
if (!dbUser) {
|
|
164
|
+
missing.push('dbUser');
|
|
165
|
+
}
|
|
166
|
+
if (!dbPassword) {
|
|
167
|
+
missing.push('dbPassword');
|
|
168
|
+
}
|
|
169
|
+
if (missing.length > 0) {
|
|
170
|
+
throw new Error(formatSavedDockerSettingsIncomplete(runtime.envName, missing));
|
|
171
|
+
}
|
|
172
|
+
const args = [
|
|
173
|
+
'run',
|
|
174
|
+
'-d',
|
|
175
|
+
'--name',
|
|
176
|
+
runtime.containerName,
|
|
177
|
+
'--restart',
|
|
178
|
+
'always',
|
|
179
|
+
'--network',
|
|
180
|
+
runtime.workspaceName,
|
|
181
|
+
];
|
|
182
|
+
const dockerPlatform = normalizeDockerPlatform(config.dockerPlatform);
|
|
183
|
+
if (dockerPlatform) {
|
|
184
|
+
args.push('--platform', dockerPlatform);
|
|
185
|
+
}
|
|
186
|
+
if (appPort) {
|
|
187
|
+
args.push('-p', `${appPort}:80`);
|
|
188
|
+
}
|
|
189
|
+
if (envFile) {
|
|
190
|
+
args.push('--env-file', envFile);
|
|
191
|
+
}
|
|
192
|
+
for (const [key, value] of Object.entries(options?.initEnvVars ?? {})) {
|
|
193
|
+
args.push('-e', `${key}=${value}`);
|
|
194
|
+
}
|
|
195
|
+
const lifecycleEnvVars = managedAppLifecycleEnvVars();
|
|
196
|
+
args.push('-e', `APP_ENV=${lifecycleEnvVars.APP_ENV}`, '-e', `NODE_ENV=${lifecycleEnvVars.NODE_ENV}`, '-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:${DOCKER_APP_STORAGE_DESTINATION}`);
|
|
197
|
+
pushOptionalEnvArg(args, 'APP_PUBLIC_PATH', appPublicPath ? resolveAppPublicPath(appPublicPath) : undefined);
|
|
198
|
+
pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema || undefined);
|
|
199
|
+
pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix || undefined);
|
|
200
|
+
pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
|
|
201
|
+
pushOptionalEnvArg(args, 'NOCOBASE_EXTRACT_CLIENT_ASSETS', extractClientAssets);
|
|
202
|
+
args.push(imageRef);
|
|
203
|
+
return {
|
|
204
|
+
appPort: appPort || undefined,
|
|
205
|
+
storagePath,
|
|
206
|
+
envFile,
|
|
207
|
+
imageRef,
|
|
208
|
+
args,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
export async function recreateSavedDockerApp(runtime, options) {
|
|
212
|
+
const plan = await buildSavedDockerRunArgs(runtime, {
|
|
213
|
+
initEnvVars: options?.initEnvVars,
|
|
214
|
+
});
|
|
215
|
+
try {
|
|
216
|
+
await ensureDockerNetwork(runtime.workspaceName);
|
|
217
|
+
await mkdir(plan.storagePath, { recursive: true });
|
|
218
|
+
await run('docker', plan.args, {
|
|
219
|
+
errorName: 'docker run',
|
|
220
|
+
stdio: commandStdio(options?.verbose),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
225
|
+
if (message.includes(`Can't start NocoBase for "${runtime.envName}" yet.`)
|
|
226
|
+
|| message.includes(`Couldn't start NocoBase for "${runtime.envName}".`)) {
|
|
227
|
+
throw error instanceof Error ? error : new Error(message);
|
|
228
|
+
}
|
|
229
|
+
throw new Error(formatDockerAppRecreateFailure(runtime.envName, message));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
export async function ensureBuiltinDbReady(runtime, options) {
|
|
233
|
+
const config = runtime.env.config ?? {};
|
|
234
|
+
if (!config.builtinDb) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const builtinDbConnection = await resolveBuiltinDbConnection(runtime);
|
|
238
|
+
const plan = Install.buildBuiltinDbPlan({
|
|
239
|
+
envName: runtime.envName,
|
|
240
|
+
workspaceName: runtime.workspaceName,
|
|
241
|
+
dockerContainerPrefix: runtime.dockerContainerPrefix,
|
|
242
|
+
appPath: config.appPath,
|
|
243
|
+
storagePath: config.storagePath,
|
|
244
|
+
source: runtime.source,
|
|
245
|
+
dbDialect: builtinDbConnection.dbDialect,
|
|
246
|
+
dbHost: builtinDbConnection.dbHost,
|
|
247
|
+
dbPort: builtinDbConnection.dbPort,
|
|
248
|
+
dbDatabase: config.dbDatabase,
|
|
249
|
+
dbUser: config.dbUser,
|
|
250
|
+
dbPassword: config.dbPassword,
|
|
251
|
+
builtinDbImage: config.builtinDbImage,
|
|
252
|
+
});
|
|
253
|
+
options?.onStartTask?.(`Restoring the built-in ${plan.dbDialect} database for "${runtime.envName}"...`);
|
|
254
|
+
try {
|
|
255
|
+
await ensureDockerNetwork(plan.networkName);
|
|
256
|
+
if (await dockerContainerExists(plan.containerName)) {
|
|
257
|
+
const state = await startDockerContainer(plan.containerName, {
|
|
258
|
+
stdio: commandStdio(options?.verbose),
|
|
259
|
+
});
|
|
260
|
+
options?.onSucceedTask?.(state === 'already-running'
|
|
261
|
+
? `The built-in ${plan.dbDialect} database is already running for "${runtime.envName}" at ${plan.dbHost}:${plan.dbPort}.`
|
|
262
|
+
: `The built-in ${plan.dbDialect} database is running for "${runtime.envName}" at ${plan.dbHost}:${plan.dbPort}.`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
await mkdir(plan.dataDir, { recursive: true });
|
|
266
|
+
await run('docker', plan.args, {
|
|
267
|
+
errorName: 'docker run',
|
|
268
|
+
stdio: commandStdio(options?.verbose),
|
|
269
|
+
});
|
|
270
|
+
options?.onSucceedTask?.(`The built-in ${plan.dbDialect} database is running for "${runtime.envName}" at ${plan.dbHost}:${plan.dbPort}.`);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
options?.onFailTask?.(`Failed to restore the built-in database for "${runtime.envName}".`);
|
|
274
|
+
throw new Error(formatBuiltinDbFailure(runtime.envName, error instanceof Error ? error.message : String(error)));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
export function buildSavedLocalDownloadArgv(runtime, options) {
|
|
278
|
+
const config = runtime.env.config ?? {};
|
|
279
|
+
const argv = ['-y', '--no-intro'];
|
|
280
|
+
if (options?.verbose) {
|
|
281
|
+
argv.push('--verbose');
|
|
282
|
+
}
|
|
283
|
+
argv.push('--source', runtime.source, '--replace', '--output-dir', runtime.projectRoot);
|
|
284
|
+
const version = String(config.downloadVersion ?? '').trim();
|
|
285
|
+
const gitUrl = String(config.gitUrl ?? '').trim();
|
|
286
|
+
const npmRegistry = String(config.npmRegistry ?? '').trim();
|
|
287
|
+
if (version) {
|
|
288
|
+
argv.push('--version', version);
|
|
289
|
+
}
|
|
290
|
+
if (gitUrl) {
|
|
291
|
+
argv.push('--git-url', gitUrl);
|
|
292
|
+
}
|
|
293
|
+
if (npmRegistry) {
|
|
294
|
+
argv.push('--npm-registry', npmRegistry);
|
|
295
|
+
}
|
|
296
|
+
if (config.devDependencies === true) {
|
|
297
|
+
argv.push('--dev-dependencies');
|
|
298
|
+
}
|
|
299
|
+
if (config.build === false) {
|
|
300
|
+
argv.push('--no-build');
|
|
301
|
+
}
|
|
302
|
+
if (config.buildDts === true) {
|
|
303
|
+
argv.push('--build-dts');
|
|
304
|
+
}
|
|
305
|
+
return argv;
|
|
306
|
+
}
|
|
307
|
+
export async function ensureSavedLocalSource(runtime, runCommand, options) {
|
|
308
|
+
if (await localProjectHasFiles(runtime.projectRoot)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const sourceLabel = localSourceLabel(runtime.source);
|
|
312
|
+
options?.onStartTask?.(`Restoring the saved ${sourceLabel} for "${runtime.envName}"...`);
|
|
313
|
+
try {
|
|
314
|
+
await runCommand('source:download', buildSavedLocalDownloadArgv(runtime, {
|
|
315
|
+
verbose: options?.verbose,
|
|
316
|
+
}));
|
|
317
|
+
options?.onSucceedTask?.(`NocoBase files are ready for "${runtime.envName}".`);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
options?.onFailTask?.(`Failed to restore NocoBase files for "${runtime.envName}".`);
|
|
321
|
+
throw new Error(formatLocalSourceRestoreFailure(runtime.envName, runtime.source, error instanceof Error ? error.message : String(error)));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export async function ensureLocalPostinstall(runtime, options) {
|
|
325
|
+
options?.onStartTask?.(`Running local postinstall for "${runtime.envName}"...`);
|
|
326
|
+
try {
|
|
327
|
+
await runLocalNocoBaseCommand(runtime, ['postinstall'], {
|
|
328
|
+
env: options?.env,
|
|
329
|
+
stdio: commandStdio(options?.verbose),
|
|
330
|
+
});
|
|
331
|
+
options?.onSucceedTask?.(`Local postinstall finished for "${runtime.envName}".`);
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
options?.onFailTask?.(`Failed to run local postinstall for "${runtime.envName}".`);
|
|
335
|
+
throw new Error(formatLocalPostinstallFailure(runtime.envName, error instanceof Error ? error.message : String(error)));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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 { readFile } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
const DEFAULT_APP_PUBLIC_PATH = '/';
|
|
12
|
+
const DEFAULT_DIST_SEGMENT = 'dist';
|
|
13
|
+
const DIST_CLIENT_DIR = 'dist-client';
|
|
14
|
+
const DIST_CLIENT_ACTIVE_VERSION_FILE = 'active-version';
|
|
15
|
+
function trimValue(value) {
|
|
16
|
+
const text = String(value ?? '').trim();
|
|
17
|
+
return text || undefined;
|
|
18
|
+
}
|
|
19
|
+
export function resolveAppPublicPath(appPublicPath = DEFAULT_APP_PUBLIC_PATH) {
|
|
20
|
+
const normalized = String(appPublicPath || DEFAULT_APP_PUBLIC_PATH)
|
|
21
|
+
.trim()
|
|
22
|
+
.replace(/\/+/g, '/')
|
|
23
|
+
.replace(/\/?$/, '/');
|
|
24
|
+
const withLeadingSlash = normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
25
|
+
return withLeadingSlash || DEFAULT_APP_PUBLIC_PATH;
|
|
26
|
+
}
|
|
27
|
+
export function appendAppPublicPath(appPublicPath, segment, options) {
|
|
28
|
+
const publicPath = resolveAppPublicPath(appPublicPath).replace(/\/+$/, '');
|
|
29
|
+
const normalizedSegment = String(segment || '')
|
|
30
|
+
.trim()
|
|
31
|
+
.replace(/^\/+/, '')
|
|
32
|
+
.replace(options?.trailingSlash ? /\/+$/ : /\/*$/, '');
|
|
33
|
+
const joinedPath = normalizedSegment ? `${publicPath}/${normalizedSegment}` : publicPath || '/';
|
|
34
|
+
if (options?.trailingSlash) {
|
|
35
|
+
return joinedPath.endsWith('/') ? joinedPath : `${joinedPath}/`;
|
|
36
|
+
}
|
|
37
|
+
return joinedPath || '/';
|
|
38
|
+
}
|
|
39
|
+
export function resolveDistPublicPath(appPublicPath) {
|
|
40
|
+
return appendAppPublicPath(appPublicPath, DEFAULT_DIST_SEGMENT, { trailingSlash: true });
|
|
41
|
+
}
|
|
42
|
+
export function buildDefaultCdnBaseUrl(appPublicPath, version) {
|
|
43
|
+
const normalizedVersion = String(version || '')
|
|
44
|
+
.trim()
|
|
45
|
+
.replace(/^\/+|\/+$/g, '');
|
|
46
|
+
return `${resolveDistPublicPath(appPublicPath)}${normalizedVersion}/`;
|
|
47
|
+
}
|
|
48
|
+
export function buildLocalAppUrl(port, appPublicPath) {
|
|
49
|
+
const value = trimValue(port);
|
|
50
|
+
if (!value) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const publicPath = resolveAppPublicPath(appPublicPath);
|
|
54
|
+
return publicPath === '/' ? `http://127.0.0.1:${value}` : `http://127.0.0.1:${value}${publicPath}`;
|
|
55
|
+
}
|
|
56
|
+
export function buildLocalApiBaseUrl(port, appPublicPath) {
|
|
57
|
+
const value = trimValue(port);
|
|
58
|
+
if (!value) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return `http://127.0.0.1:${value}${appendAppPublicPath(appPublicPath, 'api', { trailingSlash: false })}`;
|
|
62
|
+
}
|
|
63
|
+
export function resolveDistClientRoot(storagePath) {
|
|
64
|
+
return path.join(storagePath, DIST_CLIENT_DIR);
|
|
65
|
+
}
|
|
66
|
+
export function resolveDistClientVersionRoot(storagePath, version) {
|
|
67
|
+
return path.join(resolveDistClientRoot(storagePath), version);
|
|
68
|
+
}
|
|
69
|
+
export function resolveDistClientActiveVersionFile(storagePath) {
|
|
70
|
+
return path.join(resolveDistClientRoot(storagePath), DIST_CLIENT_ACTIVE_VERSION_FILE);
|
|
71
|
+
}
|
|
72
|
+
export async function readDistClientActiveVersion(storagePath) {
|
|
73
|
+
try {
|
|
74
|
+
const content = await readFile(resolveDistClientActiveVersionFile(storagePath), 'utf8');
|
|
75
|
+
return trimValue(content);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|