devops-whc 1.0.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/AGENT_MCP_USAGE.md +394 -0
- package/LICENSE +15 -0
- package/README.md +208 -0
- package/WHC_MCP_REQUIREMENTS.md +112 -0
- package/dist/audit/audit-logger.js +57 -0
- package/dist/clients/ssh-client.js +199 -0
- package/dist/clients/whc-uapi-client.js +178 -0
- package/dist/clients/wpcli-client.js +125 -0
- package/dist/config/env.js +132 -0
- package/dist/contracts/deployment.js +2 -0
- package/dist/contracts/envelope.js +2 -0
- package/dist/dispatcher/tool-dispatcher.js +145 -0
- package/dist/handlers/whc-check-health.js +131 -0
- package/dist/handlers/whc-db-backup.js +111 -0
- package/dist/handlers/whc-deploy.js +381 -0
- package/dist/handlers/whc-get-logs.js +108 -0
- package/dist/handlers/whc-pipeline-status.js +96 -0
- package/dist/handlers/whc-prepare.js +127 -0
- package/dist/handlers/whc-rollback.js +141 -0
- package/dist/handlers/whc-setup-remote.js +262 -0
- package/dist/handlers/whc-ssh-exec.js +138 -0
- package/dist/handlers/whc-verify.js +304 -0
- package/dist/idempotency/store.js +13 -0
- package/dist/index.js +109 -0
- package/dist/policy/policy-engine.js +41 -0
- package/dist/probes/connectivity.js +41 -0
- package/dist/registry/tool-registry.js +69 -0
- package/dist/schemas/whc-check-health.js +55 -0
- package/dist/schemas/whc-db-backup.js +29 -0
- package/dist/schemas/whc-deploy.js +66 -0
- package/dist/schemas/whc-get-logs.js +25 -0
- package/dist/schemas/whc-pipeline-status.js +24 -0
- package/dist/schemas/whc-prepare.js +29 -0
- package/dist/schemas/whc-rollback.js +58 -0
- package/dist/schemas/whc-setup-remote.js +60 -0
- package/dist/schemas/whc-ssh-exec.js +117 -0
- package/dist/schemas/whc-verify.js +28 -0
- package/dist/server-entry.js +8 -0
- package/dist/server.js +381 -0
- package/dist/services/deploy-runtime-ops.js +104 -0
- package/dist/services/deployment-locks.js +34 -0
- package/dist/state/workspace-state.js +201 -0
- package/package.json +48 -0
- package/scripts/prepare-first-time.cjs +75 -0
- package/scripts/start-mcp.cjs +42 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMcpServer = createMcpServer;
|
|
4
|
+
exports.startMcpServer = startMcpServer;
|
|
5
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
6
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const env_js_1 = require("./config/env.js");
|
|
10
|
+
const tool_dispatcher_js_1 = require("./dispatcher/tool-dispatcher.js");
|
|
11
|
+
const store_js_1 = require("./idempotency/store.js");
|
|
12
|
+
const audit_logger_js_1 = require("./audit/audit-logger.js");
|
|
13
|
+
const tool_registry_js_1 = require("./registry/tool-registry.js");
|
|
14
|
+
// Handlers
|
|
15
|
+
const whc_check_health_js_1 = require("./handlers/whc-check-health.js");
|
|
16
|
+
const whc_setup_remote_js_1 = require("./handlers/whc-setup-remote.js");
|
|
17
|
+
const whc_deploy_js_1 = require("./handlers/whc-deploy.js");
|
|
18
|
+
const whc_get_logs_js_1 = require("./handlers/whc-get-logs.js");
|
|
19
|
+
const whc_ssh_exec_js_1 = require("./handlers/whc-ssh-exec.js");
|
|
20
|
+
const whc_db_backup_js_1 = require("./handlers/whc-db-backup.js");
|
|
21
|
+
const whc_prepare_js_1 = require("./handlers/whc-prepare.js");
|
|
22
|
+
const whc_verify_js_1 = require("./handlers/whc-verify.js");
|
|
23
|
+
const whc_pipeline_status_js_1 = require("./handlers/whc-pipeline-status.js");
|
|
24
|
+
const whc_rollback_js_1 = require("./handlers/whc-rollback.js");
|
|
25
|
+
// Validators
|
|
26
|
+
const whc_check_health_js_2 = require("./schemas/whc-check-health.js");
|
|
27
|
+
const whc_setup_remote_js_2 = require("./schemas/whc-setup-remote.js");
|
|
28
|
+
const whc_deploy_js_2 = require("./schemas/whc-deploy.js");
|
|
29
|
+
const whc_get_logs_js_2 = require("./schemas/whc-get-logs.js");
|
|
30
|
+
const whc_ssh_exec_js_2 = require("./schemas/whc-ssh-exec.js");
|
|
31
|
+
const whc_db_backup_js_2 = require("./schemas/whc-db-backup.js");
|
|
32
|
+
const whc_prepare_js_2 = require("./schemas/whc-prepare.js");
|
|
33
|
+
const whc_verify_js_2 = require("./schemas/whc-verify.js");
|
|
34
|
+
const whc_pipeline_status_js_2 = require("./schemas/whc-pipeline-status.js");
|
|
35
|
+
const whc_rollback_js_2 = require("./schemas/whc-rollback.js");
|
|
36
|
+
// --- Shared infrastructure ---
|
|
37
|
+
const idempotencyStore = new store_js_1.InMemoryIdempotencyStore();
|
|
38
|
+
function resultToContent(result) {
|
|
39
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
40
|
+
}
|
|
41
|
+
// --- MCP Server setup ---
|
|
42
|
+
function createMcpServer() {
|
|
43
|
+
const config = (0, env_js_1.loadConfig)();
|
|
44
|
+
const flowLogPath = config.flowLogPath ?? ".mcp/whc-mcp/logs/flow-events.jsonl";
|
|
45
|
+
const auditLogger = new audit_logger_js_1.CompositeAuditLogger([
|
|
46
|
+
new audit_logger_js_1.ConsoleAuditLogger(),
|
|
47
|
+
new audit_logger_js_1.FileAuditLogger(flowLogPath),
|
|
48
|
+
]);
|
|
49
|
+
const instanceName = (process.env.WHC_MCP_INSTANCE_NAME ?? "").trim() || "whc-mcp";
|
|
50
|
+
const server = new mcp_js_1.McpServer({
|
|
51
|
+
name: instanceName,
|
|
52
|
+
version: "1.0.1",
|
|
53
|
+
});
|
|
54
|
+
// ── whc_prepare ───────────────────────────────────────────────────────────
|
|
55
|
+
server.registerTool("whc_prepare", {
|
|
56
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_prepare"].description,
|
|
57
|
+
inputSchema: zod_1.z.object({
|
|
58
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
59
|
+
actor: zod_1.z
|
|
60
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
61
|
+
.default({ kind: "copilot" }),
|
|
62
|
+
dry_run: zod_1.z.boolean().optional(),
|
|
63
|
+
confirmed: zod_1.z.boolean().optional(),
|
|
64
|
+
idempotency_key: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
65
|
+
payload: zod_1.z
|
|
66
|
+
.object({
|
|
67
|
+
workspace_root: zod_1.z.string().min(1).optional(),
|
|
68
|
+
target_environment: zod_1.z.enum(["live", "staging"]).optional(),
|
|
69
|
+
ensure_gitignore_rule: zod_1.z.boolean().default(true),
|
|
70
|
+
force_reinitialize: zod_1.z.boolean().default(false),
|
|
71
|
+
})
|
|
72
|
+
.default({ ensure_gitignore_rule: true, force_reinitialize: false }),
|
|
73
|
+
}),
|
|
74
|
+
}, async (args) => {
|
|
75
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
76
|
+
config,
|
|
77
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_prepare"],
|
|
78
|
+
rawInput: args,
|
|
79
|
+
validate: whc_prepare_js_2.validateWhcPrepareRequest,
|
|
80
|
+
execute: (cfg, req) => (0, whc_prepare_js_1.executeWhcPrepare)(cfg, req),
|
|
81
|
+
idempotencyStore,
|
|
82
|
+
auditLogger,
|
|
83
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
84
|
+
});
|
|
85
|
+
return resultToContent(result);
|
|
86
|
+
});
|
|
87
|
+
// ── whc_check_health ──────────────────────────────────────────────────────
|
|
88
|
+
server.registerTool("whc_check_health", {
|
|
89
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_check_health"].description,
|
|
90
|
+
inputSchema: zod_1.z.object({
|
|
91
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
92
|
+
actor: zod_1.z
|
|
93
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
94
|
+
.default({ kind: "copilot" }),
|
|
95
|
+
payload: zod_1.z
|
|
96
|
+
.object({ target_environment: zod_1.z.enum(["live", "staging"]).default("live") })
|
|
97
|
+
.default({ target_environment: "live" }),
|
|
98
|
+
}),
|
|
99
|
+
}, async (args) => {
|
|
100
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
101
|
+
config,
|
|
102
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_check_health"],
|
|
103
|
+
rawInput: args,
|
|
104
|
+
validate: whc_check_health_js_2.validateWhcCheckHealthRequest,
|
|
105
|
+
execute: (cfg, req) => (0, whc_check_health_js_1.executeWhcCheckHealth)(cfg, req),
|
|
106
|
+
idempotencyStore,
|
|
107
|
+
auditLogger,
|
|
108
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
109
|
+
});
|
|
110
|
+
return resultToContent(result);
|
|
111
|
+
});
|
|
112
|
+
// ── whc_setup_remote ──────────────────────────────────────────────────────
|
|
113
|
+
server.registerTool("whc_setup_remote", {
|
|
114
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_setup_remote"].description,
|
|
115
|
+
inputSchema: zod_1.z.object({
|
|
116
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
117
|
+
actor: zod_1.z
|
|
118
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
119
|
+
.default({ kind: "copilot" }),
|
|
120
|
+
dry_run: zod_1.z.boolean().optional(),
|
|
121
|
+
confirmed: zod_1.z.boolean().optional(),
|
|
122
|
+
idempotency_key: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
123
|
+
payload: zod_1.z.object({
|
|
124
|
+
target_environment: zod_1.z.enum(["live", "staging"]),
|
|
125
|
+
release_intent: zod_1.z.enum(["refresh", "deploy", "promote", "migrate", "recover"]),
|
|
126
|
+
pipeline_id: zod_1.z.enum(["P0", "P1", "P2", "P2R", "P3", "P3D", "P4", "P5"]),
|
|
127
|
+
source_profile: zod_1.z.object({
|
|
128
|
+
source_kind: zod_1.z.enum(["full_site", "partial_content", "package_only", "artifact_first", "monorepo_slice"]),
|
|
129
|
+
deploy_unit: zod_1.z.enum(["raw_source", "build_artifact", "package_bundle"]),
|
|
130
|
+
}),
|
|
131
|
+
deploy_target_path: zod_1.z.string().min(1),
|
|
132
|
+
clone_url: zod_1.z.string().url().optional(),
|
|
133
|
+
}),
|
|
134
|
+
}),
|
|
135
|
+
}, async (args) => {
|
|
136
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
137
|
+
config,
|
|
138
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_setup_remote"],
|
|
139
|
+
rawInput: args,
|
|
140
|
+
validate: whc_setup_remote_js_2.validateWhcSetupRemoteRequest,
|
|
141
|
+
execute: (cfg, req) => (0, whc_setup_remote_js_1.executeWhcSetupRemote)(cfg, req),
|
|
142
|
+
idempotencyStore,
|
|
143
|
+
auditLogger,
|
|
144
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
145
|
+
});
|
|
146
|
+
return resultToContent(result);
|
|
147
|
+
});
|
|
148
|
+
// ── whc_deploy ────────────────────────────────────────────────────────────
|
|
149
|
+
server.registerTool("whc_deploy", {
|
|
150
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_deploy"].description,
|
|
151
|
+
inputSchema: zod_1.z.object({
|
|
152
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
153
|
+
actor: zod_1.z
|
|
154
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
155
|
+
.default({ kind: "copilot" }),
|
|
156
|
+
dry_run: zod_1.z.boolean().optional(),
|
|
157
|
+
confirmed: zod_1.z.boolean().optional(),
|
|
158
|
+
idempotency_key: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
159
|
+
payload: zod_1.z.object({
|
|
160
|
+
workflow_mode: zod_1.z.enum(["managed_clone_sync", "git_controlled"]),
|
|
161
|
+
target_environment: zod_1.z.enum(["live", "staging"]),
|
|
162
|
+
release_intent: zod_1.z.enum(["refresh", "deploy", "promote", "migrate", "recover"]),
|
|
163
|
+
pipeline_id: zod_1.z.enum(["P0", "P1", "P2", "P2R", "P3", "P3D", "P4", "P5"]),
|
|
164
|
+
source_profile: zod_1.z.object({
|
|
165
|
+
source_kind: zod_1.z.enum(["full_site", "partial_content", "package_only", "artifact_first", "monorepo_slice"]),
|
|
166
|
+
deploy_unit: zod_1.z.enum(["raw_source", "build_artifact", "package_bundle"]),
|
|
167
|
+
}),
|
|
168
|
+
direction: zod_1.z.enum(["live_to_staging", "staging_to_live"]).optional(),
|
|
169
|
+
sync_scope: zod_1.z.enum(["files", "database", "everything"]).optional(),
|
|
170
|
+
repository_root: zod_1.z.string().min(1).optional(),
|
|
171
|
+
branch: zod_1.z.string().min(1).optional(),
|
|
172
|
+
rollback_branch: zod_1.z.string().min(1).optional(),
|
|
173
|
+
backup_reference: zod_1.z.string().min(1).optional(),
|
|
174
|
+
allow_emergency_without_backup: zod_1.z.boolean().optional(),
|
|
175
|
+
verify_after_deploy: zod_1.z.boolean().optional(),
|
|
176
|
+
auto_rollback_on_verify_failure: zod_1.z.boolean().optional(),
|
|
177
|
+
lock_key: zod_1.z.string().min(1).optional(),
|
|
178
|
+
}),
|
|
179
|
+
}),
|
|
180
|
+
}, async (args) => {
|
|
181
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
182
|
+
config,
|
|
183
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_deploy"],
|
|
184
|
+
rawInput: args,
|
|
185
|
+
validate: whc_deploy_js_2.validateWhcDeployRequest,
|
|
186
|
+
execute: (cfg, req) => (0, whc_deploy_js_1.executeWhcDeploy)(cfg, req),
|
|
187
|
+
idempotencyStore,
|
|
188
|
+
auditLogger,
|
|
189
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
190
|
+
});
|
|
191
|
+
return resultToContent(result);
|
|
192
|
+
});
|
|
193
|
+
// ── whc_get_logs ──────────────────────────────────────────────────────────
|
|
194
|
+
server.registerTool("whc_get_logs", {
|
|
195
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_get_logs"].description,
|
|
196
|
+
inputSchema: zod_1.z.object({
|
|
197
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
198
|
+
actor: zod_1.z
|
|
199
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
200
|
+
.default({ kind: "copilot" }),
|
|
201
|
+
payload: zod_1.z.object({
|
|
202
|
+
target_environment: zod_1.z.enum(["live", "staging"]),
|
|
203
|
+
log_type: zod_1.z.enum(["apache_error", "apache_access", "cron", "php_error"]).default("apache_error"),
|
|
204
|
+
lines: zod_1.z.number().int().min(1).max(500).default(50),
|
|
205
|
+
}),
|
|
206
|
+
}),
|
|
207
|
+
}, async (args) => {
|
|
208
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
209
|
+
config,
|
|
210
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_get_logs"],
|
|
211
|
+
rawInput: args,
|
|
212
|
+
validate: whc_get_logs_js_2.validateWhcGetLogsRequest,
|
|
213
|
+
execute: (cfg, req) => (0, whc_get_logs_js_1.executeWhcGetLogs)(cfg, req),
|
|
214
|
+
idempotencyStore,
|
|
215
|
+
auditLogger,
|
|
216
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
217
|
+
});
|
|
218
|
+
return resultToContent(result);
|
|
219
|
+
});
|
|
220
|
+
// ── whc_ssh_exec ──────────────────────────────────────────────────────────
|
|
221
|
+
server.registerTool("whc_ssh_exec", {
|
|
222
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_ssh_exec"].description,
|
|
223
|
+
inputSchema: zod_1.z.object({
|
|
224
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
225
|
+
actor: zod_1.z
|
|
226
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
227
|
+
.default({ kind: "copilot" }),
|
|
228
|
+
dry_run: zod_1.z.boolean().optional(),
|
|
229
|
+
confirmed: zod_1.z.boolean().optional(),
|
|
230
|
+
idempotency_key: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
231
|
+
payload: zod_1.z.object({
|
|
232
|
+
target_environment: zod_1.z.enum(["live", "staging"]),
|
|
233
|
+
raw_command: zod_1.z.string().min(1).max(1024),
|
|
234
|
+
working_dir: zod_1.z.string().min(1).optional(),
|
|
235
|
+
}),
|
|
236
|
+
}),
|
|
237
|
+
}, async (args) => {
|
|
238
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
239
|
+
config,
|
|
240
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_ssh_exec"],
|
|
241
|
+
rawInput: args,
|
|
242
|
+
validate: whc_ssh_exec_js_2.validateWhcSshExecRequest,
|
|
243
|
+
execute: (cfg, req) => (0, whc_ssh_exec_js_1.executeWhcSshExec)(cfg, req),
|
|
244
|
+
idempotencyStore,
|
|
245
|
+
auditLogger,
|
|
246
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
247
|
+
});
|
|
248
|
+
return resultToContent(result);
|
|
249
|
+
});
|
|
250
|
+
// ── whc_db_backup ─────────────────────────────────────────────────────────
|
|
251
|
+
server.registerTool("whc_db_backup", {
|
|
252
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_db_backup"].description,
|
|
253
|
+
inputSchema: zod_1.z.object({
|
|
254
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
255
|
+
actor: zod_1.z
|
|
256
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
257
|
+
.default({ kind: "copilot" }),
|
|
258
|
+
dry_run: zod_1.z.boolean().optional(),
|
|
259
|
+
confirmed: zod_1.z.boolean().optional(),
|
|
260
|
+
idempotency_key: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
261
|
+
payload: zod_1.z.object({
|
|
262
|
+
target_environment: zod_1.z.enum(["live", "staging"]),
|
|
263
|
+
output_path: zod_1.z.string().min(1).optional(),
|
|
264
|
+
compress: zod_1.z.boolean().default(true),
|
|
265
|
+
tables: zod_1.z.array(zod_1.z.string().min(1)).optional(),
|
|
266
|
+
}),
|
|
267
|
+
}),
|
|
268
|
+
}, async (args) => {
|
|
269
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
270
|
+
config,
|
|
271
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_db_backup"],
|
|
272
|
+
rawInput: args,
|
|
273
|
+
validate: whc_db_backup_js_2.validateWhcDbBackupRequest,
|
|
274
|
+
execute: (cfg, req) => (0, whc_db_backup_js_1.executeWhcDbBackup)(cfg, req),
|
|
275
|
+
idempotencyStore,
|
|
276
|
+
auditLogger,
|
|
277
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
278
|
+
});
|
|
279
|
+
return resultToContent(result);
|
|
280
|
+
});
|
|
281
|
+
// ── whc_verify ────────────────────────────────────────────────────────────
|
|
282
|
+
server.registerTool("whc_verify", {
|
|
283
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_verify"].description,
|
|
284
|
+
inputSchema: zod_1.z.object({
|
|
285
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
286
|
+
actor: zod_1.z
|
|
287
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
288
|
+
.default({ kind: "copilot" }),
|
|
289
|
+
payload: zod_1.z.object({
|
|
290
|
+
target_environment: zod_1.z.enum(["live", "staging"]),
|
|
291
|
+
release_id: zod_1.z.string().min(1).optional(),
|
|
292
|
+
manifest_path: zod_1.z.string().min(1).optional(),
|
|
293
|
+
verify_level: zod_1.z.enum(["default", "with_db"]).default("default"),
|
|
294
|
+
random_sample_size: zod_1.z.number().int().min(1).max(1000).optional(),
|
|
295
|
+
random_seed: zod_1.z.string().min(1).optional(),
|
|
296
|
+
}),
|
|
297
|
+
}),
|
|
298
|
+
}, async (args) => {
|
|
299
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
300
|
+
config,
|
|
301
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_verify"],
|
|
302
|
+
rawInput: args,
|
|
303
|
+
validate: whc_verify_js_2.validateWhcVerifyRequest,
|
|
304
|
+
execute: (cfg, req) => (0, whc_verify_js_1.executeWhcVerify)(cfg, req),
|
|
305
|
+
idempotencyStore,
|
|
306
|
+
auditLogger,
|
|
307
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
308
|
+
});
|
|
309
|
+
return resultToContent(result);
|
|
310
|
+
});
|
|
311
|
+
// ── whc_pipeline_status ───────────────────────────────────────────────────
|
|
312
|
+
server.registerTool("whc_pipeline_status", {
|
|
313
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_pipeline_status"].description,
|
|
314
|
+
inputSchema: zod_1.z.object({
|
|
315
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
316
|
+
actor: zod_1.z
|
|
317
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
318
|
+
.default({ kind: "copilot" }),
|
|
319
|
+
payload: zod_1.z
|
|
320
|
+
.object({
|
|
321
|
+
workspace_root: zod_1.z.string().min(1).optional(),
|
|
322
|
+
request_id: zod_1.z.string().min(1).optional(),
|
|
323
|
+
})
|
|
324
|
+
.default({}),
|
|
325
|
+
}),
|
|
326
|
+
}, async (args) => {
|
|
327
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
328
|
+
config,
|
|
329
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_pipeline_status"],
|
|
330
|
+
rawInput: args,
|
|
331
|
+
validate: whc_pipeline_status_js_2.validateWhcPipelineStatusRequest,
|
|
332
|
+
execute: (cfg, req) => (0, whc_pipeline_status_js_1.executeWhcPipelineStatus)(cfg, req),
|
|
333
|
+
idempotencyStore,
|
|
334
|
+
auditLogger,
|
|
335
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
336
|
+
});
|
|
337
|
+
return resultToContent(result);
|
|
338
|
+
});
|
|
339
|
+
// ── whc_rollback ──────────────────────────────────────────────────────────
|
|
340
|
+
server.registerTool("whc_rollback", {
|
|
341
|
+
description: tool_registry_js_1.TOOL_REGISTRY["whc_rollback"].description,
|
|
342
|
+
inputSchema: zod_1.z.object({
|
|
343
|
+
request_id: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
344
|
+
actor: zod_1.z
|
|
345
|
+
.object({ kind: zod_1.z.literal("copilot"), user_hint: zod_1.z.string().optional() })
|
|
346
|
+
.default({ kind: "copilot" }),
|
|
347
|
+
dry_run: zod_1.z.boolean().optional(),
|
|
348
|
+
confirmed: zod_1.z.boolean().optional(),
|
|
349
|
+
idempotency_key: zod_1.z.string().default(() => (0, node_crypto_1.randomUUID)()),
|
|
350
|
+
payload: zod_1.z.object({
|
|
351
|
+
target_environment: zod_1.z.enum(["live", "staging"]),
|
|
352
|
+
rollback_mode: zod_1.z.enum(["git_branch", "db_backup", "managed_sync_reverse"]),
|
|
353
|
+
rollback_branch: zod_1.z.string().min(1).optional(),
|
|
354
|
+
backup_reference: zod_1.z.string().min(1).optional(),
|
|
355
|
+
direction: zod_1.z.enum(["live_to_staging", "staging_to_live"]).optional(),
|
|
356
|
+
reason: zod_1.z.string().min(1),
|
|
357
|
+
verify_release_id: zod_1.z.string().min(1).optional(),
|
|
358
|
+
verify_report_path: zod_1.z.string().min(1).optional(),
|
|
359
|
+
repository_root: zod_1.z.string().min(1).optional(),
|
|
360
|
+
}),
|
|
361
|
+
}),
|
|
362
|
+
}, async (args) => {
|
|
363
|
+
const result = await (0, tool_dispatcher_js_1.dispatch)({
|
|
364
|
+
config,
|
|
365
|
+
toolDef: tool_registry_js_1.TOOL_REGISTRY["whc_rollback"],
|
|
366
|
+
rawInput: args,
|
|
367
|
+
validate: whc_rollback_js_2.validateWhcRollbackRequest,
|
|
368
|
+
execute: (cfg, req) => (0, whc_rollback_js_1.executeWhcRollback)(cfg, req),
|
|
369
|
+
idempotencyStore,
|
|
370
|
+
auditLogger,
|
|
371
|
+
actionIdFactory: node_crypto_1.randomUUID,
|
|
372
|
+
});
|
|
373
|
+
return resultToContent(result);
|
|
374
|
+
});
|
|
375
|
+
return server;
|
|
376
|
+
}
|
|
377
|
+
async function startMcpServer() {
|
|
378
|
+
const server = createMcpServer();
|
|
379
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
380
|
+
await server.connect(transport);
|
|
381
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDeployVerification = runDeployVerification;
|
|
4
|
+
exports.runDeployRollback = runDeployRollback;
|
|
5
|
+
async function runDeployVerification(config, request, sshClient) {
|
|
6
|
+
const payload = request.payload;
|
|
7
|
+
if (payload.workflow_mode !== "git_controlled") {
|
|
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})` };
|
|
45
|
+
}
|
|
46
|
+
async function runDeployRollback(_config, request, uapiClient) {
|
|
47
|
+
const payload = request.payload;
|
|
48
|
+
if (payload.workflow_mode !== "git_controlled") {
|
|
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
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
value: {
|
|
98
|
+
host: staging.host,
|
|
99
|
+
port: staging.port,
|
|
100
|
+
username: staging.username,
|
|
101
|
+
privateKeyPath: staging.privateKeyPath,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InMemoryDeploymentLockService = void 0;
|
|
4
|
+
class InMemoryDeploymentLockService {
|
|
5
|
+
locks = new Map();
|
|
6
|
+
acquire(key, owner, ttlMs = 5 * 60 * 1000) {
|
|
7
|
+
const now = Date.now();
|
|
8
|
+
const existing = this.locks.get(key);
|
|
9
|
+
if (existing && existing.expiresAt > now) {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
reason: `Deployment lock already held by ${existing.owner}`,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const lock = {
|
|
16
|
+
key,
|
|
17
|
+
owner,
|
|
18
|
+
acquiredAt: now,
|
|
19
|
+
expiresAt: now + ttlMs,
|
|
20
|
+
};
|
|
21
|
+
this.locks.set(key, lock);
|
|
22
|
+
return { ok: true, lock };
|
|
23
|
+
}
|
|
24
|
+
release(key, owner) {
|
|
25
|
+
const existing = this.locks.get(key);
|
|
26
|
+
if (!existing) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (existing.owner === owner || existing.expiresAt <= Date.now()) {
|
|
30
|
+
this.locks.delete(key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.InMemoryDeploymentLockService = InMemoryDeploymentLockService;
|