devops-whc 1.0.1 → 1.0.2-next
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/{AGENT_MCP_USAGE.md → AGENT_USAGE.md} +129 -148
- package/README.md +214 -45
- package/{WHC_MCP_REQUIREMENTS.md → WHC_REQUIREMENTS.md} +8 -6
- package/dist/config/env.js +46 -21
- package/dist/handlers/whc-db-backup.js +0 -1
- package/dist/handlers/whc-deploy.js +80 -224
- package/dist/handlers/whc-pipeline-status.js +2 -2
- package/dist/handlers/whc-prepare.js +1 -1
- package/dist/handlers/whc-setup-remote.js +4 -3
- package/dist/index.js +258 -14
- package/dist/probes/source-compatibility.js +457 -0
- package/dist/schemas/whc-deploy.js +13 -9
- package/dist/server-entry.js +2 -2
- package/dist/server.js +13 -12
- package/dist/services/deploy-runtime-ops.js +8 -96
- package/dist/state/workspace-state.js +107 -7
- package/package.json +12 -7
- package/scripts/prepare-first-time.cjs +3 -3
- package/scripts/{start-mcp.cjs → start-whc.cjs} +3 -4
|
@@ -2,103 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runDeployVerification = runDeployVerification;
|
|
4
4
|
exports.runDeployRollback = runDeployRollback;
|
|
5
|
-
async function runDeployVerification(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return { ok: true, message: "Verification passed for managed_clone_sync mode" };
|
|
9
|
-
}
|
|
10
|
-
if (!payload.repository_root) {
|
|
11
|
-
return { ok: false, message: "Missing repository_root for verification" };
|
|
12
|
-
}
|
|
13
|
-
const repoPath = shellQuote(payload.repository_root);
|
|
14
|
-
const verifyProfile = resolveVerifyProfile(payload.source_profile.source_kind);
|
|
15
|
-
const commandParts = [
|
|
16
|
-
`test -d ${repoPath} && echo __PATH_OK__ || echo __PATH_MISSING__`,
|
|
17
|
-
`if [ -f ${repoPath}/.cpanel.yml ]; then echo __CPANEL_YML_OK__; else echo __CPANEL_YML_MISSING__; fi`,
|
|
18
|
-
];
|
|
19
|
-
if (verifyProfile.requireWpCliCheck) {
|
|
20
|
-
commandParts.push(`if command -v wp >/dev/null 2>&1; then wp core is-installed --path=${repoPath} >/dev/null 2>&1 && echo __WP_OK__ || echo __WP_NOT_READY__; else echo __WPCLI_MISSING__; fi`);
|
|
21
|
-
}
|
|
22
|
-
const command = commandParts.join(" ; ");
|
|
23
|
-
const target = resolveTarget(config, payload.target_environment);
|
|
24
|
-
if (!target.ok) {
|
|
25
|
-
return { ok: false, message: target.message };
|
|
26
|
-
}
|
|
27
|
-
const sshResult = await sshClient.execWithKey(target.value, command);
|
|
28
|
-
if (!sshResult.ok) {
|
|
29
|
-
return { ok: false, message: `SSH verify command failed: ${sshResult.message}` };
|
|
30
|
-
}
|
|
31
|
-
const output = sshResult.stdout;
|
|
32
|
-
if (output.includes("__PATH_MISSING__")) {
|
|
33
|
-
return { ok: false, message: "Deployment path does not exist after deploy" };
|
|
34
|
-
}
|
|
35
|
-
if (output.includes("__CPANEL_YML_MISSING__")) {
|
|
36
|
-
return { ok: false, message: ".cpanel.yml is missing in deployment root" };
|
|
37
|
-
}
|
|
38
|
-
if (verifyProfile.requireWpCliCheck && output.includes("__WPCLI_MISSING__")) {
|
|
39
|
-
return { ok: false, message: "WP-CLI is required for full_site verification but is not available" };
|
|
40
|
-
}
|
|
41
|
-
if (verifyProfile.requireWpCliCheck && output.includes("__WP_NOT_READY__")) {
|
|
42
|
-
return { ok: false, message: "WordPress core is not installed/ready after deploy" };
|
|
43
|
-
}
|
|
44
|
-
return { ok: true, message: `Runtime verification passed (${verifyProfile.name})` };
|
|
5
|
+
async function runDeployVerification(_config, _request, _sshClient) {
|
|
6
|
+
// Verification for git_deploy is transport-level only.
|
|
7
|
+
return { ok: true, message: "Verification passed for git_deploy mode" };
|
|
45
8
|
}
|
|
46
|
-
async function runDeployRollback(_config,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
ok: false,
|
|
51
|
-
message: "Automatic rollback is currently supported only for git_controlled mode",
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
if (!payload.repository_root) {
|
|
55
|
-
return { ok: false, message: "Missing repository_root for rollback" };
|
|
56
|
-
}
|
|
57
|
-
if (!payload.rollback_branch) {
|
|
58
|
-
return {
|
|
59
|
-
ok: false,
|
|
60
|
-
message: "rollback_branch is required for automatic rollback",
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
const rollbackResult = await uapiClient.triggerDeployment(payload.repository_root, payload.rollback_branch);
|
|
64
|
-
if (!rollbackResult.ok) {
|
|
65
|
-
return {
|
|
66
|
-
ok: false,
|
|
67
|
-
message: `Rollback deployment failed: ${rollbackResult.message}`,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
return { ok: true, message: `Rollback deployed from branch ${payload.rollback_branch}` };
|
|
71
|
-
}
|
|
72
|
-
function shellQuote(value) {
|
|
73
|
-
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
74
|
-
}
|
|
75
|
-
function resolveVerifyProfile(sourceKind) {
|
|
76
|
-
if (sourceKind === "full_site") {
|
|
77
|
-
return { name: "wordpress", requireWpCliCheck: true };
|
|
78
|
-
}
|
|
79
|
-
if (sourceKind === "package_only" || sourceKind === "artifact_first") {
|
|
80
|
-
return { name: "generic", requireWpCliCheck: false };
|
|
81
|
-
}
|
|
82
|
-
return { name: "generic", requireWpCliCheck: false };
|
|
83
|
-
}
|
|
84
|
-
function resolveTarget(config, environment) {
|
|
85
|
-
if (environment === "live") {
|
|
86
|
-
return { ok: true, value: config.sshTargets.prod };
|
|
87
|
-
}
|
|
88
|
-
const staging = config.sshTargets.staging;
|
|
89
|
-
if (!staging) {
|
|
90
|
-
return { ok: false, message: "Staging SSH target is not configured" };
|
|
91
|
-
}
|
|
92
|
-
if (!staging.privateKeyPath) {
|
|
93
|
-
return { ok: false, message: "Staging verification requires key-based SSH auth" };
|
|
94
|
-
}
|
|
9
|
+
async function runDeployRollback(_config, _request, _uapiClient) {
|
|
10
|
+
// Automatic rollback via UAPI is not supported on WHC Managed WordPress.
|
|
11
|
+
// WHC does not expose cPanel Git Version Control deployment for rollback.
|
|
95
12
|
return {
|
|
96
|
-
ok:
|
|
97
|
-
|
|
98
|
-
host: staging.host,
|
|
99
|
-
port: staging.port,
|
|
100
|
-
username: staging.username,
|
|
101
|
-
privateKeyPath: staging.privateKeyPath,
|
|
102
|
-
},
|
|
13
|
+
ok: false,
|
|
14
|
+
message: "Automatic rollback is not supported on this platform. Restore from DB backup created before deploy.",
|
|
103
15
|
};
|
|
104
16
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_STATE_ROOT = void 0;
|
|
4
4
|
exports.ensureWorkspaceState = ensureWorkspaceState;
|
|
5
|
+
exports.ensureWorkspaceBootstrap = ensureWorkspaceBootstrap;
|
|
5
6
|
exports.writePipelineStartState = writePipelineStartState;
|
|
6
7
|
exports.writePipelineEndState = writePipelineEndState;
|
|
7
8
|
exports.resolveStateRoot = resolveStateRoot;
|
|
@@ -15,7 +16,9 @@ exports.readJsonFile = readJsonFile;
|
|
|
15
16
|
const node_child_process_1 = require("node:child_process");
|
|
16
17
|
const node_fs_1 = require("node:fs");
|
|
17
18
|
const node_path_1 = require("node:path");
|
|
18
|
-
exports.DEFAULT_STATE_ROOT = ".
|
|
19
|
+
exports.DEFAULT_STATE_ROOT = ".whc";
|
|
20
|
+
const DEFAULT_BOOTSTRAP_ENV_FILE = "whc.env";
|
|
21
|
+
const DEFAULT_BOOTSTRAP_ENV_EXAMPLE_FILE = "whc.env.example";
|
|
19
22
|
function ensureWorkspaceState(rootDir = process.cwd()) {
|
|
20
23
|
const stateRoot = resolveStateRoot(rootDir);
|
|
21
24
|
const stateDir = (0, node_path_1.join)(stateRoot, "state");
|
|
@@ -29,7 +32,61 @@ function ensureWorkspaceState(rootDir = process.cwd()) {
|
|
|
29
32
|
(0, node_fs_1.mkdirSync)(reportsDir, { recursive: true });
|
|
30
33
|
(0, node_fs_1.mkdirSync)(logsDir, { recursive: true });
|
|
31
34
|
(0, node_fs_1.mkdirSync)(envDir, { recursive: true });
|
|
32
|
-
ensureGitignoreRule(rootDir, ".
|
|
35
|
+
ensureGitignoreRule(rootDir, ".whc/");
|
|
36
|
+
}
|
|
37
|
+
function ensureWorkspaceBootstrap(rootDir = process.cwd(), options = {}) {
|
|
38
|
+
const stateRootAbsolute = resolveStateRoot(rootDir);
|
|
39
|
+
const stateRootRelative = getRelativeStateRoot();
|
|
40
|
+
const stateDir = (0, node_path_1.join)(stateRootAbsolute, "state");
|
|
41
|
+
const releasesDir = (0, node_path_1.join)(stateDir, "releases");
|
|
42
|
+
const reportsDir = (0, node_path_1.join)(stateDir, "reports");
|
|
43
|
+
const logsDir = (0, node_path_1.join)(stateRootAbsolute, "logs");
|
|
44
|
+
const envDir = (0, node_path_1.join)(stateRootAbsolute, "env");
|
|
45
|
+
const trackedPaths = [
|
|
46
|
+
stateRootAbsolute,
|
|
47
|
+
stateDir,
|
|
48
|
+
releasesDir,
|
|
49
|
+
reportsDir,
|
|
50
|
+
logsDir,
|
|
51
|
+
envDir,
|
|
52
|
+
];
|
|
53
|
+
const createdPaths = [];
|
|
54
|
+
for (const dirPath of trackedPaths) {
|
|
55
|
+
if (!(0, node_fs_1.existsSync)(dirPath)) {
|
|
56
|
+
(0, node_fs_1.mkdirSync)(dirPath, { recursive: true });
|
|
57
|
+
createdPaths.push(dirPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const gitignoreUpdated = ensureGitignoreRule(rootDir, ".whc/");
|
|
61
|
+
const pipelineStatusFile = getPipelineStatusFile(rootDir);
|
|
62
|
+
if (!(0, node_fs_1.existsSync)(pipelineStatusFile)) {
|
|
63
|
+
(0, node_fs_1.writeFileSync)(pipelineStatusFile, JSON.stringify({
|
|
64
|
+
started: false,
|
|
65
|
+
completed: false,
|
|
66
|
+
status: "idle",
|
|
67
|
+
next_step: "Run a write tool (deploy/setup/backup/ssh) to start tracking.",
|
|
68
|
+
}, null, 2) + "\n", { encoding: "utf8" });
|
|
69
|
+
createdPaths.push(pipelineStatusFile);
|
|
70
|
+
}
|
|
71
|
+
const envFile = (0, node_path_1.join)(envDir, DEFAULT_BOOTSTRAP_ENV_FILE);
|
|
72
|
+
if (!(0, node_fs_1.existsSync)(envFile)) {
|
|
73
|
+
(0, node_fs_1.writeFileSync)(envFile, buildBootstrapEnvTemplate(rootDir, options), { encoding: "utf8" });
|
|
74
|
+
createdPaths.push(envFile);
|
|
75
|
+
}
|
|
76
|
+
const envExampleFile = (0, node_path_1.join)(envDir, DEFAULT_BOOTSTRAP_ENV_EXAMPLE_FILE);
|
|
77
|
+
if (!(0, node_fs_1.existsSync)(envExampleFile)) {
|
|
78
|
+
(0, node_fs_1.writeFileSync)(envExampleFile, buildBootstrapEnvTemplate(rootDir, options), { encoding: "utf8" });
|
|
79
|
+
createdPaths.push(envExampleFile);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
stateRootRelative,
|
|
83
|
+
stateRootAbsolute,
|
|
84
|
+
createdPaths,
|
|
85
|
+
gitignoreUpdated,
|
|
86
|
+
pipelineStatusFile,
|
|
87
|
+
envFile,
|
|
88
|
+
envExampleFile,
|
|
89
|
+
};
|
|
33
90
|
}
|
|
34
91
|
function writePipelineStartState(input) {
|
|
35
92
|
ensureWorkspaceState(input.rootDir);
|
|
@@ -112,15 +169,16 @@ function ensureGitignoreRule(rootDir, rule) {
|
|
|
112
169
|
const gitignorePath = (0, node_path_1.join)(rootDir, ".gitignore");
|
|
113
170
|
if (!(0, node_fs_1.existsSync)(gitignorePath)) {
|
|
114
171
|
(0, node_fs_1.writeFileSync)(gitignorePath, `${rule}\n`, { encoding: "utf8" });
|
|
115
|
-
return;
|
|
172
|
+
return true;
|
|
116
173
|
}
|
|
117
174
|
const content = (0, node_fs_1.readFileSync)(gitignorePath, "utf8");
|
|
118
175
|
const hasRule = content.split(/\r?\n/).some((line) => line.trim() === rule);
|
|
119
176
|
if (hasRule) {
|
|
120
|
-
return;
|
|
177
|
+
return false;
|
|
121
178
|
}
|
|
122
179
|
const next = content.endsWith("\n") ? `${content}${rule}\n` : `${content}\n${rule}\n`;
|
|
123
180
|
(0, node_fs_1.writeFileSync)(gitignorePath, next, { encoding: "utf8" });
|
|
181
|
+
return true;
|
|
124
182
|
}
|
|
125
183
|
function safeFileName(value) {
|
|
126
184
|
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
@@ -129,17 +187,59 @@ function defaultNextStep(ok) {
|
|
|
129
187
|
if (ok) {
|
|
130
188
|
return "Proceed to next pipeline step based on policy and verification scope.";
|
|
131
189
|
}
|
|
132
|
-
return "Inspect .
|
|
190
|
+
return "Inspect .whc/logs/flow-events.jsonl and retry after fixing the reported error.";
|
|
133
191
|
}
|
|
134
192
|
function resolveStateRoot(rootDir = process.cwd()) {
|
|
135
|
-
const
|
|
136
|
-
const configuredOrDefault = configured.length > 0 ? configured : exports.DEFAULT_STATE_ROOT;
|
|
193
|
+
const configuredOrDefault = getRelativeStateRoot();
|
|
137
194
|
if ((0, node_path_1.isAbsolute)(configuredOrDefault)) {
|
|
138
195
|
return configuredOrDefault;
|
|
139
196
|
}
|
|
140
197
|
const segments = configuredOrDefault.split(/[\\/]+/).filter((part) => part.length > 0);
|
|
141
198
|
return (0, node_path_1.join)(rootDir, ...segments);
|
|
142
199
|
}
|
|
200
|
+
function getRelativeStateRoot() {
|
|
201
|
+
const configured = (process.env.WHC_STATE_ROOT ?? "").trim();
|
|
202
|
+
return configured.length > 0 ? configured : exports.DEFAULT_STATE_ROOT;
|
|
203
|
+
}
|
|
204
|
+
function buildBootstrapEnvTemplate(rootDir, options) {
|
|
205
|
+
return [
|
|
206
|
+
"# Project-local WHC bootstrap env",
|
|
207
|
+
"# Fill the values needed for this specific deploy workspace.",
|
|
208
|
+
`WHC_LOCAL_PROJECT_ROOT=${options.localProjectRoot ?? rootDir}`,
|
|
209
|
+
`WHC_LOCAL_APP_PATH=${options.localAppPath ?? ""}`,
|
|
210
|
+
"WHC_WORKFLOW_MODE=",
|
|
211
|
+
"WHC_SOURCE_KIND=full_site",
|
|
212
|
+
"WHC_DEPLOY_UNIT=raw_source",
|
|
213
|
+
"WHC_DEFAULT_RELEASE_INTENT=deploy",
|
|
214
|
+
"",
|
|
215
|
+
"# Managed WHC mode",
|
|
216
|
+
"WHC_API_TOKEN=",
|
|
217
|
+
"WHC_USER=",
|
|
218
|
+
"WHC_HOST=",
|
|
219
|
+
"WHC_PROD_PATH=/public_html",
|
|
220
|
+
"WHC_PROD_SSH_HOST=",
|
|
221
|
+
"WHC_PROD_SSH_PORT=27",
|
|
222
|
+
"WHC_PROD_SSH_USERNAME=",
|
|
223
|
+
"WHC_PROD_SSH_PRIVATE_KEY_PATH=",
|
|
224
|
+
"",
|
|
225
|
+
"# Staging/source-driven mode",
|
|
226
|
+
"WHC_STAGING_DOMAIN=",
|
|
227
|
+
"WHC_STAGING_PATH=",
|
|
228
|
+
"WHC_STAGING_SSH_HOST=",
|
|
229
|
+
"WHC_STAGING_SSH_PORT=27",
|
|
230
|
+
"WHC_STAGING_SSH_USERNAME=",
|
|
231
|
+
"WHC_STAGING_SSH_PRIVATE_KEY_PATH=",
|
|
232
|
+
"WHC_STAGING_SSH_PASSWORD=",
|
|
233
|
+
"WHC_STAGING_SSH_HOSTKEY=",
|
|
234
|
+
"",
|
|
235
|
+
"WHC_REQUIRE_STAGING_CONFIRM=true",
|
|
236
|
+
"WHC_WARN_DYNAMIC_DATA_SYNC=true",
|
|
237
|
+
"WHC_ENFORCE_STAGING_FIRST=true",
|
|
238
|
+
"WHC_API_TIMEOUT_MS=15000",
|
|
239
|
+
"WHC_SSH_TIMEOUT_MS=10000",
|
|
240
|
+
"",
|
|
241
|
+
].join("\n");
|
|
242
|
+
}
|
|
143
243
|
function getPipelineStatusFile(rootDir = process.cwd()) {
|
|
144
244
|
return (0, node_path_1.join)(resolveStateRoot(rootDir), "state", "pipeline-status.json");
|
|
145
245
|
}
|
package/package.json
CHANGED
|
@@ -1,37 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devops-whc",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "WHC cPanel
|
|
3
|
+
"version": "1.0.2-next",
|
|
4
|
+
"description": "WHC cPanel automation CLI for deploy, verify, and rollback flows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"whc
|
|
7
|
+
"whc": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./dist/index.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
|
-
"scripts/start-
|
|
14
|
+
"scripts/start-whc.cjs",
|
|
15
15
|
"scripts/prepare-first-time.cjs",
|
|
16
16
|
"README.md",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"AGENT_USAGE.md",
|
|
18
|
+
"WHC_REQUIREMENTS.md",
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
22
|
"prepack": "node ../../scripts/prepare-publish-package.cjs",
|
|
23
|
+
"publish:release": "npm publish",
|
|
23
24
|
"pack:check": "npm pack --dry-run",
|
|
24
25
|
"prehelp": "node ../../scripts/prepare-publish-package.cjs",
|
|
25
26
|
"help": "node dist/index.js --help",
|
|
27
|
+
"prestart": "node ../../scripts/prepare-publish-package.cjs",
|
|
28
|
+
"start": "node scripts/start-whc.cjs",
|
|
26
29
|
"preserve": "node ../../scripts/prepare-publish-package.cjs",
|
|
27
30
|
"serve": "node dist/index.js --serve",
|
|
28
31
|
"preprobe": "node ../../scripts/prepare-publish-package.cjs",
|
|
29
32
|
"probe": "node dist/index.js --probe",
|
|
33
|
+
"precheck:generic": "node ../../scripts/prepare-publish-package.cjs",
|
|
34
|
+
"check:generic": "node dist/index.js --check-generic",
|
|
30
35
|
"precheck:health": "node ../../scripts/prepare-publish-package.cjs",
|
|
31
36
|
"check:health": "node dist/index.js --check-health"
|
|
32
37
|
},
|
|
33
38
|
"keywords": [
|
|
34
|
-
"
|
|
39
|
+
"cli",
|
|
35
40
|
"whc",
|
|
36
41
|
"cpanel",
|
|
37
42
|
"deploy"
|
|
@@ -30,7 +30,7 @@ function ensureFileIfMissing(filePath, data) {
|
|
|
30
30
|
|
|
31
31
|
function resolveStateRoot(rootDir) {
|
|
32
32
|
const configured = (process.env.WHC_STATE_ROOT || "").trim();
|
|
33
|
-
const relative = configured.length > 0 ? configured : ".
|
|
33
|
+
const relative = configured.length > 0 ? configured : ".whc";
|
|
34
34
|
const segments = relative.split(/[\\/]+/).filter(Boolean);
|
|
35
35
|
return path.join(rootDir, ...segments);
|
|
36
36
|
}
|
|
@@ -51,7 +51,7 @@ function main() {
|
|
|
51
51
|
ensureDir(logsDir);
|
|
52
52
|
ensureDir(envDir);
|
|
53
53
|
|
|
54
|
-
ensureGitignoreRule(rootDir, ".
|
|
54
|
+
ensureGitignoreRule(rootDir, ".whc/");
|
|
55
55
|
|
|
56
56
|
const statusFile = path.join(stateDir, "pipeline-status.json");
|
|
57
57
|
ensureFileIfMissing(
|
|
@@ -68,7 +68,7 @@ function main() {
|
|
|
68
68
|
) + "\n",
|
|
69
69
|
);
|
|
70
70
|
|
|
71
|
-
console.log("[prepare-first-time] Initialized .
|
|
71
|
+
console.log("[prepare-first-time] Initialized .whc hidden state and ensured .gitignore contains .whc/");
|
|
72
72
|
console.log(`[prepare-first-time] State file: ${statusFile}`);
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -9,8 +9,7 @@ const projectRootHint = process.env.WHC_LOCAL_PROJECT_ROOT;
|
|
|
9
9
|
const envCandidates = [
|
|
10
10
|
requestedEnvPath,
|
|
11
11
|
projectRootHint ? path.join(projectRootHint, ".vscode", "whc.env") : undefined,
|
|
12
|
-
projectRootHint ? path.join(projectRootHint, ".
|
|
13
|
-
projectRootHint ? path.join(projectRootHint, ".mcp", "whc.env") : undefined,
|
|
12
|
+
projectRootHint ? path.join(projectRootHint, ".whc", "env", "whc.env") : undefined,
|
|
14
13
|
path.join(workspaceRoot, ".env"),
|
|
15
14
|
]
|
|
16
15
|
.filter(Boolean)
|
|
@@ -19,7 +18,7 @@ const envCandidates = [
|
|
|
19
18
|
const envFilePath = envCandidates.find((p) => fs.existsSync(p));
|
|
20
19
|
|
|
21
20
|
// Load .env manually before importing the compiled server
|
|
22
|
-
// (
|
|
21
|
+
// (tool clients can start the process without any env pre-loaded)
|
|
23
22
|
if (envFilePath && fs.existsSync(envFilePath)) {
|
|
24
23
|
const parsed = dotenv.parse(fs.readFileSync(envFilePath));
|
|
25
24
|
for (const [key, value] of Object.entries(parsed)) {
|
|
@@ -36,7 +35,7 @@ const serverEntryCandidates = [
|
|
|
36
35
|
|
|
37
36
|
const serverEntry = serverEntryCandidates.find((p) => fs.existsSync(p));
|
|
38
37
|
if (!serverEntry) {
|
|
39
|
-
throw new Error("
|
|
38
|
+
throw new Error("Build output not found. Run npm run build before npm run start.");
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
require(serverEntry);
|