@whenlabs/when 0.9.3 → 0.11.0
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/README.md +21 -2
- package/action.yml +64 -2
- package/dist/chunk-JI5NCJQ2.js +496 -0
- package/dist/index.js +712 -30
- package/dist/{install-V24JHOA2.js → install-33GE3HKA.js} +7 -0
- package/dist/mcp.js +104 -89
- package/package.json +2 -1
- package/dist/chunk-2A2EZZF4.js +0 -19
package/dist/index.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
CACHE_DIR,
|
|
4
|
+
CONFIG_FILENAME,
|
|
5
|
+
deriveProject,
|
|
6
|
+
findBin,
|
|
7
|
+
generateDashboard,
|
|
8
|
+
loadConfig,
|
|
9
|
+
runCli,
|
|
10
|
+
writeCache
|
|
11
|
+
} from "./chunk-JI5NCJQ2.js";
|
|
5
12
|
import {
|
|
6
13
|
getStatusPath
|
|
7
14
|
} from "./chunk-4ZVSCJCJ.js";
|
|
8
15
|
|
|
9
16
|
// src/index.ts
|
|
10
|
-
import { Command as
|
|
17
|
+
import { Command as Command10 } from "commander";
|
|
11
18
|
|
|
12
19
|
// src/commands/delegate.ts
|
|
13
20
|
import { Command } from "commander";
|
|
@@ -297,8 +304,9 @@ function createDoctorCommand() {
|
|
|
297
304
|
import { Command as Command3 } from "commander";
|
|
298
305
|
import { spawn as spawn3 } from "child_process";
|
|
299
306
|
import { resolve as resolve2, dirname as dirname2, basename } from "path";
|
|
300
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
307
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
|
|
301
308
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
309
|
+
import { stringify } from "yaml";
|
|
302
310
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
303
311
|
var c2 = {
|
|
304
312
|
reset: "\x1B[0m",
|
|
@@ -312,12 +320,6 @@ var c2 = {
|
|
|
312
320
|
function colorize2(text, ...codes) {
|
|
313
321
|
return codes.join("") + text + c2.reset;
|
|
314
322
|
}
|
|
315
|
-
function findBin3(name) {
|
|
316
|
-
const pkgRoot = resolve2(__dirname2, "..");
|
|
317
|
-
const localBin = resolve2(pkgRoot, "node_modules", ".bin", name);
|
|
318
|
-
if (existsSync2(localBin)) return localBin;
|
|
319
|
-
return name;
|
|
320
|
-
}
|
|
321
323
|
function detectProject(cwd) {
|
|
322
324
|
let name = basename(cwd);
|
|
323
325
|
const pkgPath = resolve2(cwd, "package.json");
|
|
@@ -348,9 +350,25 @@ function detectProject(cwd) {
|
|
|
348
350
|
}
|
|
349
351
|
return { name, stack: stacks.length > 0 ? stacks.join(", ") : "unknown" };
|
|
350
352
|
}
|
|
351
|
-
function
|
|
353
|
+
function detectLicenseTemplate(cwd) {
|
|
354
|
+
const pkgPath = resolve2(cwd, "package.json");
|
|
355
|
+
if (existsSync2(pkgPath)) {
|
|
356
|
+
try {
|
|
357
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
358
|
+
const license = (pkg.license ?? "").toLowerCase();
|
|
359
|
+
if (["mit", "isc", "apache-2.0", "apache2", "bsd-2-clause", "bsd-3-clause"].some((l) => license.includes(l))) {
|
|
360
|
+
return "opensource";
|
|
361
|
+
}
|
|
362
|
+
if (license) return "commercial";
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return "opensource";
|
|
367
|
+
}
|
|
368
|
+
function runTool2(bin, args, cwd) {
|
|
352
369
|
return new Promise((resolveP) => {
|
|
353
370
|
const child = spawn3(bin, args, {
|
|
371
|
+
cwd,
|
|
354
372
|
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }
|
|
355
373
|
});
|
|
356
374
|
let stdout = "";
|
|
@@ -365,8 +383,75 @@ function runTool2(bin, args) {
|
|
|
365
383
|
child.on("close", (code) => resolveP({ stdout, stderr, exitCode: code ?? 1 }));
|
|
366
384
|
});
|
|
367
385
|
}
|
|
386
|
+
async function bootstrapConfigs(cwd) {
|
|
387
|
+
const results = [];
|
|
388
|
+
const hasEnv = existsSync2(resolve2(cwd, ".env"));
|
|
389
|
+
const hasEnvSchema = existsSync2(resolve2(cwd, ".env.schema"));
|
|
390
|
+
if (hasEnv && !hasEnvSchema) {
|
|
391
|
+
const { exitCode } = await runTool2(findBin("envalid"), ["init"], cwd);
|
|
392
|
+
if (exitCode === 0) {
|
|
393
|
+
results.push({ label: ".env.schema", action: "created", detail: "Created .env.schema from .env" });
|
|
394
|
+
} else if (exitCode === 127) {
|
|
395
|
+
results.push({ label: ".env.schema", action: "error", detail: "envalid not found" });
|
|
396
|
+
} else {
|
|
397
|
+
results.push({ label: ".env.schema", action: "error", detail: "envalid init failed" });
|
|
398
|
+
}
|
|
399
|
+
} else if (hasEnvSchema) {
|
|
400
|
+
results.push({ label: ".env.schema", action: "skipped", detail: "Skipped (already exists)" });
|
|
401
|
+
} else {
|
|
402
|
+
results.push({ label: ".env.schema", action: "skipped", detail: "Skipped (no .env found)" });
|
|
403
|
+
}
|
|
404
|
+
const hasVowConfig = existsSync2(resolve2(cwd, ".vow.json"));
|
|
405
|
+
if (!hasVowConfig) {
|
|
406
|
+
const template = detectLicenseTemplate(cwd);
|
|
407
|
+
const { exitCode } = await runTool2(findBin("vow"), ["init", "--template", template], cwd);
|
|
408
|
+
if (exitCode === 0) {
|
|
409
|
+
results.push({ label: ".vow.json", action: "created", detail: `Created .vow.json (template: ${template})` });
|
|
410
|
+
} else if (exitCode === 127) {
|
|
411
|
+
results.push({ label: ".vow.json", action: "error", detail: "vow not found" });
|
|
412
|
+
} else {
|
|
413
|
+
results.push({ label: ".vow.json", action: "error", detail: "vow init failed" });
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
results.push({ label: ".vow.json", action: "skipped", detail: "Skipped (already exists)" });
|
|
417
|
+
}
|
|
418
|
+
const hasStaleConfig = existsSync2(resolve2(cwd, ".stale.yml"));
|
|
419
|
+
let staleScanNeeded = false;
|
|
420
|
+
if (!hasStaleConfig) {
|
|
421
|
+
const { exitCode } = await runTool2(findBin("stale"), ["init"], cwd);
|
|
422
|
+
if (exitCode === 0) {
|
|
423
|
+
results.push({ label: ".stale.yml", action: "created", detail: "Created .stale.yml" });
|
|
424
|
+
staleScanNeeded = true;
|
|
425
|
+
} else if (exitCode === 127) {
|
|
426
|
+
results.push({ label: ".stale.yml", action: "error", detail: "stale not found" });
|
|
427
|
+
} else {
|
|
428
|
+
results.push({ label: ".stale.yml", action: "error", detail: "stale init failed" });
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
results.push({ label: ".stale.yml", action: "skipped", detail: "Skipped (already exists)" });
|
|
432
|
+
}
|
|
433
|
+
const { exitCode: berthCode } = await runTool2(findBin("berth"), ["register", "--yes", "--dir", cwd], cwd);
|
|
434
|
+
if (berthCode === 0) {
|
|
435
|
+
results.push({ label: "berth ports", action: "created", detail: "Registered project ports" });
|
|
436
|
+
} else if (berthCode === 127) {
|
|
437
|
+
results.push({ label: "berth ports", action: "error", detail: "berth not found" });
|
|
438
|
+
} else {
|
|
439
|
+
results.push({ label: "berth ports", action: "error", detail: "berth register failed" });
|
|
440
|
+
}
|
|
441
|
+
return { results, staleScanNeeded };
|
|
442
|
+
}
|
|
443
|
+
function bootstrapIcon(action) {
|
|
444
|
+
switch (action) {
|
|
445
|
+
case "created":
|
|
446
|
+
return colorize2("+", c2.green);
|
|
447
|
+
case "skipped":
|
|
448
|
+
return colorize2("-", c2.dim);
|
|
449
|
+
case "error":
|
|
450
|
+
return colorize2("!", c2.yellow);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
368
453
|
async function scanStale(cwd) {
|
|
369
|
-
const { stdout, exitCode } = await runTool2(
|
|
454
|
+
const { stdout, exitCode } = await runTool2(findBin("stale"), ["scan", "--format", "json", "--path", cwd]);
|
|
370
455
|
if (exitCode === 127) return { label: "Doc drift (stale)", status: "error", detail: "stale not found" };
|
|
371
456
|
try {
|
|
372
457
|
const json = JSON.parse(stdout);
|
|
@@ -377,7 +462,7 @@ async function scanStale(cwd) {
|
|
|
377
462
|
}
|
|
378
463
|
}
|
|
379
464
|
async function scanEnvalid(cwd) {
|
|
380
|
-
const { stdout, exitCode } = await runTool2(
|
|
465
|
+
const { stdout, exitCode } = await runTool2(findBin("envalid"), ["validate", "--format", "json"]);
|
|
381
466
|
if (exitCode === 127) return { label: "Env validation (envalid)", status: "error", detail: "envalid not found" };
|
|
382
467
|
if (exitCode === 2 || stdout.includes("not found")) return { label: "Env validation (envalid)", status: "skipped", detail: "No .env.schema \u2014 run `envalid init`" };
|
|
383
468
|
try {
|
|
@@ -389,7 +474,7 @@ async function scanEnvalid(cwd) {
|
|
|
389
474
|
}
|
|
390
475
|
}
|
|
391
476
|
async function scanBerth(cwd) {
|
|
392
|
-
const { stdout, exitCode } = await runTool2(
|
|
477
|
+
const { stdout, exitCode } = await runTool2(findBin("berth"), ["check", cwd, "--json"]);
|
|
393
478
|
if (exitCode === 127) return { label: "Port conflicts (berth)", status: "error", detail: "berth not found" };
|
|
394
479
|
try {
|
|
395
480
|
const json = JSON.parse(stdout);
|
|
@@ -400,7 +485,7 @@ async function scanBerth(cwd) {
|
|
|
400
485
|
}
|
|
401
486
|
}
|
|
402
487
|
async function scanVow(cwd) {
|
|
403
|
-
const { stdout, exitCode } = await runTool2(
|
|
488
|
+
const { stdout, exitCode } = await runTool2(findBin("vow"), ["scan", "--format", "json", "--path", cwd]);
|
|
404
489
|
if (exitCode === 127) return { label: "License scan (vow)", status: "error", detail: "vow not found" };
|
|
405
490
|
const jsonStart = stdout.indexOf("{");
|
|
406
491
|
const jsonStr = jsonStart >= 0 ? stdout.slice(jsonStart) : stdout;
|
|
@@ -418,12 +503,12 @@ async function scanVow(cwd) {
|
|
|
418
503
|
async function scanAware(cwd) {
|
|
419
504
|
const hasConfig = existsSync2(resolve2(cwd, ".aware.json"));
|
|
420
505
|
if (!hasConfig) {
|
|
421
|
-
const { exitCode: exitCode2 } = await runTool2(
|
|
506
|
+
const { exitCode: exitCode2 } = await runTool2(findBin("aware"), ["init", "--force"], cwd);
|
|
422
507
|
if (exitCode2 === 0) return { label: "AI context (aware)", status: "ok", detail: "Generated .aware.json and context files" };
|
|
423
508
|
if (exitCode2 === 127) return { label: "AI context (aware)", status: "error", detail: "aware not found" };
|
|
424
509
|
return { label: "AI context (aware)", status: "skipped", detail: "Could not generate \u2014 run `aware init` manually" };
|
|
425
510
|
}
|
|
426
|
-
const { stdout, stderr, exitCode } = await runTool2(
|
|
511
|
+
const { stdout, stderr, exitCode } = await runTool2(findBin("aware"), ["doctor"], cwd);
|
|
427
512
|
if (exitCode === 127) return { label: "AI context (aware)", status: "error", detail: "aware not found" };
|
|
428
513
|
const combined = (stdout + stderr).trim();
|
|
429
514
|
const warnings = combined.split("\n").filter((l) => l.includes("\u26A0") || /warn/i.test(l)).length;
|
|
@@ -443,7 +528,7 @@ function statusIcon2(status) {
|
|
|
443
528
|
}
|
|
444
529
|
function createInitCommand() {
|
|
445
530
|
const cmd = new Command3("init");
|
|
446
|
-
cmd.description("Interactive onboarding \u2014 detect stack,
|
|
531
|
+
cmd.description("Interactive onboarding \u2014 detect stack, bootstrap tool configs, run all checks");
|
|
447
532
|
cmd.action(async () => {
|
|
448
533
|
const cwd = process.cwd();
|
|
449
534
|
console.log("");
|
|
@@ -454,6 +539,53 @@ function createInitCommand() {
|
|
|
454
539
|
console.log(` Stack: ${colorize2(project.stack, c2.cyan)}`);
|
|
455
540
|
console.log(` Path: ${colorize2(cwd, c2.dim)}`);
|
|
456
541
|
console.log("");
|
|
542
|
+
process.stdout.write(colorize2(" Bootstrapping tool configs\u2026", c2.dim) + "\n");
|
|
543
|
+
const { results: bootstrapResults, staleScanNeeded } = await bootstrapConfigs(cwd);
|
|
544
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
545
|
+
console.log(colorize2(" Bootstrap", c2.bold));
|
|
546
|
+
console.log(colorize2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c2.dim));
|
|
547
|
+
for (const r of bootstrapResults) {
|
|
548
|
+
const icon = bootstrapIcon(r.action);
|
|
549
|
+
const label = r.label.padEnd(20);
|
|
550
|
+
const detail = r.action === "created" ? colorize2(r.detail, c2.green) : r.action === "error" ? colorize2(r.detail, c2.yellow) : colorize2(r.detail, c2.dim);
|
|
551
|
+
console.log(` ${icon} ${label} ${detail}`);
|
|
552
|
+
}
|
|
553
|
+
const whenlabsConfigPath = resolve2(cwd, CONFIG_FILENAME);
|
|
554
|
+
if (!existsSync2(whenlabsConfigPath)) {
|
|
555
|
+
try {
|
|
556
|
+
const mergedConfig = {};
|
|
557
|
+
const staleConfigPath = resolve2(cwd, ".stale.yml");
|
|
558
|
+
if (existsSync2(staleConfigPath)) {
|
|
559
|
+
mergedConfig["stale"] = {};
|
|
560
|
+
}
|
|
561
|
+
const vowConfigPath = resolve2(cwd, ".vow.json");
|
|
562
|
+
if (existsSync2(vowConfigPath)) {
|
|
563
|
+
try {
|
|
564
|
+
const vowData = JSON.parse(readFileSync(vowConfigPath, "utf-8"));
|
|
565
|
+
mergedConfig["vow"] = {
|
|
566
|
+
...typeof vowData.policy === "string" ? { policy: vowData.policy } : {},
|
|
567
|
+
...typeof vowData.production_only === "boolean" ? { production_only: vowData.production_only } : {}
|
|
568
|
+
};
|
|
569
|
+
} catch {
|
|
570
|
+
mergedConfig["vow"] = {};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const envSchemaPath = resolve2(cwd, ".env.schema");
|
|
574
|
+
if (existsSync2(envSchemaPath)) {
|
|
575
|
+
mergedConfig["envalid"] = { schema: ".env.schema" };
|
|
576
|
+
}
|
|
577
|
+
mergedConfig["berth"] = {};
|
|
578
|
+
mergedConfig["aware"] = {};
|
|
579
|
+
mergedConfig["velocity"] = {};
|
|
580
|
+
writeFileSync(whenlabsConfigPath, stringify(mergedConfig, { lineWidth: 0 }), "utf-8");
|
|
581
|
+
console.log(` ${colorize2("+", c2.green)} ${colorize2(CONFIG_FILENAME, c2.bold)} ${colorize2("created", c2.green)}`);
|
|
582
|
+
} catch {
|
|
583
|
+
console.log(` ${colorize2("!", c2.yellow)} Could not generate ${colorize2(CONFIG_FILENAME, c2.bold)}`);
|
|
584
|
+
}
|
|
585
|
+
} else {
|
|
586
|
+
console.log(` ${colorize2("-", c2.dim)} ${colorize2(CONFIG_FILENAME, c2.bold)} ${colorize2("already exists", c2.dim)}`);
|
|
587
|
+
}
|
|
588
|
+
console.log("");
|
|
457
589
|
process.stdout.write(colorize2(" Scanning project\u2026", c2.dim) + "\n");
|
|
458
590
|
const results = await Promise.all([
|
|
459
591
|
scanStale(cwd),
|
|
@@ -471,13 +603,32 @@ function createInitCommand() {
|
|
|
471
603
|
const detail = r.status === "ok" ? colorize2(r.detail, c2.green) : r.status === "skipped" ? colorize2(r.detail, c2.dim) : r.status === "error" ? colorize2(r.detail, c2.yellow) : colorize2(r.detail, c2.red);
|
|
472
604
|
console.log(` ${icon} ${label} ${detail}`);
|
|
473
605
|
}
|
|
606
|
+
const staleResult = results.find((r) => r.label === "Doc drift (stale)");
|
|
607
|
+
if (staleResult?.status === "issues" || staleScanNeeded) {
|
|
608
|
+
process.stdout.write(colorize2(" Auto-fixing doc drift\u2026", c2.dim) + "\n");
|
|
609
|
+
const { exitCode: fixCode } = await runTool2(findBin("stale"), ["fix", "--apply"], cwd);
|
|
610
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
611
|
+
if (fixCode === 0) {
|
|
612
|
+
console.log(` ${colorize2("\u2713", c2.green)} ${colorize2("Doc drift auto-fixed", c2.green)}`);
|
|
613
|
+
} else if (fixCode === 127) {
|
|
614
|
+
console.log(` ${colorize2("!", c2.yellow)} ${colorize2("stale not found for auto-fix", c2.yellow)}`);
|
|
615
|
+
} else {
|
|
616
|
+
console.log(` ${colorize2("-", c2.dim)} ${colorize2("No high-confidence fixes available", c2.dim)}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
474
619
|
const issueCount = results.filter((r) => r.status === "issues").length;
|
|
475
620
|
const errorCount = results.filter((r) => r.status === "error").length;
|
|
621
|
+
const bootstrapErrors = bootstrapResults.filter((r) => r.action === "error").length;
|
|
622
|
+
const bootstrapCreated = bootstrapResults.filter((r) => r.action === "created").length;
|
|
476
623
|
console.log(colorize2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c2.dim));
|
|
477
|
-
if (issueCount + errorCount === 0) {
|
|
624
|
+
if (issueCount + errorCount + bootstrapErrors === 0) {
|
|
478
625
|
console.log(colorize2(" All clear \u2014 project looks healthy!", c2.green, c2.bold));
|
|
479
626
|
} else {
|
|
480
|
-
|
|
627
|
+
const parts = [];
|
|
628
|
+
if (bootstrapCreated > 0) parts.push(`${bootstrapCreated} config(s) created`);
|
|
629
|
+
if (issueCount > 0) parts.push(`${issueCount} scan(s) found issues`);
|
|
630
|
+
if (errorCount + bootstrapErrors > 0) parts.push(`${errorCount + bootstrapErrors} tool(s) could not run`);
|
|
631
|
+
console.log(colorize2(` ${parts.join(", ")}`, c2.yellow, c2.bold));
|
|
481
632
|
}
|
|
482
633
|
console.log("");
|
|
483
634
|
console.log(colorize2(" Next steps:", c2.bold));
|
|
@@ -498,7 +649,7 @@ function createInitCommand() {
|
|
|
498
649
|
import { Command as Command4 } from "commander";
|
|
499
650
|
import { join } from "path";
|
|
500
651
|
import { homedir } from "os";
|
|
501
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
652
|
+
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
502
653
|
var STATUS_DIR = join(homedir(), ".whenlabs");
|
|
503
654
|
function toolResultToStatus(r) {
|
|
504
655
|
const count = r.issues + r.warnings;
|
|
@@ -535,10 +686,10 @@ function writeStatus(results) {
|
|
|
535
686
|
},
|
|
536
687
|
summary: buildSummary(results)
|
|
537
688
|
};
|
|
538
|
-
|
|
689
|
+
writeFileSync2(getStatusPath(), JSON.stringify(status, null, 2) + "\n");
|
|
539
690
|
}
|
|
540
691
|
function sleep(ms) {
|
|
541
|
-
return new Promise((
|
|
692
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
542
693
|
}
|
|
543
694
|
function createWatchCommand() {
|
|
544
695
|
const cmd = new Command4("watch");
|
|
@@ -585,16 +736,541 @@ function createWatchCommand() {
|
|
|
585
736
|
return cmd;
|
|
586
737
|
}
|
|
587
738
|
|
|
588
|
-
// src/
|
|
589
|
-
import {
|
|
590
|
-
import {
|
|
739
|
+
// src/commands/config.ts
|
|
740
|
+
import { Command as Command5 } from "commander";
|
|
741
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
742
|
+
import { resolve as resolve3 } from "path";
|
|
743
|
+
import { parse, stringify as stringify2 } from "yaml";
|
|
744
|
+
var c3 = {
|
|
745
|
+
reset: "\x1B[0m",
|
|
746
|
+
bold: "\x1B[1m",
|
|
747
|
+
green: "\x1B[32m",
|
|
748
|
+
yellow: "\x1B[33m",
|
|
749
|
+
red: "\x1B[31m",
|
|
750
|
+
cyan: "\x1B[36m",
|
|
751
|
+
dim: "\x1B[2m"
|
|
752
|
+
};
|
|
753
|
+
function colorize3(text, ...codes) {
|
|
754
|
+
return codes.join("") + text + c3.reset;
|
|
755
|
+
}
|
|
756
|
+
function readExistingToolConfigs(cwd) {
|
|
757
|
+
const config = {};
|
|
758
|
+
const stalePath = resolve3(cwd, ".stale.yml");
|
|
759
|
+
if (existsSync3(stalePath)) {
|
|
760
|
+
try {
|
|
761
|
+
const raw = readFileSync2(stalePath, "utf-8");
|
|
762
|
+
const parsed = parse(raw);
|
|
763
|
+
config.stale = {
|
|
764
|
+
ignore: Array.isArray(parsed?.ignore) ? parsed.ignore : void 0,
|
|
765
|
+
deep: typeof parsed?.deep === "boolean" ? parsed.deep : void 0
|
|
766
|
+
};
|
|
767
|
+
} catch {
|
|
768
|
+
config.stale = {};
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const vowPath = resolve3(cwd, ".vow.json");
|
|
772
|
+
if (existsSync3(vowPath)) {
|
|
773
|
+
try {
|
|
774
|
+
const vow = JSON.parse(readFileSync2(vowPath, "utf-8"));
|
|
775
|
+
config.vow = {
|
|
776
|
+
policy: typeof vow.policy === "string" ? vow.policy : void 0,
|
|
777
|
+
production_only: typeof vow.production_only === "boolean" ? vow.production_only : void 0
|
|
778
|
+
};
|
|
779
|
+
} catch {
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const schemaPath = resolve3(cwd, ".env.schema");
|
|
783
|
+
if (existsSync3(schemaPath)) {
|
|
784
|
+
config.envalid = { schema: ".env.schema" };
|
|
785
|
+
}
|
|
786
|
+
return config;
|
|
787
|
+
}
|
|
788
|
+
function generateDefaultConfig(cwd) {
|
|
789
|
+
const base = readExistingToolConfigs(cwd);
|
|
790
|
+
return {
|
|
791
|
+
stale: base.stale ?? {},
|
|
792
|
+
envalid: base.envalid ?? {},
|
|
793
|
+
vow: base.vow ?? {},
|
|
794
|
+
berth: {},
|
|
795
|
+
aware: {},
|
|
796
|
+
velocity: {}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
function validateConfig(config) {
|
|
800
|
+
const errors = [];
|
|
801
|
+
if (config.stale !== void 0 && typeof config.stale !== "object") {
|
|
802
|
+
errors.push("stale: must be an object");
|
|
803
|
+
}
|
|
804
|
+
if (config.stale?.ignore !== void 0 && !Array.isArray(config.stale.ignore)) {
|
|
805
|
+
errors.push("stale.ignore: must be an array of strings");
|
|
806
|
+
}
|
|
807
|
+
if (config.stale?.deep !== void 0 && typeof config.stale.deep !== "boolean") {
|
|
808
|
+
errors.push("stale.deep: must be a boolean");
|
|
809
|
+
}
|
|
810
|
+
if (config.envalid !== void 0 && typeof config.envalid !== "object") {
|
|
811
|
+
errors.push("envalid: must be an object");
|
|
812
|
+
}
|
|
813
|
+
if (config.envalid?.schema !== void 0 && typeof config.envalid.schema !== "string") {
|
|
814
|
+
errors.push("envalid.schema: must be a string");
|
|
815
|
+
}
|
|
816
|
+
if (config.envalid?.environments !== void 0 && !Array.isArray(config.envalid.environments)) {
|
|
817
|
+
errors.push("envalid.environments: must be an array of strings");
|
|
818
|
+
}
|
|
819
|
+
if (config.vow !== void 0 && typeof config.vow !== "object") {
|
|
820
|
+
errors.push("vow: must be an object");
|
|
821
|
+
}
|
|
822
|
+
if (config.vow?.policy !== void 0 && typeof config.vow.policy !== "string") {
|
|
823
|
+
errors.push("vow.policy: must be a string");
|
|
824
|
+
}
|
|
825
|
+
if (config.vow?.production_only !== void 0 && typeof config.vow.production_only !== "boolean") {
|
|
826
|
+
errors.push("vow.production_only: must be a boolean");
|
|
827
|
+
}
|
|
828
|
+
if (config.berth !== void 0 && typeof config.berth !== "object") {
|
|
829
|
+
errors.push("berth: must be an object");
|
|
830
|
+
}
|
|
831
|
+
if (config.berth?.ports !== void 0) {
|
|
832
|
+
if (typeof config.berth.ports !== "object" || Array.isArray(config.berth.ports)) {
|
|
833
|
+
errors.push("berth.ports: must be a key/value map of port names to numbers");
|
|
834
|
+
} else {
|
|
835
|
+
for (const [k, v] of Object.entries(config.berth.ports)) {
|
|
836
|
+
if (typeof v !== "number") errors.push(`berth.ports.${k}: must be a number`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (config.aware !== void 0 && typeof config.aware !== "object") {
|
|
841
|
+
errors.push("aware: must be an object");
|
|
842
|
+
}
|
|
843
|
+
if (config.aware?.targets !== void 0 && !Array.isArray(config.aware.targets)) {
|
|
844
|
+
errors.push("aware.targets: must be an array of strings");
|
|
845
|
+
}
|
|
846
|
+
if (config.velocity !== void 0 && typeof config.velocity !== "object") {
|
|
847
|
+
errors.push("velocity: must be an object");
|
|
848
|
+
}
|
|
849
|
+
if (config.velocity?.project !== void 0 && typeof config.velocity.project !== "string") {
|
|
850
|
+
errors.push("velocity.project: must be a string");
|
|
851
|
+
}
|
|
852
|
+
return errors;
|
|
853
|
+
}
|
|
854
|
+
function createConfigCommand() {
|
|
855
|
+
const cmd = new Command5("config");
|
|
856
|
+
cmd.description("Manage unified .whenlabs.yml project config");
|
|
857
|
+
cmd.action(() => {
|
|
858
|
+
const cwd = process.cwd();
|
|
859
|
+
const configPath = resolve3(cwd, CONFIG_FILENAME);
|
|
860
|
+
console.log("");
|
|
861
|
+
console.log(colorize3(" when config", c3.bold, c3.cyan));
|
|
862
|
+
console.log(colorize3(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c3.dim));
|
|
863
|
+
if (!existsSync3(configPath)) {
|
|
864
|
+
console.log(` ${colorize3("-", c3.dim)} No ${colorize3(CONFIG_FILENAME, c3.bold)} found`);
|
|
865
|
+
console.log(` ${colorize3("\u2022", c3.dim)} Run ${colorize3("when config init", c3.bold)} to generate one`);
|
|
866
|
+
console.log("");
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
870
|
+
console.log(` ${colorize3(configPath, c3.dim)}`);
|
|
871
|
+
console.log("");
|
|
872
|
+
for (const line of raw.split("\n")) {
|
|
873
|
+
console.log(` ${line}`);
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
const initCmd = new Command5("init");
|
|
877
|
+
initCmd.description(`Generate ${CONFIG_FILENAME} from existing tool configs`);
|
|
878
|
+
initCmd.option("--force", "Overwrite existing config");
|
|
879
|
+
initCmd.action((options) => {
|
|
880
|
+
const cwd = process.cwd();
|
|
881
|
+
const configPath = resolve3(cwd, CONFIG_FILENAME);
|
|
882
|
+
console.log("");
|
|
883
|
+
console.log(colorize3(" when config init", c3.bold, c3.cyan));
|
|
884
|
+
console.log(colorize3(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c3.dim));
|
|
885
|
+
if (existsSync3(configPath) && !options.force) {
|
|
886
|
+
console.log(` ${colorize3("-", c3.dim)} ${colorize3(CONFIG_FILENAME, c3.bold)} already exists \u2014 use ${colorize3("--force", c3.bold)} to overwrite`);
|
|
887
|
+
console.log("");
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const config = generateDefaultConfig(cwd);
|
|
891
|
+
const yaml = stringify2(config, { lineWidth: 0 });
|
|
892
|
+
writeFileSync3(configPath, yaml, "utf-8");
|
|
893
|
+
console.log(` ${colorize3("+", c3.green)} Created ${colorize3(CONFIG_FILENAME, c3.bold)}`);
|
|
894
|
+
console.log("");
|
|
895
|
+
for (const line of yaml.split("\n")) {
|
|
896
|
+
if (line.trim()) console.log(` ${colorize3(line, c3.dim)}`);
|
|
897
|
+
}
|
|
898
|
+
console.log("");
|
|
899
|
+
});
|
|
900
|
+
const validateCmd = new Command5("validate");
|
|
901
|
+
validateCmd.description(`Validate ${CONFIG_FILENAME} structure`);
|
|
902
|
+
validateCmd.action(() => {
|
|
903
|
+
const cwd = process.cwd();
|
|
904
|
+
const configPath = resolve3(cwd, CONFIG_FILENAME);
|
|
905
|
+
console.log("");
|
|
906
|
+
console.log(colorize3(" when config validate", c3.bold, c3.cyan));
|
|
907
|
+
console.log(colorize3(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c3.dim));
|
|
908
|
+
if (!existsSync3(configPath)) {
|
|
909
|
+
console.log(` ${colorize3("-", c3.dim)} No ${colorize3(CONFIG_FILENAME, c3.bold)} found \u2014 nothing to validate`);
|
|
910
|
+
console.log("");
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const config = loadConfig(cwd);
|
|
914
|
+
if (!config) {
|
|
915
|
+
console.log(` ${colorize3("!", c3.yellow)} Could not parse ${colorize3(CONFIG_FILENAME, c3.bold)} \u2014 invalid YAML`);
|
|
916
|
+
console.log("");
|
|
917
|
+
process.exitCode = 1;
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
const errors = validateConfig(config);
|
|
921
|
+
if (errors.length === 0) {
|
|
922
|
+
console.log(` ${colorize3("\u2713", c3.green)} ${colorize3(CONFIG_FILENAME, c3.bold)} is valid`);
|
|
923
|
+
} else {
|
|
924
|
+
for (const err of errors) {
|
|
925
|
+
console.log(` ${colorize3("\u2717", c3.red)} ${err}`);
|
|
926
|
+
}
|
|
927
|
+
process.exitCode = 1;
|
|
928
|
+
}
|
|
929
|
+
console.log("");
|
|
930
|
+
});
|
|
931
|
+
cmd.addCommand(initCmd);
|
|
932
|
+
cmd.addCommand(validateCmd);
|
|
933
|
+
return cmd;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/commands/upgrade.ts
|
|
937
|
+
import { Command as Command6 } from "commander";
|
|
938
|
+
import { execSync } from "child_process";
|
|
939
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
940
|
+
import { resolve as resolve4, dirname as dirname3 } from "path";
|
|
591
941
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
592
942
|
var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
|
|
593
|
-
var
|
|
594
|
-
|
|
943
|
+
var c4 = {
|
|
944
|
+
reset: "\x1B[0m",
|
|
945
|
+
bold: "\x1B[1m",
|
|
946
|
+
green: "\x1B[32m",
|
|
947
|
+
yellow: "\x1B[33m",
|
|
948
|
+
red: "\x1B[31m",
|
|
949
|
+
cyan: "\x1B[36m",
|
|
950
|
+
dim: "\x1B[2m"
|
|
951
|
+
};
|
|
952
|
+
function colorize4(text, ...codes) {
|
|
953
|
+
return codes.join("") + text + c4.reset;
|
|
954
|
+
}
|
|
955
|
+
function parseVersion(v) {
|
|
956
|
+
return v.trim().split(".").map(Number);
|
|
957
|
+
}
|
|
958
|
+
function versionGte(a, b) {
|
|
959
|
+
const pa = parseVersion(a);
|
|
960
|
+
const pb = parseVersion(b);
|
|
961
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
962
|
+
const na = pa[i] ?? 0;
|
|
963
|
+
const nb = pb[i] ?? 0;
|
|
964
|
+
if (na > nb) return true;
|
|
965
|
+
if (na < nb) return false;
|
|
966
|
+
}
|
|
967
|
+
return true;
|
|
968
|
+
}
|
|
969
|
+
function createUpgradeCommand() {
|
|
970
|
+
const cmd = new Command6("upgrade");
|
|
971
|
+
cmd.description("Upgrade @whenlabs/when to the latest version");
|
|
972
|
+
cmd.action(async () => {
|
|
973
|
+
console.log("");
|
|
974
|
+
console.log(colorize4(" when upgrade", c4.bold, c4.cyan));
|
|
975
|
+
console.log(colorize4(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c4.dim));
|
|
976
|
+
const pkgPath = resolve4(__dirname3, "..", "..", "package.json");
|
|
977
|
+
let current;
|
|
978
|
+
try {
|
|
979
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
980
|
+
current = pkg.version;
|
|
981
|
+
} catch {
|
|
982
|
+
console.log(` ${colorize4("!", c4.red)} Could not read current version`);
|
|
983
|
+
console.log("");
|
|
984
|
+
process.exitCode = 1;
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
console.log(` ${colorize4("current", c4.dim)} ${colorize4(current, c4.bold)}`);
|
|
988
|
+
let latest;
|
|
989
|
+
try {
|
|
990
|
+
latest = execSync("npm view @whenlabs/when version", { encoding: "utf-8" }).trim();
|
|
991
|
+
} catch {
|
|
992
|
+
console.log(` ${colorize4("!", c4.yellow)} Could not reach npm registry \u2014 check your network connection`);
|
|
993
|
+
console.log("");
|
|
994
|
+
process.exitCode = 1;
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
console.log(` ${colorize4("latest", c4.dim)} ${colorize4(latest, c4.bold)}`);
|
|
998
|
+
console.log("");
|
|
999
|
+
if (versionGte(current, latest)) {
|
|
1000
|
+
console.log(` ${colorize4("\u2713", c4.green)} Already up to date`);
|
|
1001
|
+
console.log("");
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
console.log(` ${colorize4("\u2191", c4.yellow)} Upgrade available: ${colorize4(current, c4.dim)} \u2192 ${colorize4(latest, c4.green + c4.bold)}`);
|
|
1005
|
+
console.log(` ${colorize4("\u2022", c4.dim)} Running: ${colorize4("npm install -g @whenlabs/when@latest", c4.bold)}`);
|
|
1006
|
+
console.log("");
|
|
1007
|
+
try {
|
|
1008
|
+
execSync("npm install -g @whenlabs/when@latest", { stdio: "inherit" });
|
|
1009
|
+
console.log("");
|
|
1010
|
+
console.log(` ${colorize4("\u2713", c4.green)} Upgraded to ${colorize4(latest, c4.bold)}`);
|
|
1011
|
+
} catch {
|
|
1012
|
+
console.log(` ${colorize4("\u2717", c4.red)} Install failed \u2014 try running with sudo or check npm permissions`);
|
|
1013
|
+
process.exitCode = 1;
|
|
1014
|
+
}
|
|
1015
|
+
console.log("");
|
|
1016
|
+
});
|
|
1017
|
+
return cmd;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/commands/eject.ts
|
|
1021
|
+
import { Command as Command7 } from "commander";
|
|
1022
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4, copyFileSync } from "fs";
|
|
1023
|
+
import { resolve as resolve5 } from "path";
|
|
1024
|
+
import { stringify as stringify3 } from "yaml";
|
|
1025
|
+
var c5 = {
|
|
1026
|
+
reset: "\x1B[0m",
|
|
1027
|
+
bold: "\x1B[1m",
|
|
1028
|
+
green: "\x1B[32m",
|
|
1029
|
+
yellow: "\x1B[33m",
|
|
1030
|
+
red: "\x1B[31m",
|
|
1031
|
+
cyan: "\x1B[36m",
|
|
1032
|
+
dim: "\x1B[2m"
|
|
1033
|
+
};
|
|
1034
|
+
function colorize5(text, ...codes) {
|
|
1035
|
+
return codes.join("") + text + c5.reset;
|
|
1036
|
+
}
|
|
1037
|
+
function createEjectCommand() {
|
|
1038
|
+
const cmd = new Command7("eject");
|
|
1039
|
+
cmd.description("Write each tool section of .whenlabs.yml back to its native config format");
|
|
1040
|
+
cmd.option("--force", "Overwrite existing files without prompting");
|
|
1041
|
+
cmd.action((options) => {
|
|
1042
|
+
const cwd = process.cwd();
|
|
1043
|
+
console.log("");
|
|
1044
|
+
console.log(colorize5(" when eject", c5.bold, c5.cyan));
|
|
1045
|
+
console.log(colorize5(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c5.dim));
|
|
1046
|
+
const config = loadConfig(cwd);
|
|
1047
|
+
if (!config) {
|
|
1048
|
+
console.log(` ${colorize5("!", c5.yellow)} No ${colorize5(".whenlabs.yml", c5.bold)} found \u2014 nothing to eject`);
|
|
1049
|
+
console.log(` ${colorize5("\u2022", c5.dim)} Run ${colorize5("when config init", c5.bold)} to generate one first`);
|
|
1050
|
+
console.log("");
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
let ejected = 0;
|
|
1054
|
+
let skipped = 0;
|
|
1055
|
+
if (config.stale && Object.keys(config.stale).length > 0) {
|
|
1056
|
+
const dest = resolve5(cwd, ".stale.yml");
|
|
1057
|
+
if (existsSync4(dest) && !options.force) {
|
|
1058
|
+
console.log(` ${colorize5("!", c5.yellow)} ${colorize5(".stale.yml", c5.bold)} already exists \u2014 use ${colorize5("--force", c5.bold)} to overwrite`);
|
|
1059
|
+
skipped++;
|
|
1060
|
+
} else {
|
|
1061
|
+
const yaml = stringify3(config.stale, { lineWidth: 0 });
|
|
1062
|
+
writeFileSync4(dest, yaml, "utf-8");
|
|
1063
|
+
console.log(` ${colorize5("+", c5.green)} Wrote ${colorize5(".stale.yml", c5.bold)}`);
|
|
1064
|
+
ejected++;
|
|
1065
|
+
}
|
|
1066
|
+
} else if (config.stale !== void 0) {
|
|
1067
|
+
console.log(` ${colorize5("-", c5.dim)} stale: empty config \u2014 skipping .stale.yml`);
|
|
1068
|
+
}
|
|
1069
|
+
if (config.vow && Object.keys(config.vow).length > 0) {
|
|
1070
|
+
const dest = resolve5(cwd, ".vow.json");
|
|
1071
|
+
if (existsSync4(dest) && !options.force) {
|
|
1072
|
+
console.log(` ${colorize5("!", c5.yellow)} ${colorize5(".vow.json", c5.bold)} already exists \u2014 use ${colorize5("--force", c5.bold)} to overwrite`);
|
|
1073
|
+
skipped++;
|
|
1074
|
+
} else {
|
|
1075
|
+
writeFileSync4(dest, JSON.stringify(config.vow, null, 2) + "\n", "utf-8");
|
|
1076
|
+
console.log(` ${colorize5("+", c5.green)} Wrote ${colorize5(".vow.json", c5.bold)}`);
|
|
1077
|
+
ejected++;
|
|
1078
|
+
}
|
|
1079
|
+
} else if (config.vow !== void 0) {
|
|
1080
|
+
console.log(` ${colorize5("-", c5.dim)} vow: empty config \u2014 skipping .vow.json`);
|
|
1081
|
+
}
|
|
1082
|
+
if (config.envalid?.schema) {
|
|
1083
|
+
const src = resolve5(cwd, config.envalid.schema);
|
|
1084
|
+
const dest = resolve5(cwd, ".env.schema");
|
|
1085
|
+
const isSamePath = resolve5(src) === resolve5(dest);
|
|
1086
|
+
if (isSamePath) {
|
|
1087
|
+
console.log(` ${colorize5("-", c5.dim)} envalid.schema already points to ${colorize5(".env.schema", c5.bold)}`);
|
|
1088
|
+
} else if (!existsSync4(src)) {
|
|
1089
|
+
console.log(` ${colorize5("!", c5.yellow)} envalid.schema source ${colorize5(config.envalid.schema, c5.bold)} not found \u2014 skipping`);
|
|
1090
|
+
skipped++;
|
|
1091
|
+
} else if (existsSync4(dest) && !options.force) {
|
|
1092
|
+
console.log(` ${colorize5("!", c5.yellow)} ${colorize5(".env.schema", c5.bold)} already exists \u2014 use ${colorize5("--force", c5.bold)} to overwrite`);
|
|
1093
|
+
skipped++;
|
|
1094
|
+
} else {
|
|
1095
|
+
copyFileSync(src, dest);
|
|
1096
|
+
console.log(` ${colorize5("+", c5.green)} Copied ${colorize5(config.envalid.schema, c5.bold)} \u2192 ${colorize5(".env.schema", c5.bold)}`);
|
|
1097
|
+
ejected++;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (config.berth !== void 0) {
|
|
1101
|
+
const portCount = config.berth.ports ? Object.keys(config.berth.ports).length : 0;
|
|
1102
|
+
if (portCount > 0) {
|
|
1103
|
+
console.log(` ${colorize5("\u2022", c5.dim)} berth: ${portCount} port(s) configured \u2014 berth has no standalone config file, managed via ${colorize5(".whenlabs.yml", c5.bold)}`);
|
|
1104
|
+
} else {
|
|
1105
|
+
console.log(` ${colorize5("-", c5.dim)} berth: no standalone config file`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
console.log("");
|
|
1109
|
+
if (ejected > 0) {
|
|
1110
|
+
console.log(` ${colorize5("\u2713", c5.green)} Ejected ${colorize5(String(ejected), c5.bold)} file(s)`);
|
|
1111
|
+
}
|
|
1112
|
+
if (skipped > 0) {
|
|
1113
|
+
console.log(` ${colorize5("!", c5.yellow)} Skipped ${colorize5(String(skipped), c5.bold)} file(s) \u2014 run with ${colorize5("--force", c5.bold)} to overwrite`);
|
|
1114
|
+
}
|
|
1115
|
+
if (ejected === 0 && skipped === 0) {
|
|
1116
|
+
console.log(` ${colorize5("-", c5.dim)} Nothing to eject`);
|
|
1117
|
+
}
|
|
1118
|
+
console.log("");
|
|
1119
|
+
});
|
|
1120
|
+
return cmd;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/commands/diff.ts
|
|
1124
|
+
import { Command as Command8 } from "commander";
|
|
1125
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
1126
|
+
import { join as join2 } from "path";
|
|
1127
|
+
var c6 = {
|
|
1128
|
+
reset: "\x1B[0m",
|
|
1129
|
+
bold: "\x1B[1m",
|
|
1130
|
+
green: "\x1B[32m",
|
|
1131
|
+
yellow: "\x1B[33m",
|
|
1132
|
+
red: "\x1B[31m",
|
|
1133
|
+
cyan: "\x1B[36m",
|
|
1134
|
+
dim: "\x1B[2m"
|
|
1135
|
+
};
|
|
1136
|
+
function colorize6(text, ...codes) {
|
|
1137
|
+
return codes.join("") + text + c6.reset;
|
|
1138
|
+
}
|
|
1139
|
+
function readCache(tool, project) {
|
|
1140
|
+
const file = join2(CACHE_DIR, `${tool}_${project}.json`);
|
|
1141
|
+
if (!existsSync5(file)) return null;
|
|
1142
|
+
try {
|
|
1143
|
+
return JSON.parse(readFileSync5(file, "utf-8"));
|
|
1144
|
+
} catch {
|
|
1145
|
+
return null;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function diffLines(oldOutput, newOutput) {
|
|
1149
|
+
const oldLines = new Set(oldOutput.split("\n").map((l) => l.trim()).filter(Boolean));
|
|
1150
|
+
const newLines = new Set(newOutput.split("\n").map((l) => l.trim()).filter(Boolean));
|
|
1151
|
+
const added = [];
|
|
1152
|
+
const removed = [];
|
|
1153
|
+
const unchanged = [];
|
|
1154
|
+
for (const line of newLines) {
|
|
1155
|
+
if (oldLines.has(line)) {
|
|
1156
|
+
unchanged.push(line);
|
|
1157
|
+
} else {
|
|
1158
|
+
added.push(line);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
for (const line of oldLines) {
|
|
1162
|
+
if (!newLines.has(line)) {
|
|
1163
|
+
removed.push(line);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return { added, removed, unchanged };
|
|
1167
|
+
}
|
|
1168
|
+
var TOOLS = [
|
|
1169
|
+
{ bin: "stale", args: ["scan"], label: "stale" },
|
|
1170
|
+
{ bin: "envalid", args: ["validate"], label: "envalid" },
|
|
1171
|
+
{ bin: "berth", args: ["status"], label: "berth" },
|
|
1172
|
+
{ bin: "vow", args: ["scan"], label: "vow" },
|
|
1173
|
+
{ bin: "aware", args: ["doctor"], label: "aware" }
|
|
1174
|
+
];
|
|
1175
|
+
function createDiffCommand() {
|
|
1176
|
+
const cmd = new Command8("diff");
|
|
1177
|
+
cmd.description("Compare cached tool results to fresh runs and show what changed");
|
|
1178
|
+
cmd.action(async () => {
|
|
1179
|
+
const cwd = process.cwd();
|
|
1180
|
+
const project = deriveProject(cwd);
|
|
1181
|
+
console.log("");
|
|
1182
|
+
console.log(colorize6(" when diff", c6.bold, c6.cyan));
|
|
1183
|
+
console.log(colorize6(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", c6.dim));
|
|
1184
|
+
console.log(` ${colorize6("project", c6.dim)} ${colorize6(project, c6.bold)}`);
|
|
1185
|
+
console.log("");
|
|
1186
|
+
let anyChanges = false;
|
|
1187
|
+
for (const tool of TOOLS) {
|
|
1188
|
+
const cached = readCache(tool.label, project);
|
|
1189
|
+
const fresh = await runCli(tool.bin, tool.args, cwd);
|
|
1190
|
+
const freshOutput = fresh.stdout.trim() || fresh.stderr.trim() || "";
|
|
1191
|
+
if (!cached) {
|
|
1192
|
+
console.log(` ${colorize6(tool.label, c6.bold, c6.cyan)}`);
|
|
1193
|
+
if (freshOutput) {
|
|
1194
|
+
for (const line of freshOutput.split("\n").slice(0, 5)) {
|
|
1195
|
+
if (line.trim()) console.log(` ${colorize6(line, c6.dim)}`);
|
|
1196
|
+
}
|
|
1197
|
+
const total = freshOutput.split("\n").filter(Boolean).length;
|
|
1198
|
+
if (total > 5) console.log(` ${colorize6(`\u2026 ${total - 5} more lines`, c6.dim)}`);
|
|
1199
|
+
} else {
|
|
1200
|
+
console.log(` ${colorize6("no output", c6.dim)}`);
|
|
1201
|
+
}
|
|
1202
|
+
console.log(` ${colorize6("(no prior cache \u2014 this is now the baseline)", c6.dim)}`);
|
|
1203
|
+
} else {
|
|
1204
|
+
const oldOutput = cached.output.trim();
|
|
1205
|
+
const { added, removed, unchanged } = diffLines(oldOutput, freshOutput);
|
|
1206
|
+
const hasChanges = added.length > 0 || removed.length > 0;
|
|
1207
|
+
if (hasChanges) anyChanges = true;
|
|
1208
|
+
console.log(` ${colorize6(tool.label, c6.bold, c6.cyan)}`);
|
|
1209
|
+
if (!hasChanges) {
|
|
1210
|
+
console.log(` ${colorize6("\u2713", c6.dim)} ${colorize6("no changes", c6.dim)} ${colorize6(`(${unchanged.length} line(s))`, c6.dim)}`);
|
|
1211
|
+
} else {
|
|
1212
|
+
for (const line of added) {
|
|
1213
|
+
console.log(` ${colorize6("+", c6.green)} ${colorize6(line, c6.green)}`);
|
|
1214
|
+
}
|
|
1215
|
+
for (const line of removed) {
|
|
1216
|
+
console.log(` ${colorize6("-", c6.red)} ${colorize6(line, c6.red)}`);
|
|
1217
|
+
}
|
|
1218
|
+
if (unchanged.length > 0) {
|
|
1219
|
+
console.log(` ${colorize6("\xB7", c6.dim)} ${colorize6(`${unchanged.length} line(s) unchanged`, c6.dim)}`);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
writeCache(tool.label, project, freshOutput, fresh.code);
|
|
1224
|
+
console.log("");
|
|
1225
|
+
}
|
|
1226
|
+
if (!anyChanges) {
|
|
1227
|
+
console.log(` ${colorize6("\u2713", c6.green)} All tools unchanged since last run`);
|
|
1228
|
+
} else {
|
|
1229
|
+
console.log(` ${colorize6("\u2022", c6.dim)} Cache updated with latest results`);
|
|
1230
|
+
}
|
|
1231
|
+
console.log("");
|
|
1232
|
+
});
|
|
1233
|
+
return cmd;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/commands/dashboard.ts
|
|
1237
|
+
import { Command as Command9 } from "commander";
|
|
1238
|
+
import { execSync as execSync2 } from "child_process";
|
|
1239
|
+
function createDashboardCommand() {
|
|
1240
|
+
const cmd = new Command9("dashboard");
|
|
1241
|
+
cmd.description("Generate an HTML velocity dashboard and open it in the browser");
|
|
1242
|
+
cmd.option("--no-open", "Write the HTML file without opening the browser");
|
|
1243
|
+
cmd.action(async (options) => {
|
|
1244
|
+
const { path, summary } = await generateDashboard();
|
|
1245
|
+
console.log(summary);
|
|
1246
|
+
if (options.open !== false) {
|
|
1247
|
+
const platform = process.platform;
|
|
1248
|
+
try {
|
|
1249
|
+
if (platform === "darwin") {
|
|
1250
|
+
execSync2(`open "${path}"`, { stdio: "ignore" });
|
|
1251
|
+
} else if (platform === "linux") {
|
|
1252
|
+
execSync2(`xdg-open "${path}"`, { stdio: "ignore" });
|
|
1253
|
+
} else {
|
|
1254
|
+
console.log(`Dashboard written to: ${path}`);
|
|
1255
|
+
}
|
|
1256
|
+
} catch {
|
|
1257
|
+
console.log(`Dashboard written to: ${path}`);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
return cmd;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// src/index.ts
|
|
1265
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1266
|
+
import { resolve as resolve6, dirname as dirname4 } from "path";
|
|
1267
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1268
|
+
var __dirname4 = dirname4(fileURLToPath4(import.meta.url));
|
|
1269
|
+
var { version } = JSON.parse(readFileSync6(resolve6(__dirname4, "..", "package.json"), "utf8"));
|
|
1270
|
+
var program = new Command10();
|
|
595
1271
|
program.name("when").version(version).description("The WhenLabs developer toolkit \u2014 6 tools, one install");
|
|
596
1272
|
program.command("install").description("Install all WhenLabs tools globally (MCP server + CLAUDE.md instructions)").option("--cursor", "Install MCP servers into Cursor (~/.cursor/mcp.json)").option("--vscode", "Install MCP servers into VS Code (settings.json)").option("--windsurf", "Install MCP servers into Windsurf (~/.codeium/windsurf/mcp_config.json)").option("--all", "Install MCP servers into all supported editors").action(async (options) => {
|
|
597
|
-
const { install } = await import("./install-
|
|
1273
|
+
const { install } = await import("./install-33GE3HKA.js");
|
|
598
1274
|
await install(options);
|
|
599
1275
|
});
|
|
600
1276
|
program.command("uninstall").description("Remove all WhenLabs tools").option("--cursor", "Remove MCP servers from Cursor").option("--vscode", "Remove MCP servers from VS Code").option("--windsurf", "Remove MCP servers from Windsurf").option("--all", "Remove MCP servers from all supported editors").action(async (options) => {
|
|
@@ -612,10 +1288,16 @@ program.command("ci").description("Run stale, envalid, and vow checks \u2014 exi
|
|
|
612
1288
|
program.addCommand(createInitCommand());
|
|
613
1289
|
program.addCommand(createDoctorCommand());
|
|
614
1290
|
program.addCommand(createWatchCommand());
|
|
1291
|
+
program.addCommand(createConfigCommand());
|
|
1292
|
+
program.addCommand(createUpgradeCommand());
|
|
1293
|
+
program.addCommand(createEjectCommand());
|
|
1294
|
+
program.addCommand(createDiffCommand());
|
|
615
1295
|
program.addCommand(createDelegateCommand("stale", "Detect documentation drift in your codebase"));
|
|
616
1296
|
program.addCommand(createDelegateCommand("envalid", "Validate .env files against a type-safe schema"));
|
|
617
1297
|
program.addCommand(createDelegateCommand("berth", "Detect and resolve port conflicts"));
|
|
618
1298
|
program.addCommand(createDelegateCommand("aware", "Auto-detect your stack and generate AI context files"));
|
|
619
1299
|
program.addCommand(createDelegateCommand("vow", "Scan dependency licenses and validate against policies"));
|
|
620
|
-
|
|
1300
|
+
var velocityCmd = createDelegateCommand("velocity", "velocity-mcp task timing server", "velocity-mcp");
|
|
1301
|
+
velocityCmd.addCommand(createDashboardCommand());
|
|
1302
|
+
program.addCommand(velocityCmd);
|
|
621
1303
|
program.parse();
|