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
|
@@ -7,15 +7,7 @@ const whc_uapi_client_1 = require("../clients/whc-uapi-client");
|
|
|
7
7
|
const deployment_locks_1 = require("../services/deployment-locks");
|
|
8
8
|
const ssh_client_1 = require("../clients/ssh-client");
|
|
9
9
|
const deploy_runtime_ops_1 = require("../services/deploy-runtime-ops");
|
|
10
|
-
|
|
11
|
-
* Resolves the effective safety level for this deploy operation.
|
|
12
|
-
* staging_to_live + database or everything → Level D.
|
|
13
|
-
* All other writes remain at Level C.
|
|
14
|
-
*/
|
|
15
|
-
function resolveDeploySafetyLevel(direction, syncScope) {
|
|
16
|
-
if (direction === "staging_to_live" && (syncScope === "database" || syncScope === "everything")) {
|
|
17
|
-
return "D";
|
|
18
|
-
}
|
|
10
|
+
function resolveDeploySafetyLevel() {
|
|
19
11
|
return "C";
|
|
20
12
|
}
|
|
21
13
|
async function executeWhcDeploy(config, request, deps = {}) {
|
|
@@ -26,90 +18,78 @@ async function executeWhcDeploy(config, request, deps = {}) {
|
|
|
26
18
|
const lockService = deps.lockService ?? new deployment_locks_1.InMemoryDeploymentLockService();
|
|
27
19
|
const verifyRunner = deps.verifyRunner ?? ((cfg, req) => (0, deploy_runtime_ops_1.runDeployVerification)(cfg, req, sshClient));
|
|
28
20
|
const rollbackRunner = deps.rollbackRunner ?? ((cfg, req) => (0, deploy_runtime_ops_1.runDeployRollback)(cfg, req, uapiClient));
|
|
29
|
-
const { workflow_mode, target_environment, release_intent, pipeline_id, source_profile,
|
|
30
|
-
const effectiveSafetyLevel = resolveDeploySafetyLevel(
|
|
31
|
-
const needsBackupGate = direction === "staging_to_live" && (sync_scope === "database" || sync_scope === "everything");
|
|
21
|
+
const { workflow_mode, target_environment, release_intent, pipeline_id, source_profile, repository_root, branch, direction, sync_scope, backup_reference, verify_after_deploy, auto_rollback_on_verify_failure, lock_key, } = request.payload;
|
|
22
|
+
const effectiveSafetyLevel = resolveDeploySafetyLevel();
|
|
32
23
|
const shouldVerify = verify_after_deploy ?? false;
|
|
33
|
-
const deployLockKey = lock_key ??
|
|
24
|
+
const deployLockKey = lock_key ?? `whc_deploy:${target_environment}:${workflow_mode}:${repository_root ?? "unset"}`;
|
|
34
25
|
const lockOwner = request.request_id;
|
|
35
26
|
const warnings = [];
|
|
36
|
-
if ((config.safety.enforceStagingFirst ?? true) && target_environment === "live") {
|
|
37
|
-
const isManagedPromoteFlow = workflow_mode === "managed_clone_sync" &&
|
|
38
|
-
direction === "staging_to_live" &&
|
|
39
|
-
release_intent === "promote";
|
|
40
|
-
if (!isManagedPromoteFlow) {
|
|
41
|
-
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "BUSINESS_POLICY_BLOCKED", [
|
|
42
|
-
"Staging-first policy: direct live deploy is blocked.",
|
|
43
|
-
"Only promote flow from staging to live is allowed.",
|
|
44
|
-
"Required payload shape for live target:",
|
|
45
|
-
JSON.stringify({
|
|
46
|
-
workflow_mode: "managed_clone_sync",
|
|
47
|
-
target_environment: "live",
|
|
48
|
-
release_intent: "promote",
|
|
49
|
-
direction: "staging_to_live",
|
|
50
|
-
}, null, 2),
|
|
51
|
-
].join("\n"));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (target_environment === "staging" &&
|
|
55
|
-
config.workflowMode === "managed_clone_sync" &&
|
|
56
|
-
workflow_mode === "git_controlled" &&
|
|
57
|
-
!config.safety.allowStagingGitControlled) {
|
|
27
|
+
if ((config.safety.enforceStagingFirst ?? true) && workflow_mode === "git_deploy" && target_environment === "live" && release_intent !== "promote") {
|
|
58
28
|
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "BUSINESS_POLICY_BLOCKED", [
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"Use this payload instead:",
|
|
62
|
-
JSON.stringify(buildStagingAlternativePayload(request), null, 2),
|
|
29
|
+
"Staging-first policy: direct live git deployment is blocked.",
|
|
30
|
+
"Only release_intent='promote' is allowed when target_environment='live'.",
|
|
63
31
|
].join("\n"));
|
|
64
32
|
}
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
33
|
+
if (workflow_mode === "ssh_scp_wpcli") {
|
|
34
|
+
const sourceScriptHint = [
|
|
35
|
+
"ssh_scp_wpcli execution is not implemented in whc_deploy yet.",
|
|
36
|
+
"This workflow is modeled after mytho/source where deploy is:",
|
|
37
|
+
"1. doctor",
|
|
38
|
+
"2. backup",
|
|
39
|
+
"3. scp code sync",
|
|
40
|
+
"4. wp-cli runtime bootstrap",
|
|
41
|
+
"5. wp eval-file seed/bootstrap",
|
|
42
|
+
"6. HTTP/API smoke",
|
|
43
|
+
"Use the project-local pipeline script for real execution until source-specific deploy integration lands.",
|
|
44
|
+
].join("\n");
|
|
45
|
+
if (request.dry_run) {
|
|
46
|
+
const phaseCoverage = buildPhaseCoverage({
|
|
47
|
+
workflowMode: workflow_mode,
|
|
48
|
+
verifyStatus: "skipped",
|
|
49
|
+
dryRun: true,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
action_id: actionIdFactory(),
|
|
54
|
+
tool: "whc_deploy",
|
|
55
|
+
data: {
|
|
56
|
+
outcome: "dry_run_preview",
|
|
57
|
+
workflow_mode,
|
|
58
|
+
target_environment,
|
|
59
|
+
direction,
|
|
60
|
+
sync_scope,
|
|
61
|
+
lock_key: deployLockKey,
|
|
62
|
+
backup_reference,
|
|
63
|
+
pipeline_status: derivePipelineStatus(phaseCoverage),
|
|
64
|
+
phase_coverage: phaseCoverage,
|
|
65
|
+
warnings: [...warnings, sourceScriptHint],
|
|
66
|
+
},
|
|
67
|
+
error: null,
|
|
68
|
+
meta: {
|
|
69
|
+
latency_ms: Date.now() - startedAt,
|
|
70
|
+
safety_level: effectiveSafetyLevel,
|
|
71
|
+
workflow_mode,
|
|
72
|
+
pipeline_id,
|
|
73
|
+
release_intent,
|
|
74
|
+
source_profile,
|
|
75
|
+
delivery_mechanism: "file_transfer",
|
|
76
|
+
},
|
|
77
|
+
};
|
|
97
78
|
}
|
|
79
|
+
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "BUSINESS_POLICY_BLOCKED", sourceScriptHint);
|
|
98
80
|
}
|
|
99
|
-
if (direction
|
|
100
|
-
warnings.push("
|
|
81
|
+
if (direction || sync_scope) {
|
|
82
|
+
warnings.push("direction/sync_scope are legacy managed-sync fields and are ignored by git_deploy.");
|
|
101
83
|
}
|
|
102
|
-
if (
|
|
103
|
-
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "
|
|
84
|
+
if (!repository_root) {
|
|
85
|
+
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "VALIDATION_ERROR", "git_deploy requires payload.repository_root so WHC knows which cPanel-managed repository to deploy.");
|
|
104
86
|
}
|
|
105
|
-
if (
|
|
106
|
-
warnings.push("
|
|
87
|
+
if (!branch) {
|
|
88
|
+
warnings.push("No branch was supplied; cPanel will deploy the repository's current configured HEAD.");
|
|
107
89
|
}
|
|
108
|
-
// Dry run: return preview without making changes
|
|
109
90
|
if (request.dry_run) {
|
|
110
91
|
const phaseCoverage = buildPhaseCoverage({
|
|
111
92
|
workflowMode: workflow_mode,
|
|
112
|
-
syncScope: sync_scope,
|
|
113
93
|
verifyStatus: "skipped",
|
|
114
94
|
dryRun: true,
|
|
115
95
|
});
|
|
@@ -121,11 +101,10 @@ async function executeWhcDeploy(config, request, deps = {}) {
|
|
|
121
101
|
outcome: "dry_run_preview",
|
|
122
102
|
workflow_mode,
|
|
123
103
|
target_environment,
|
|
124
|
-
direction,
|
|
125
|
-
sync_scope,
|
|
126
104
|
repository_root,
|
|
127
105
|
branch,
|
|
128
|
-
|
|
106
|
+
direction,
|
|
107
|
+
sync_scope,
|
|
129
108
|
lock_key: deployLockKey,
|
|
130
109
|
backup_reference,
|
|
131
110
|
pipeline_status: derivePipelineStatus(phaseCoverage),
|
|
@@ -140,7 +119,7 @@ async function executeWhcDeploy(config, request, deps = {}) {
|
|
|
140
119
|
pipeline_id,
|
|
141
120
|
release_intent,
|
|
142
121
|
source_profile,
|
|
143
|
-
delivery_mechanism:
|
|
122
|
+
delivery_mechanism: "git_deploy",
|
|
144
123
|
},
|
|
145
124
|
};
|
|
146
125
|
}
|
|
@@ -149,78 +128,9 @@ async function executeWhcDeploy(config, request, deps = {}) {
|
|
|
149
128
|
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "BUSINESS_POLICY_BLOCKED", `Concurrent deployment blocked: ${lockAcquired.reason}`);
|
|
150
129
|
}
|
|
151
130
|
try {
|
|
152
|
-
|
|
153
|
-
if (workflow_mode === "managed_clone_sync") {
|
|
154
|
-
if (!direction) {
|
|
155
|
-
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "VALIDATION_ERROR", "managed_clone_sync mode requires 'direction' field.");
|
|
156
|
-
}
|
|
157
|
-
// WHC managed sync is represented with guarded intent + recovery contract.
|
|
158
|
-
const outcome = direction === "live_to_staging" ? "synced_live_to_staging" : "synced_staging_to_live";
|
|
159
|
-
let verifyStatus = "skipped";
|
|
160
|
-
let rollbackStatus = "not_needed";
|
|
161
|
-
if (shouldVerify) {
|
|
162
|
-
const verifyResult = await verifyRunner(config, request);
|
|
163
|
-
if (!verifyResult.ok) {
|
|
164
|
-
verifyStatus = "failed";
|
|
165
|
-
if (auto_rollback_on_verify_failure) {
|
|
166
|
-
rollbackStatus = "attempted";
|
|
167
|
-
const rollbackResult = await rollbackRunner(config, request);
|
|
168
|
-
rollbackStatus = rollbackResult.ok ? "succeeded" : "failed";
|
|
169
|
-
}
|
|
170
|
-
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "TEMPORARY_UPSTREAM_ERROR", `Post-deploy verification failed: ${verifyResult.message}`);
|
|
171
|
-
}
|
|
172
|
-
verifyStatus = "passed";
|
|
173
|
-
}
|
|
174
|
-
const phaseCoverage = buildPhaseCoverage({
|
|
175
|
-
workflowMode: workflow_mode,
|
|
176
|
-
syncScope: sync_scope,
|
|
177
|
-
verifyStatus,
|
|
178
|
-
dryRun: false,
|
|
179
|
-
});
|
|
180
|
-
return {
|
|
181
|
-
ok: true,
|
|
182
|
-
action_id: actionIdFactory(),
|
|
183
|
-
tool: "whc_deploy",
|
|
184
|
-
data: {
|
|
185
|
-
outcome,
|
|
186
|
-
workflow_mode,
|
|
187
|
-
target_environment,
|
|
188
|
-
direction,
|
|
189
|
-
sync_scope,
|
|
190
|
-
verify_status: verifyStatus,
|
|
191
|
-
rollback_status: rollbackStatus,
|
|
192
|
-
lock_key: deployLockKey,
|
|
193
|
-
backup_reference,
|
|
194
|
-
pipeline_status: derivePipelineStatus(phaseCoverage),
|
|
195
|
-
phase_coverage: phaseCoverage,
|
|
196
|
-
warnings,
|
|
197
|
-
},
|
|
198
|
-
error: null,
|
|
199
|
-
meta: {
|
|
200
|
-
latency_ms: Date.now() - startedAt,
|
|
201
|
-
safety_level: effectiveSafetyLevel,
|
|
202
|
-
workflow_mode,
|
|
203
|
-
pipeline_id,
|
|
204
|
-
release_intent,
|
|
205
|
-
source_profile,
|
|
206
|
-
delivery_mechanism: "managed_sync",
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
// git_controlled mode: trigger UAPI VersionControl::deployment
|
|
211
|
-
if (!repository_root) {
|
|
212
|
-
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "VALIDATION_ERROR", "git_controlled mode requires 'repository_root' field.");
|
|
213
|
-
}
|
|
214
|
-
let deployResult;
|
|
215
|
-
try {
|
|
216
|
-
deployResult = await uapiClient.triggerDeployment(repository_root, branch);
|
|
217
|
-
}
|
|
218
|
-
catch (error) {
|
|
219
|
-
const message = error instanceof Error ? error.message : "UAPI deployment trigger failed";
|
|
220
|
-
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "AUTH_ERROR", message);
|
|
221
|
-
}
|
|
131
|
+
const deployResult = await uapiClient.triggerDeployment(repository_root, branch);
|
|
222
132
|
if (!deployResult.ok) {
|
|
223
|
-
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "TEMPORARY_UPSTREAM_ERROR", deployResult.message);
|
|
133
|
+
return buildErrorResponse(actionIdFactory(), startedAt, effectiveSafetyLevel, "TEMPORARY_UPSTREAM_ERROR", `Git deployment trigger failed: ${deployResult.message}`);
|
|
224
134
|
}
|
|
225
135
|
let verifyStatus = "skipped";
|
|
226
136
|
let rollbackStatus = "not_needed";
|
|
@@ -239,7 +149,6 @@ async function executeWhcDeploy(config, request, deps = {}) {
|
|
|
239
149
|
}
|
|
240
150
|
const phaseCoverage = buildPhaseCoverage({
|
|
241
151
|
workflowMode: workflow_mode,
|
|
242
|
-
syncScope: sync_scope,
|
|
243
152
|
verifyStatus,
|
|
244
153
|
dryRun: false,
|
|
245
154
|
});
|
|
@@ -248,12 +157,13 @@ async function executeWhcDeploy(config, request, deps = {}) {
|
|
|
248
157
|
action_id: actionIdFactory(),
|
|
249
158
|
tool: "whc_deploy",
|
|
250
159
|
data: {
|
|
251
|
-
outcome: "
|
|
160
|
+
outcome: "deployment_triggered",
|
|
252
161
|
workflow_mode,
|
|
253
162
|
target_environment,
|
|
254
163
|
repository_root,
|
|
255
164
|
branch,
|
|
256
|
-
|
|
165
|
+
direction,
|
|
166
|
+
sync_scope,
|
|
257
167
|
deployment_id: deployResult.deployment_id,
|
|
258
168
|
verify_status: verifyStatus,
|
|
259
169
|
rollback_status: rollbackStatus,
|
|
@@ -289,83 +199,29 @@ function buildErrorResponse(actionId, startedAt, safetyLevel, code, message) {
|
|
|
289
199
|
meta: { latency_ms: Date.now() - startedAt, safety_level: safetyLevel },
|
|
290
200
|
};
|
|
291
201
|
}
|
|
292
|
-
function buildDefaultLockKey(targetEnvironment, workflowMode, repositoryRoot) {
|
|
293
|
-
return `whc_deploy:${targetEnvironment}:${workflowMode}:${repositoryRoot ?? "-"}`;
|
|
294
|
-
}
|
|
295
|
-
function extractHomeOwner(pathValue) {
|
|
296
|
-
const match = pathValue.match(/^\/home\/([^\/]+)\//);
|
|
297
|
-
return match?.[1];
|
|
298
|
-
}
|
|
299
|
-
function buildSuggestedDeployPayload(request, currentUser) {
|
|
300
|
-
return {
|
|
301
|
-
request_id: request.request_id,
|
|
302
|
-
actor: request.actor,
|
|
303
|
-
dry_run: request.dry_run,
|
|
304
|
-
confirmed: request.confirmed,
|
|
305
|
-
idempotency_key: request.idempotency_key,
|
|
306
|
-
payload: {
|
|
307
|
-
...request.payload,
|
|
308
|
-
target_environment: "live",
|
|
309
|
-
repository_root: `/home/${currentUser}/public_html`,
|
|
310
|
-
},
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
function buildStagingAlternativePayload(request) {
|
|
314
|
-
return {
|
|
315
|
-
workflow_mode: "managed_clone_sync",
|
|
316
|
-
target_environment: "staging",
|
|
317
|
-
release_intent: "refresh",
|
|
318
|
-
pipeline_id: request.payload.pipeline_id,
|
|
319
|
-
source_profile: request.payload.source_profile,
|
|
320
|
-
direction: "live_to_staging",
|
|
321
|
-
sync_scope: "files",
|
|
322
|
-
verify_after_deploy: request.payload.verify_after_deploy,
|
|
323
|
-
auto_rollback_on_verify_failure: request.payload.auto_rollback_on_verify_failure,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function buildSuggestedStagingGitControlledPayload(request, stagingPath) {
|
|
327
|
-
return {
|
|
328
|
-
request_id: request.request_id,
|
|
329
|
-
actor: request.actor,
|
|
330
|
-
dry_run: request.dry_run,
|
|
331
|
-
confirmed: request.confirmed,
|
|
332
|
-
idempotency_key: request.idempotency_key,
|
|
333
|
-
payload: {
|
|
334
|
-
...request.payload,
|
|
335
|
-
target_environment: "staging",
|
|
336
|
-
repository_root: stagingPath,
|
|
337
|
-
},
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
202
|
function buildPhaseCoverage(input) {
|
|
341
203
|
const notes = [];
|
|
342
|
-
|
|
343
|
-
let runtimeState = "excluded";
|
|
344
|
-
let dataState = "excluded";
|
|
204
|
+
const codeState = input.dryRun ? "excluded" : "included";
|
|
345
205
|
const deploymentState = input.dryRun ? "excluded" : "included";
|
|
346
|
-
if (input.workflowMode === "
|
|
347
|
-
|
|
348
|
-
runtimeState = input.verifyStatus === "passed" ? "included" : "excluded";
|
|
349
|
-
dataState = "excluded";
|
|
350
|
-
notes.push("git_controlled deploy covers code transport/deployment state; data/content bootstrap is out of scope.");
|
|
206
|
+
if (input.workflowMode === "git_deploy") {
|
|
207
|
+
notes.push("git_deploy triggers a cPanel deployment task for repository-managed code delivery.", "git_deploy does not bootstrap WordPress runtime/data; use separate runtime checks or project-specific scripts for that layer.");
|
|
351
208
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
notes.push("managed_clone_sync does not bootstrap app runtime configuration (plugin/theme activation, rewrite, Woo setup).", "Managed verification is transport-level unless additional runtime checks are run externally.");
|
|
209
|
+
else {
|
|
210
|
+
notes.push("ssh_scp_wpcli is expected to cover code sync, WP-CLI runtime bootstrap, seed/bootstrap, and smoke gating.", "whc_deploy does not execute that source-specific flow yet; use a project-local pipeline script for real runs today.");
|
|
211
|
+
}
|
|
212
|
+
if (input.verifyStatus === "passed") {
|
|
213
|
+
notes.push("Post-deploy verification passed.");
|
|
214
|
+
}
|
|
215
|
+
if (input.verifyStatus === "failed") {
|
|
216
|
+
notes.push("Post-deploy verification failed.");
|
|
361
217
|
}
|
|
362
218
|
if (input.dryRun) {
|
|
363
219
|
notes.push("dry_run preview only; no target state has been changed.");
|
|
364
220
|
}
|
|
365
221
|
return {
|
|
366
222
|
code_state: codeState,
|
|
367
|
-
runtime_state:
|
|
368
|
-
data_state:
|
|
223
|
+
runtime_state: "excluded",
|
|
224
|
+
data_state: "excluded",
|
|
369
225
|
deployment_state: deploymentState,
|
|
370
226
|
notes,
|
|
371
227
|
};
|
|
@@ -21,7 +21,7 @@ async function executeWhcPipelineStatus(config, request) {
|
|
|
21
21
|
next_step: "Run whc_prepare then a write tool to initialize pipeline state.",
|
|
22
22
|
artifacts: {
|
|
23
23
|
state_file: (0, workspace_state_1.getPipelineStatusFile)(rootDir),
|
|
24
|
-
flow_log_file: config.flowLogPath ?? ".
|
|
24
|
+
flow_log_file: config.flowLogPath ?? ".whc/logs/flow-events.jsonl",
|
|
25
25
|
},
|
|
26
26
|
},
|
|
27
27
|
error: null,
|
|
@@ -57,7 +57,7 @@ async function executeWhcPipelineStatus(config, request) {
|
|
|
57
57
|
artifacts: {
|
|
58
58
|
state_file: (0, workspace_state_1.getPipelineStatusFile)(rootDir),
|
|
59
59
|
manifest_file: manifestFile,
|
|
60
|
-
flow_log_file: config.flowLogPath ?? ".
|
|
60
|
+
flow_log_file: config.flowLogPath ?? ".whc/logs/flow-events.jsonl",
|
|
61
61
|
},
|
|
62
62
|
},
|
|
63
63
|
error: null,
|
|
@@ -49,7 +49,7 @@ async function executeWhcPrepare(_config, request) {
|
|
|
49
49
|
}
|
|
50
50
|
(0, workspace_state_1.ensureWorkspaceState)(rootDir);
|
|
51
51
|
const gitignoreUpdated = request.payload.ensure_gitignore_rule !== false
|
|
52
|
-
? ensureGitignoreRule(rootDir, ".
|
|
52
|
+
? ensureGitignoreRule(rootDir, ".whc/")
|
|
53
53
|
: false;
|
|
54
54
|
if (!(0, node_fs_1.existsSync)(pipelineStatusFile) || request.payload.force_reinitialize) {
|
|
55
55
|
const payload = {
|
|
@@ -15,7 +15,7 @@ async function executeWhcSetupRemote(config, request, deps = {}) {
|
|
|
15
15
|
`Path owner mismatch: deploy_target_path belongs to '${pathOwner}' but WHC_USER is '${config.user}'.`,
|
|
16
16
|
];
|
|
17
17
|
if (isStaging) {
|
|
18
|
-
lines.push("WHC managed staging uses a separate cPanel account; git repo setup via this user's UAPI cannot target it.", "Use
|
|
18
|
+
lines.push("WHC managed staging uses a separate cPanel account; git repo setup via this user's UAPI cannot target it.", "Use git_deploy mode to drive repository-based staging refresh/deploy flows, or configure dedicated staging credentials.");
|
|
19
19
|
}
|
|
20
20
|
else {
|
|
21
21
|
const suggestedPayload = buildSuggestedSetupRemotePayload(request, config.user);
|
|
@@ -183,7 +183,8 @@ async function executeWhcSetupRemote(config, request, deps = {}) {
|
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
185
|
function buildSshRemoteHint(config, deployTargetPath) {
|
|
186
|
-
const
|
|
186
|
+
const host = config.sshTargets.prod.host || config.host;
|
|
187
|
+
const username = config.sshTargets.prod.username || config.user;
|
|
187
188
|
return `${username}@${host}:${deployTargetPath}`;
|
|
188
189
|
}
|
|
189
190
|
function inferBaselineKind(sourceKind) {
|
|
@@ -226,7 +227,7 @@ function buildSetupRemotePhaseCoverage(dryRun) {
|
|
|
226
227
|
};
|
|
227
228
|
}
|
|
228
229
|
function extractHomeOwner(pathValue) {
|
|
229
|
-
const match = pathValue.match(/^\/home\/([
|
|
230
|
+
const match = pathValue.match(/^\/home\/([^/]+)\//);
|
|
230
231
|
return match?.[1];
|
|
231
232
|
}
|
|
232
233
|
function buildSuggestedSetupRemotePayload(request, currentUser) {
|