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
package/dist/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
7
|
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
5
11
|
const env_1 = require("./config/env");
|
|
6
12
|
const tool_dispatcher_1 = require("./dispatcher/tool-dispatcher");
|
|
7
13
|
const whc_check_health_1 = require("./handlers/whc-check-health");
|
|
@@ -11,14 +17,16 @@ const tool_registry_1 = require("./registry/tool-registry");
|
|
|
11
17
|
const store_1 = require("./idempotency/store");
|
|
12
18
|
const audit_logger_1 = require("./audit/audit-logger");
|
|
13
19
|
const server_1 = require("./server");
|
|
14
|
-
const
|
|
15
|
-
const
|
|
20
|
+
const source_compatibility_1 = require("./probes/source-compatibility");
|
|
21
|
+
const workspace_state_1 = require("./state/workspace-state");
|
|
22
|
+
const APP_VERSION = process.env.npm_package_version ?? "0.0.0";
|
|
16
23
|
const HELP_TEXT = [
|
|
17
|
-
`WHC
|
|
24
|
+
`WHC CLI Help v${APP_VERSION}`,
|
|
18
25
|
"",
|
|
19
26
|
"Modes:",
|
|
20
|
-
" --serve Start
|
|
27
|
+
" --serve Start stdio tool server (registers all tools)",
|
|
21
28
|
" --probe Run connectivity probe (UAPI, SSH, WP-CLI)",
|
|
29
|
+
" --check-generic Run one-shot deploy readiness check for a specific project folder",
|
|
22
30
|
" --check-health Execute whc_check_health once and print JSON",
|
|
23
31
|
" --version Print CLI version",
|
|
24
32
|
" --help Show this help",
|
|
@@ -26,12 +34,13 @@ const HELP_TEXT = [
|
|
|
26
34
|
"Quick start:",
|
|
27
35
|
" 1) npm install",
|
|
28
36
|
" 2) copy .env.example to .env and fill secrets",
|
|
29
|
-
" 3) npm run
|
|
37
|
+
" 3) npm run prepare",
|
|
30
38
|
" 4) npm run serve",
|
|
31
39
|
"",
|
|
32
40
|
"Published package use:",
|
|
33
41
|
" npx -y devops-whc --serve",
|
|
34
42
|
" npx -y devops-whc --probe",
|
|
43
|
+
" npx -y devops-whc --check-generic --project-root D:/Source/mytho/source/wordpress --workflow-mode ssh_scp_wpcli",
|
|
35
44
|
" npx -y devops-whc --check-health",
|
|
36
45
|
"",
|
|
37
46
|
"Safe release flow:",
|
|
@@ -42,13 +51,17 @@ const HELP_TEXT = [
|
|
|
42
51
|
" 5) smoke gate",
|
|
43
52
|
" 6) promote live only when smoke is green",
|
|
44
53
|
"",
|
|
45
|
-
"
|
|
46
|
-
" -
|
|
47
|
-
" -
|
|
48
|
-
" -
|
|
54
|
+
"Git deploy payload reminders:",
|
|
55
|
+
" - repository_root is required",
|
|
56
|
+
" - branch is optional; omitted means deploy current configured HEAD",
|
|
57
|
+
" - run whc_setup_remote + local git push before whc_deploy",
|
|
58
|
+
"",
|
|
59
|
+
"ssh_scp_wpcli reminders:",
|
|
60
|
+
" - check-generic validates mytho/source-style doctor prerequisites",
|
|
61
|
+
" - whc_deploy currently returns guidance/dry-run only for this mode",
|
|
49
62
|
"",
|
|
50
63
|
"Flow log:",
|
|
51
|
-
" - default: .
|
|
64
|
+
" - default: .whc/logs/flow-events.jsonl",
|
|
52
65
|
" - override: set WHC_FLOW_LOG_PATH",
|
|
53
66
|
"",
|
|
54
67
|
"Secrets safety:",
|
|
@@ -56,7 +69,7 @@ const HELP_TEXT = [
|
|
|
56
69
|
" - npm tarball excludes local env files by design",
|
|
57
70
|
"",
|
|
58
71
|
"Reference:",
|
|
59
|
-
" -
|
|
72
|
+
" - AGENT_USAGE.md",
|
|
60
73
|
].join("\n");
|
|
61
74
|
async function main() {
|
|
62
75
|
if (process.argv.includes("--help")) {
|
|
@@ -67,13 +80,13 @@ async function main() {
|
|
|
67
80
|
console.log(APP_VERSION);
|
|
68
81
|
return;
|
|
69
82
|
}
|
|
70
|
-
//
|
|
83
|
+
// Stdio server mode — all tools registered, reads from stdin/stdout
|
|
71
84
|
if (process.argv.includes("--serve")) {
|
|
72
|
-
await (0, server_1.
|
|
85
|
+
await (0, server_1.startWhcToolServer)();
|
|
73
86
|
return;
|
|
74
87
|
}
|
|
75
|
-
const config = (0, env_1.loadConfig)();
|
|
76
88
|
if (process.argv.includes("--probe")) {
|
|
89
|
+
const config = (0, env_1.loadConfig)();
|
|
77
90
|
const report = await (0, connectivity_1.runConnectivityProbe)(config);
|
|
78
91
|
const stagingWpCliOk = report.wpcli.staging.reachable;
|
|
79
92
|
console.log(JSON.stringify({
|
|
@@ -84,7 +97,187 @@ async function main() {
|
|
|
84
97
|
}, null, 2));
|
|
85
98
|
return;
|
|
86
99
|
}
|
|
100
|
+
if (process.argv.includes("--check-generic")) {
|
|
101
|
+
const projectRootArg = readCliOption("--project-root");
|
|
102
|
+
if (!projectRootArg) {
|
|
103
|
+
console.log(JSON.stringify({
|
|
104
|
+
ok: false,
|
|
105
|
+
ready_for_deploy: false,
|
|
106
|
+
status: "blocked",
|
|
107
|
+
next_actions: [
|
|
108
|
+
"Re-run with --project-root <folder> so WHC can bootstrap hidden state for that specific project.",
|
|
109
|
+
],
|
|
110
|
+
action_id: (0, node_crypto_1.randomUUID)(),
|
|
111
|
+
tool: "whc_source_compatibility_check",
|
|
112
|
+
data: {
|
|
113
|
+
ok: false,
|
|
114
|
+
ready_for_deploy: false,
|
|
115
|
+
status: "blocked",
|
|
116
|
+
blockers: ["Missing required CLI argument: --project-root"],
|
|
117
|
+
warnings: [],
|
|
118
|
+
next_actions: [
|
|
119
|
+
"Use --project-root <folder> to tell WHC which deploy workspace to initialize and check.",
|
|
120
|
+
],
|
|
121
|
+
checks: [
|
|
122
|
+
{
|
|
123
|
+
id: "project-root-argument",
|
|
124
|
+
status: "fail",
|
|
125
|
+
message: "Project root folder was not provided.",
|
|
126
|
+
hint: "Example: npx -y devops-whc --check-generic --project-root D:/Source/mytho/source/wordpress --workflow-mode ssh_scp_wpcli",
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
}, null, 2));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const projectRoot = node_path_1.default.resolve(projectRootArg);
|
|
134
|
+
const appPathArg = readCliOption("--app-path");
|
|
135
|
+
const effectiveAppPath = appPathArg ?? ".";
|
|
136
|
+
const appRoot = node_path_1.default.resolve(projectRoot, effectiveAppPath);
|
|
137
|
+
if (!(0, node_fs_1.existsSync)(appRoot)) {
|
|
138
|
+
console.log(JSON.stringify({
|
|
139
|
+
ok: false,
|
|
140
|
+
ready_for_deploy: false,
|
|
141
|
+
status: "blocked",
|
|
142
|
+
next_actions: [
|
|
143
|
+
"Fix --app-path so it points to an existing application directory, then rerun check-generic.",
|
|
144
|
+
],
|
|
145
|
+
action_id: (0, node_crypto_1.randomUUID)(),
|
|
146
|
+
tool: "whc_source_compatibility_check",
|
|
147
|
+
data: {
|
|
148
|
+
ok: false,
|
|
149
|
+
ready_for_deploy: false,
|
|
150
|
+
status: "blocked",
|
|
151
|
+
paths: {
|
|
152
|
+
local_project_root: projectRoot,
|
|
153
|
+
app_root: appRoot,
|
|
154
|
+
wp_content_path: node_path_1.default.resolve(appRoot, "wp-content"),
|
|
155
|
+
},
|
|
156
|
+
blockers: [`Application path does not exist: ${appRoot}`],
|
|
157
|
+
warnings: [],
|
|
158
|
+
next_actions: [
|
|
159
|
+
"Fix --app-path so it points to an existing application directory, then rerun check-generic.",
|
|
160
|
+
],
|
|
161
|
+
checks: [
|
|
162
|
+
{
|
|
163
|
+
id: "app-root",
|
|
164
|
+
status: "fail",
|
|
165
|
+
message: "Application root path is missing.",
|
|
166
|
+
details: appRoot,
|
|
167
|
+
hint: "User must point WHC at the correct project-root/app-path. Hidden WHC files are not initialized until the effective app root is valid.",
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
}, null, 2));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const bootstrap = (0, workspace_state_1.ensureWorkspaceBootstrap)(appRoot, {
|
|
175
|
+
localProjectRoot: projectRoot,
|
|
176
|
+
localAppPath: appPathArg,
|
|
177
|
+
});
|
|
178
|
+
const projectEnv = readProjectEnv(bootstrap.envFile);
|
|
179
|
+
const mergedEnv = {
|
|
180
|
+
...withoutWhcEnv(process.env),
|
|
181
|
+
...projectEnv,
|
|
182
|
+
WHC_LOCAL_PROJECT_ROOT: projectRoot,
|
|
183
|
+
};
|
|
184
|
+
if (appPathArg) {
|
|
185
|
+
mergedEnv.WHC_LOCAL_APP_PATH = appPathArg;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
mergedEnv.WHC_LOCAL_APP_PATH = "";
|
|
189
|
+
}
|
|
190
|
+
const workflowModeArg = readCliOption("--workflow-mode");
|
|
191
|
+
if (workflowModeArg) {
|
|
192
|
+
mergedEnv.WHC_WORKFLOW_MODE = workflowModeArg;
|
|
193
|
+
}
|
|
194
|
+
let config;
|
|
195
|
+
try {
|
|
196
|
+
config = (0, env_1.loadConfig)(mergedEnv);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const message = error instanceof Error ? error.message : "Unknown config error";
|
|
200
|
+
const missingKeys = extractMissingEnvKeys(message);
|
|
201
|
+
console.log(JSON.stringify({
|
|
202
|
+
ok: false,
|
|
203
|
+
ready_for_deploy: false,
|
|
204
|
+
status: "blocked",
|
|
205
|
+
next_actions: [
|
|
206
|
+
`Fill the missing credentials in ${bootstrap.envFile}.`,
|
|
207
|
+
"Re-run check-generic after updating the project-local WHC env file.",
|
|
208
|
+
],
|
|
209
|
+
action_id: (0, node_crypto_1.randomUUID)(),
|
|
210
|
+
tool: "whc_source_compatibility_check",
|
|
211
|
+
data: {
|
|
212
|
+
ok: false,
|
|
213
|
+
ready_for_deploy: false,
|
|
214
|
+
status: "blocked",
|
|
215
|
+
paths: {
|
|
216
|
+
local_project_root: projectRoot,
|
|
217
|
+
app_root: appRoot,
|
|
218
|
+
wp_content_path: node_path_1.default.resolve(appRoot, "wp-content"),
|
|
219
|
+
},
|
|
220
|
+
bootstrap: {
|
|
221
|
+
state_root: bootstrap.stateRootRelative,
|
|
222
|
+
created_paths: bootstrap.createdPaths,
|
|
223
|
+
pipeline_status_file: bootstrap.pipelineStatusFile,
|
|
224
|
+
env_file: bootstrap.envFile,
|
|
225
|
+
env_example_file: bootstrap.envExampleFile,
|
|
226
|
+
gitignore_updated: bootstrap.gitignoreUpdated,
|
|
227
|
+
},
|
|
228
|
+
summary: {
|
|
229
|
+
pass: 1,
|
|
230
|
+
warn: 0,
|
|
231
|
+
fail: missingKeys.length,
|
|
232
|
+
},
|
|
233
|
+
blockers: missingKeys.map((key) => `Set ${key} in ${bootstrap.envFile}`),
|
|
234
|
+
warnings: [],
|
|
235
|
+
next_actions: [
|
|
236
|
+
`Fill the missing credentials in ${bootstrap.envFile}.`,
|
|
237
|
+
"Re-run check-generic after updating the project-local WHC env file.",
|
|
238
|
+
],
|
|
239
|
+
checks: [
|
|
240
|
+
{
|
|
241
|
+
id: "workspace-bootstrap",
|
|
242
|
+
status: "pass",
|
|
243
|
+
message: "App-specific WHC hidden files are initialized.",
|
|
244
|
+
details: bootstrap.stateRootAbsolute,
|
|
245
|
+
},
|
|
246
|
+
...missingKeys.map((key) => ({
|
|
247
|
+
id: `env:${key}`,
|
|
248
|
+
status: "fail",
|
|
249
|
+
message: `${key} is missing for this project workspace.`,
|
|
250
|
+
hint: `Fill ${key} in ${bootstrap.envFile}`,
|
|
251
|
+
})),
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
}, null, 2));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const report = await (0, source_compatibility_1.runSourceCompatibilityCheck)(config);
|
|
258
|
+
console.log(JSON.stringify({
|
|
259
|
+
ok: report.ok,
|
|
260
|
+
ready_for_deploy: report.ready_for_deploy,
|
|
261
|
+
status: report.status,
|
|
262
|
+
next_actions: report.next_actions,
|
|
263
|
+
action_id: (0, node_crypto_1.randomUUID)(),
|
|
264
|
+
tool: "whc_source_compatibility_check",
|
|
265
|
+
data: {
|
|
266
|
+
...report,
|
|
267
|
+
bootstrap: {
|
|
268
|
+
state_root: bootstrap.stateRootRelative,
|
|
269
|
+
created_paths: bootstrap.createdPaths,
|
|
270
|
+
pipeline_status_file: bootstrap.pipelineStatusFile,
|
|
271
|
+
env_file: bootstrap.envFile,
|
|
272
|
+
env_example_file: bootstrap.envExampleFile,
|
|
273
|
+
gitignore_updated: bootstrap.gitignoreUpdated,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
}, null, 2));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
87
279
|
if (process.argv.includes("--check-health")) {
|
|
280
|
+
const config = (0, env_1.loadConfig)();
|
|
88
281
|
const request = (0, whc_check_health_1.buildDefaultWhcCheckHealthRequest)(config);
|
|
89
282
|
const toolDef = tool_registry_1.TOOL_REGISTRY["whc_check_health"];
|
|
90
283
|
const result = await (0, tool_dispatcher_1.dispatch)({
|
|
@@ -102,6 +295,57 @@ async function main() {
|
|
|
102
295
|
}
|
|
103
296
|
console.log(HELP_TEXT);
|
|
104
297
|
}
|
|
298
|
+
function readCliOption(flag) {
|
|
299
|
+
const index = process.argv.indexOf(flag);
|
|
300
|
+
if (index === -1 || index + 1 >= process.argv.length) {
|
|
301
|
+
const normalized = flag.replace(/^--/, "").replace(/-/g, "_");
|
|
302
|
+
const npmConfigValue = process.env[`npm_config_${normalized}`];
|
|
303
|
+
if (npmConfigValue && npmConfigValue.length > 0) {
|
|
304
|
+
return npmConfigValue;
|
|
305
|
+
}
|
|
306
|
+
return readCheckGenericPositional(flag);
|
|
307
|
+
}
|
|
308
|
+
return process.argv[index + 1];
|
|
309
|
+
}
|
|
310
|
+
function readProjectEnv(filePath) {
|
|
311
|
+
if (!(0, node_fs_1.existsSync)(filePath)) {
|
|
312
|
+
return {};
|
|
313
|
+
}
|
|
314
|
+
const parsed = dotenv_1.default.parse((0, node_fs_1.readFileSync)(filePath, "utf8"));
|
|
315
|
+
return parsed;
|
|
316
|
+
}
|
|
317
|
+
function extractMissingEnvKeys(message) {
|
|
318
|
+
const prefix = "VALIDATION_ERROR: Missing or invalid env vars:";
|
|
319
|
+
if (!message.startsWith(prefix)) {
|
|
320
|
+
return ["project-specific WHC env values"];
|
|
321
|
+
}
|
|
322
|
+
return message
|
|
323
|
+
.slice(prefix.length)
|
|
324
|
+
.split(",")
|
|
325
|
+
.map((item) => item.trim())
|
|
326
|
+
.filter((item) => item.length > 0);
|
|
327
|
+
}
|
|
328
|
+
function withoutWhcEnv(env) {
|
|
329
|
+
return Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("WHC_")));
|
|
330
|
+
}
|
|
331
|
+
function readCheckGenericPositional(flag) {
|
|
332
|
+
const checkIndex = process.argv.indexOf("--check-generic");
|
|
333
|
+
if (checkIndex === -1) {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
const trailing = process.argv.slice(checkIndex + 1);
|
|
337
|
+
const positionalMap = {
|
|
338
|
+
"--project-root": 0,
|
|
339
|
+
"--app-path": 1,
|
|
340
|
+
"--workflow-mode": 2,
|
|
341
|
+
};
|
|
342
|
+
const position = positionalMap[flag];
|
|
343
|
+
if (position === undefined) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
const value = trailing[position];
|
|
347
|
+
return value && !value.startsWith("--") ? value : undefined;
|
|
348
|
+
}
|
|
105
349
|
main().catch((error) => {
|
|
106
350
|
const message = error instanceof Error ? error.message : "Unknown startup error";
|
|
107
351
|
console.error(message);
|