@stackwright-pro/mcp 0.2.0-alpha.107 → 0.2.0-alpha.109
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/dist/integrity.js +9 -9
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +9 -9
- package/dist/integrity.mjs.map +1 -1
- package/dist/pipeline-constants.js +5 -0
- package/dist/pipeline-constants.js.map +1 -1
- package/dist/pipeline-constants.mjs +5 -0
- package/dist/pipeline-constants.mjs.map +1 -1
- package/dist/server.js +644 -227
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +591 -174
- package/dist/server.mjs.map +1 -1
- package/package.json +3 -3
package/dist/server.mjs
CHANGED
|
@@ -1310,6 +1310,10 @@ function answersToManifestFormat(answers, questions) {
|
|
|
1310
1310
|
// src/validation.ts
|
|
1311
1311
|
var SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
|
|
1312
1312
|
var SAFE_QUESTION_ID = /^[a-z0-9][a-z0-9-]{0,63}$/i;
|
|
1313
|
+
var SAFE_SLUG = /^[a-z0-9][a-z0-9-]{0,79}$/;
|
|
1314
|
+
function isValidSlug(slug) {
|
|
1315
|
+
return SAFE_SLUG.test(slug);
|
|
1316
|
+
}
|
|
1313
1317
|
function isValidPhase(phase) {
|
|
1314
1318
|
return SAFE_PHASE.test(phase);
|
|
1315
1319
|
}
|
|
@@ -1999,9 +2003,9 @@ function registerOrchestrationTools(server2) {
|
|
|
1999
2003
|
|
|
2000
2004
|
// src/tools/pipeline.ts
|
|
2001
2005
|
import { z as z13 } from "zod";
|
|
2002
|
-
import { readFileSync as
|
|
2006
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync4, lstatSync as lstatSync6, readdirSync as readdirSync5 } from "fs";
|
|
2003
2007
|
import { lockSync } from "proper-lockfile";
|
|
2004
|
-
import { join as
|
|
2008
|
+
import { join as join7 } from "path";
|
|
2005
2009
|
import { createHash as createHash3 } from "crypto";
|
|
2006
2010
|
|
|
2007
2011
|
// src/artifact-signing.ts
|
|
@@ -2544,6 +2548,234 @@ function formatPulseRetryPrompt(result) {
|
|
|
2544
2548
|
return lines.join("\n");
|
|
2545
2549
|
}
|
|
2546
2550
|
|
|
2551
|
+
// src/tools/validate-api-integration.ts
|
|
2552
|
+
var CANONICAL_API_AUTH_TYPES = ["apiKey", "bearer", "oauth2", "oidc", "none"];
|
|
2553
|
+
var CANONICAL_SET = new Set(CANONICAL_API_AUTH_TYPES);
|
|
2554
|
+
var VALID_SET_DISPLAY = CANONICAL_API_AUTH_TYPES.map((t) => `\`${t}\``).join(", ");
|
|
2555
|
+
var RENAME_MAP = {
|
|
2556
|
+
// apiKey variants
|
|
2557
|
+
"api-key": "apiKey",
|
|
2558
|
+
api_key: "apiKey",
|
|
2559
|
+
apikey: "apiKey",
|
|
2560
|
+
// lowercase of 'apiKey' / 'APIKEY'
|
|
2561
|
+
// oauth2 variants
|
|
2562
|
+
"oauth-2": "oauth2",
|
|
2563
|
+
oauth_2: "oauth2",
|
|
2564
|
+
// oidc variants
|
|
2565
|
+
"open-id-connect": "oidc",
|
|
2566
|
+
"openid-connect": "oidc",
|
|
2567
|
+
openidconnect: "oidc",
|
|
2568
|
+
// lowercase of 'openIdConnect'
|
|
2569
|
+
openidconnect_: "oidc",
|
|
2570
|
+
// belt-and-suspenders
|
|
2571
|
+
openIdConnect: "oidc",
|
|
2572
|
+
// exact camelCase as emitted by some tooling
|
|
2573
|
+
// bearer variants
|
|
2574
|
+
"bearer-token": "bearer",
|
|
2575
|
+
bearer_token: "bearer"
|
|
2576
|
+
};
|
|
2577
|
+
function renameSuggestion(badLiteral) {
|
|
2578
|
+
return RENAME_MAP[badLiteral] ?? RENAME_MAP[badLiteral.toLowerCase()];
|
|
2579
|
+
}
|
|
2580
|
+
function validateAuthType(authType, yamlPath, artifactPath) {
|
|
2581
|
+
if (authType === void 0 || authType === null) return null;
|
|
2582
|
+
if (typeof authType !== "string") {
|
|
2583
|
+
return [
|
|
2584
|
+
`${artifactPath}: \`${yamlPath}\` must be a string, got \`${typeof authType}\`.`,
|
|
2585
|
+
`Valid values: ${VALID_SET_DISPLAY}.`,
|
|
2586
|
+
"Fix this value and re-call stackwright_pro_validate_artifact."
|
|
2587
|
+
].join("\n");
|
|
2588
|
+
}
|
|
2589
|
+
if (CANONICAL_SET.has(authType)) return null;
|
|
2590
|
+
const suggestion = renameSuggestion(authType);
|
|
2591
|
+
const lines = [
|
|
2592
|
+
`${artifactPath}: \`${yamlPath}\` has non-canonical value \`"${authType}"\`.`,
|
|
2593
|
+
`Valid values: ${VALID_SET_DISPLAY}.`
|
|
2594
|
+
];
|
|
2595
|
+
if (suggestion) {
|
|
2596
|
+
lines.push(`Did you mean: \`${suggestion}\`?`);
|
|
2597
|
+
}
|
|
2598
|
+
lines.push("Fix this value and re-call stackwright_pro_validate_artifact.");
|
|
2599
|
+
return lines.join("\n");
|
|
2600
|
+
}
|
|
2601
|
+
function validateApiIntegration(artifact, integrationName) {
|
|
2602
|
+
const artifactFile = integrationName ? `api-config-${integrationName}.json` : "api-config.json";
|
|
2603
|
+
const artifactPath = `.stackwright/artifacts/${artifactFile}`;
|
|
2604
|
+
if (artifact === null || typeof artifact !== "object") {
|
|
2605
|
+
return { valid: true };
|
|
2606
|
+
}
|
|
2607
|
+
const obj = artifact;
|
|
2608
|
+
const errors = [];
|
|
2609
|
+
if ("auth" in obj && obj["auth"] !== null && typeof obj["auth"] === "object") {
|
|
2610
|
+
const auth = obj["auth"];
|
|
2611
|
+
const err = validateAuthType(auth["type"], "auth.type", artifactPath);
|
|
2612
|
+
if (err) errors.push(err);
|
|
2613
|
+
}
|
|
2614
|
+
if ("securitySchemes" in obj && obj["securitySchemes"] !== null && typeof obj["securitySchemes"] === "object" && !Array.isArray(obj["securitySchemes"])) {
|
|
2615
|
+
const schemes = obj["securitySchemes"];
|
|
2616
|
+
for (const [schemeName, scheme] of Object.entries(schemes)) {
|
|
2617
|
+
if (scheme !== null && typeof scheme === "object") {
|
|
2618
|
+
const s = scheme;
|
|
2619
|
+
const err = validateAuthType(s["type"], `securitySchemes.${schemeName}.type`, artifactPath);
|
|
2620
|
+
if (err) errors.push(err);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
if (errors.length === 0) return { valid: true };
|
|
2625
|
+
const retryPrompt = [
|
|
2626
|
+
`API integration auth validation failed: ${errors.length} violation(s) in ${artifactPath}.`,
|
|
2627
|
+
"",
|
|
2628
|
+
"You MUST fix ALL violations and re-call stackwright_pro_validate_artifact.",
|
|
2629
|
+
"",
|
|
2630
|
+
...errors.flatMap((e) => [e, ""]),
|
|
2631
|
+
`Canonical auth.type values: ${VALID_SET_DISPLAY}.`,
|
|
2632
|
+
"Do NOT use hyphenated or underscored variants (api-key, oauth-2, bearer-token, etc.)."
|
|
2633
|
+
].join("\n");
|
|
2634
|
+
return { valid: false, retryPrompt };
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
// src/tools/validate-auth-config.ts
|
|
2638
|
+
var KNOWN_NESTED_STRATEGIES = ["oidc", "pki"];
|
|
2639
|
+
var NESTED_STRATEGY_FIELD_EXAMPLES = {
|
|
2640
|
+
oidc: ["discoveryUrl", "clientId", "clientSecret"],
|
|
2641
|
+
pki: ["profile", "source", "headerPrefix"]
|
|
2642
|
+
};
|
|
2643
|
+
function nestedBlockError(strategy, nestedObj, artifactPath) {
|
|
2644
|
+
const exampleFields = NESTED_STRATEGY_FIELD_EXAMPLES[strategy];
|
|
2645
|
+
const actualKeys = Object.keys(nestedObj).slice(0, 2);
|
|
2646
|
+
const showField0 = actualKeys[0] ?? exampleFields[0];
|
|
2647
|
+
const showField1 = actualKeys[1] ?? exampleFields[1];
|
|
2648
|
+
return [
|
|
2649
|
+
`${artifactPath}: \`authConfig.${strategy}\` must not be a nested provider block.`,
|
|
2650
|
+
`Hoist \`authConfig.${strategy}.*\` fields to \`authConfig.*\`. The canonical AuthConfig`,
|
|
2651
|
+
`schema does not nest provider-specific fields under a named sub-object.`,
|
|
2652
|
+
``,
|
|
2653
|
+
`Bad (nested block):`,
|
|
2654
|
+
` "authConfig": {`,
|
|
2655
|
+
` "type": "${strategy}",`,
|
|
2656
|
+
` "${strategy}": {`,
|
|
2657
|
+
` "${showField0}": "...",`,
|
|
2658
|
+
` "${showField1}": "...",`,
|
|
2659
|
+
` ...`,
|
|
2660
|
+
` }`,
|
|
2661
|
+
` }`,
|
|
2662
|
+
``,
|
|
2663
|
+
`Correct (hoisted to root of authConfig):`,
|
|
2664
|
+
` "authConfig": {`,
|
|
2665
|
+
` "type": "${strategy}",`,
|
|
2666
|
+
` "${exampleFields[0]}": "...",`,
|
|
2667
|
+
` "${exampleFields[1]}": "...",`,
|
|
2668
|
+
` "${exampleFields[2]}": "...",`,
|
|
2669
|
+
` ...`,
|
|
2670
|
+
` }`,
|
|
2671
|
+
``,
|
|
2672
|
+
`Move every key from \`authConfig.${strategy}\` up to \`authConfig\` and remove the \`"${strategy}"\` sub-object.`
|
|
2673
|
+
].join("\n");
|
|
2674
|
+
}
|
|
2675
|
+
function discriminatorKeyError(methodVal, artifactPath) {
|
|
2676
|
+
return [
|
|
2677
|
+
`${artifactPath}: \`authConfig.method\` is not a valid key in the canonical AuthConfig schema.`,
|
|
2678
|
+
`Rename \`authConfig.method\` \u2192 \`authConfig.type\`. The canonical AuthConfig schema uses`,
|
|
2679
|
+
`\`type\` as the auth-strategy discriminator, matching the api-config \`auth.type\` convention`,
|
|
2680
|
+
`shipped in swp-fimq.`,
|
|
2681
|
+
``,
|
|
2682
|
+
`Bad:`,
|
|
2683
|
+
` "authConfig": { "method": "${methodVal}", ... }`,
|
|
2684
|
+
``,
|
|
2685
|
+
`Correct:`,
|
|
2686
|
+
` "authConfig": { "type": "${methodVal}", ... }`
|
|
2687
|
+
].join("\n");
|
|
2688
|
+
}
|
|
2689
|
+
function ambiguousDiscriminatorError(methodVal, typeVal, artifactPath) {
|
|
2690
|
+
return [
|
|
2691
|
+
`${artifactPath}: \`authConfig\` has both \`method: "${methodVal}"\` and \`type: "${typeVal}"\` \u2014 ambiguous discriminator.`,
|
|
2692
|
+
`Remove \`authConfig.method\` and keep only \`authConfig.type\`. The canonical AuthConfig`,
|
|
2693
|
+
`schema uses \`type\` as the sole auth-strategy discriminator.`
|
|
2694
|
+
].join("\n");
|
|
2695
|
+
}
|
|
2696
|
+
function validateAuthConfig(artifact, artifactPath) {
|
|
2697
|
+
if (artifact === null || typeof artifact !== "object") {
|
|
2698
|
+
return { valid: true };
|
|
2699
|
+
}
|
|
2700
|
+
const obj = artifact;
|
|
2701
|
+
if (!("authConfig" in obj) || obj["authConfig"] === void 0 || obj["authConfig"] === null) {
|
|
2702
|
+
return { valid: true };
|
|
2703
|
+
}
|
|
2704
|
+
if (typeof obj["authConfig"] !== "object" || Array.isArray(obj["authConfig"])) {
|
|
2705
|
+
return { valid: true };
|
|
2706
|
+
}
|
|
2707
|
+
const authConfig = obj["authConfig"];
|
|
2708
|
+
const errors = [];
|
|
2709
|
+
const hasMethod = "method" in authConfig && authConfig["method"] !== void 0;
|
|
2710
|
+
const hasType = "type" in authConfig && authConfig["type"] !== void 0;
|
|
2711
|
+
if (hasMethod && hasType) {
|
|
2712
|
+
errors.push(
|
|
2713
|
+
ambiguousDiscriminatorError(
|
|
2714
|
+
String(authConfig["method"]),
|
|
2715
|
+
String(authConfig["type"]),
|
|
2716
|
+
artifactPath
|
|
2717
|
+
)
|
|
2718
|
+
);
|
|
2719
|
+
} else if (hasMethod && !hasType) {
|
|
2720
|
+
errors.push(discriminatorKeyError(String(authConfig["method"]), artifactPath));
|
|
2721
|
+
}
|
|
2722
|
+
for (const strategy of KNOWN_NESTED_STRATEGIES) {
|
|
2723
|
+
const nested = authConfig[strategy];
|
|
2724
|
+
if (nested !== null && nested !== void 0 && typeof nested === "object" && !Array.isArray(nested)) {
|
|
2725
|
+
errors.push(nestedBlockError(strategy, nested, artifactPath));
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
if (errors.length === 0) return { valid: true };
|
|
2729
|
+
const retryPrompt = [
|
|
2730
|
+
`Auth config validation failed: ${errors.length} violation(s) in ${artifactPath}.`,
|
|
2731
|
+
``,
|
|
2732
|
+
`You MUST fix ALL violations and re-call stackwright_pro_validate_artifact.`,
|
|
2733
|
+
``,
|
|
2734
|
+
...errors.flatMap((e) => [e, ""]),
|
|
2735
|
+
`The canonical authConfig shape uses \`type\` (not \`method\`) as the discriminator,`,
|
|
2736
|
+
`with all provider-specific fields hoisted to the root of \`authConfig\`.`,
|
|
2737
|
+
`AuthConfig is a discriminated union on \`type: 'oidc' | 'pki'\`.`
|
|
2738
|
+
].join("\n");
|
|
2739
|
+
return { valid: false, retryPrompt };
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
// src/tools/auth-manifest-aggregator.ts
|
|
2743
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync3 } from "fs";
|
|
2744
|
+
import { join as join5 } from "path";
|
|
2745
|
+
function extractPatternBaseSlug(pattern) {
|
|
2746
|
+
const stripped = pattern.replace(/^\//, "");
|
|
2747
|
+
if (!stripped) return null;
|
|
2748
|
+
return stripped.split("/")[0] ?? null;
|
|
2749
|
+
}
|
|
2750
|
+
function extractManifestSlugs(artifactsDir) {
|
|
2751
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
2752
|
+
if (!existsSync6(artifactsDir)) return slugs;
|
|
2753
|
+
let entries;
|
|
2754
|
+
try {
|
|
2755
|
+
entries = readdirSync3(artifactsDir);
|
|
2756
|
+
} catch {
|
|
2757
|
+
return slugs;
|
|
2758
|
+
}
|
|
2759
|
+
for (const entry of entries) {
|
|
2760
|
+
if (!entry.endsWith("-manifest.json")) continue;
|
|
2761
|
+
try {
|
|
2762
|
+
const content = JSON.parse(readFileSync5(join5(artifactsDir, entry), "utf-8"));
|
|
2763
|
+
const pages = content.pages;
|
|
2764
|
+
if (!Array.isArray(pages)) continue;
|
|
2765
|
+
for (const page of pages) {
|
|
2766
|
+
if (typeof page.slug !== "string" || !page.slug) continue;
|
|
2767
|
+
const firstSeg = page.slug.split("/")[0];
|
|
2768
|
+
if (firstSeg) slugs.add(firstSeg);
|
|
2769
|
+
if (!page.slug.includes("[") && !page.slug.includes(":")) {
|
|
2770
|
+
slugs.add(page.slug);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
} catch {
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
return slugs;
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2547
2779
|
// src/tools/pipeline.ts
|
|
2548
2780
|
import { WorkflowFileSchema, authConfigSchema as authConfigSchema3 } from "@stackwright-pro/types";
|
|
2549
2781
|
|
|
@@ -2894,8 +3126,8 @@ function registerGetSchemaTool(server2) {
|
|
|
2894
3126
|
}
|
|
2895
3127
|
|
|
2896
3128
|
// src/tools/pipeline-graph.ts
|
|
2897
|
-
import { readFileSync as
|
|
2898
|
-
import { join as
|
|
3129
|
+
import { readFileSync as readFileSync6, readdirSync as readdirSync4, lstatSync as lstatSync5 } from "fs";
|
|
3130
|
+
import { join as join6 } from "path";
|
|
2899
3131
|
var MANIFEST_NAME_TO_PHASE = {
|
|
2900
3132
|
"stackwright-pro-designer-otter": "designer",
|
|
2901
3133
|
"stackwright-pro-theme-otter": "theme",
|
|
@@ -2925,7 +3157,7 @@ var OTTER_SEARCH_PATHS = [
|
|
|
2925
3157
|
function resolveOtterDirFromCwd() {
|
|
2926
3158
|
const cwd = process.cwd();
|
|
2927
3159
|
for (const relative2 of OTTER_SEARCH_PATHS) {
|
|
2928
|
-
const candidate =
|
|
3160
|
+
const candidate = join6(cwd, relative2);
|
|
2929
3161
|
try {
|
|
2930
3162
|
lstatSync5(candidate);
|
|
2931
3163
|
return candidate;
|
|
@@ -2937,14 +3169,14 @@ function resolveOtterDirFromCwd() {
|
|
|
2937
3169
|
);
|
|
2938
3170
|
}
|
|
2939
3171
|
function loadOtterManifestsFromDir(otterDir) {
|
|
2940
|
-
const entries =
|
|
3172
|
+
const entries = readdirSync4(otterDir);
|
|
2941
3173
|
const manifests = [];
|
|
2942
3174
|
for (const filename of entries) {
|
|
2943
3175
|
if (!filename.endsWith("-otter.json")) continue;
|
|
2944
|
-
const filePath =
|
|
3176
|
+
const filePath = join6(otterDir, filename);
|
|
2945
3177
|
if (lstatSync5(filePath).isSymbolicLink()) continue;
|
|
2946
3178
|
try {
|
|
2947
|
-
const raw =
|
|
3179
|
+
const raw = readFileSync6(filePath, "utf-8");
|
|
2948
3180
|
const manifest = JSON.parse(raw);
|
|
2949
3181
|
manifests.push(manifest);
|
|
2950
3182
|
} catch (err) {
|
|
@@ -3049,19 +3281,19 @@ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
|
|
|
3049
3281
|
function loadDistributedOtterManifests(cwd) {
|
|
3050
3282
|
const results = [];
|
|
3051
3283
|
for (const namespace of DISTRIBUTED_NAMESPACES) {
|
|
3052
|
-
const nsDir =
|
|
3284
|
+
const nsDir = join6(cwd, "node_modules", namespace);
|
|
3053
3285
|
let pkgDirNames;
|
|
3054
3286
|
try {
|
|
3055
|
-
pkgDirNames =
|
|
3287
|
+
pkgDirNames = readdirSync4(nsDir);
|
|
3056
3288
|
} catch {
|
|
3057
3289
|
continue;
|
|
3058
3290
|
}
|
|
3059
3291
|
for (const pkgDirName of pkgDirNames) {
|
|
3060
|
-
const pkgDir =
|
|
3292
|
+
const pkgDir = join6(nsDir, pkgDirName);
|
|
3061
3293
|
const packageName = `${namespace}/${pkgDirName}`;
|
|
3062
3294
|
let otterRelPaths;
|
|
3063
3295
|
try {
|
|
3064
|
-
const raw =
|
|
3296
|
+
const raw = readFileSync6(join6(pkgDir, "package.json"), "utf-8");
|
|
3065
3297
|
const pkgJson = JSON.parse(raw);
|
|
3066
3298
|
const declared = pkgJson.stackwright?.otters;
|
|
3067
3299
|
if (!Array.isArray(declared) || declared.length === 0) continue;
|
|
@@ -3070,9 +3302,9 @@ function loadDistributedOtterManifests(cwd) {
|
|
|
3070
3302
|
continue;
|
|
3071
3303
|
}
|
|
3072
3304
|
for (const relPath of otterRelPaths) {
|
|
3073
|
-
const otterFilePath =
|
|
3305
|
+
const otterFilePath = join6(pkgDir, relPath);
|
|
3074
3306
|
try {
|
|
3075
|
-
const raw =
|
|
3307
|
+
const raw = readFileSync6(otterFilePath, "utf-8");
|
|
3076
3308
|
const manifest = JSON.parse(raw);
|
|
3077
3309
|
results.push({ manifest, packageName });
|
|
3078
3310
|
} catch (err) {
|
|
@@ -3190,11 +3422,11 @@ function createDefaultState() {
|
|
|
3190
3422
|
};
|
|
3191
3423
|
}
|
|
3192
3424
|
function statePath(cwd) {
|
|
3193
|
-
return
|
|
3425
|
+
return join7(cwd, ".stackwright", "pipeline-state.json");
|
|
3194
3426
|
}
|
|
3195
3427
|
function readState(cwd) {
|
|
3196
3428
|
const p = statePath(cwd);
|
|
3197
|
-
if (!
|
|
3429
|
+
if (!existsSync7(p)) return createDefaultState();
|
|
3198
3430
|
const raw = JSON.parse(safeReadSync(p));
|
|
3199
3431
|
if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
|
|
3200
3432
|
return createDefaultState();
|
|
@@ -3202,7 +3434,7 @@ function readState(cwd) {
|
|
|
3202
3434
|
return raw;
|
|
3203
3435
|
}
|
|
3204
3436
|
function safeWriteSync(filePath, content) {
|
|
3205
|
-
if (
|
|
3437
|
+
if (existsSync7(filePath)) {
|
|
3206
3438
|
const stat = lstatSync6(filePath);
|
|
3207
3439
|
if (stat.isSymbolicLink()) {
|
|
3208
3440
|
throw new Error(`Refusing to write to symlink: ${filePath}`);
|
|
@@ -3211,25 +3443,25 @@ function safeWriteSync(filePath, content) {
|
|
|
3211
3443
|
writeFileSync4(filePath, content);
|
|
3212
3444
|
}
|
|
3213
3445
|
function safeReadSync(filePath) {
|
|
3214
|
-
if (
|
|
3446
|
+
if (existsSync7(filePath)) {
|
|
3215
3447
|
const stat = lstatSync6(filePath);
|
|
3216
3448
|
if (stat.isSymbolicLink()) {
|
|
3217
3449
|
throw new Error(`Refusing to read symlink: ${filePath}`);
|
|
3218
3450
|
}
|
|
3219
3451
|
}
|
|
3220
|
-
return
|
|
3452
|
+
return readFileSync7(filePath, "utf-8");
|
|
3221
3453
|
}
|
|
3222
3454
|
function writeState(cwd, state) {
|
|
3223
|
-
const dir =
|
|
3455
|
+
const dir = join7(cwd, ".stackwright");
|
|
3224
3456
|
mkdirSync4(dir, { recursive: true });
|
|
3225
3457
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3226
3458
|
safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
3227
3459
|
}
|
|
3228
3460
|
function updateStateAtomic(cwd, mutator) {
|
|
3229
|
-
const dir =
|
|
3461
|
+
const dir = join7(cwd, ".stackwright");
|
|
3230
3462
|
mkdirSync4(dir, { recursive: true });
|
|
3231
3463
|
const path5 = statePath(cwd);
|
|
3232
|
-
if (!
|
|
3464
|
+
if (!existsSync7(path5)) {
|
|
3233
3465
|
safeWriteSync(path5, JSON.stringify(createDefaultState(), null, 2) + "\n");
|
|
3234
3466
|
}
|
|
3235
3467
|
const release = lockSync(path5, {
|
|
@@ -3263,8 +3495,8 @@ function handleGetPipelineState(_cwd) {
|
|
|
3263
3495
|
const cwd = _cwd ?? process.cwd();
|
|
3264
3496
|
try {
|
|
3265
3497
|
const state = readState(cwd);
|
|
3266
|
-
const keyPath =
|
|
3267
|
-
if (!
|
|
3498
|
+
const keyPath = join7(cwd, ".stackwright", "pipeline-keys.json");
|
|
3499
|
+
if (!existsSync7(keyPath)) {
|
|
3268
3500
|
try {
|
|
3269
3501
|
const { fingerprint } = initPipelineKeys(cwd);
|
|
3270
3502
|
state["signingKeyFingerprint"] = fingerprint;
|
|
@@ -3395,8 +3627,8 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
3395
3627
|
isError: true
|
|
3396
3628
|
};
|
|
3397
3629
|
}
|
|
3398
|
-
const answerFile =
|
|
3399
|
-
if (!
|
|
3630
|
+
const answerFile = join7(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3631
|
+
if (!existsSync7(answerFile)) {
|
|
3400
3632
|
return {
|
|
3401
3633
|
text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
|
|
3402
3634
|
isError: false
|
|
@@ -3420,12 +3652,12 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
3420
3652
|
}
|
|
3421
3653
|
}
|
|
3422
3654
|
try {
|
|
3423
|
-
const answersDir =
|
|
3655
|
+
const answersDir = join7(cwd, ".stackwright", "answers");
|
|
3424
3656
|
const answeredPhases = [];
|
|
3425
3657
|
const missingPhases = [];
|
|
3426
3658
|
for (const phase2 of PHASE_ORDER) {
|
|
3427
|
-
const answerFile =
|
|
3428
|
-
if (
|
|
3659
|
+
const answerFile = join7(answersDir, `${phase2}.json`);
|
|
3660
|
+
if (existsSync7(answerFile)) {
|
|
3429
3661
|
try {
|
|
3430
3662
|
const raw = safeReadSync(answerFile);
|
|
3431
3663
|
const parsed = JSON.parse(raw);
|
|
@@ -3494,7 +3726,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3494
3726
|
function handleListArtifacts(_cwd) {
|
|
3495
3727
|
const cwd = _cwd ?? process.cwd();
|
|
3496
3728
|
try {
|
|
3497
|
-
const artifactsDir =
|
|
3729
|
+
const artifactsDir = join7(cwd, ".stackwright", "artifacts");
|
|
3498
3730
|
let manifest = null;
|
|
3499
3731
|
try {
|
|
3500
3732
|
manifest = loadSignatureManifest(cwd);
|
|
@@ -3504,8 +3736,8 @@ function handleListArtifacts(_cwd) {
|
|
|
3504
3736
|
let completedCount = 0;
|
|
3505
3737
|
for (const phase of PHASE_ORDER) {
|
|
3506
3738
|
const expectedFile = PHASE_ARTIFACT[phase];
|
|
3507
|
-
const fullPath =
|
|
3508
|
-
const exists =
|
|
3739
|
+
const fullPath = join7(artifactsDir, expectedFile);
|
|
3740
|
+
const exists = existsSync7(fullPath);
|
|
3509
3741
|
if (exists) completedCount++;
|
|
3510
3742
|
let signed = false;
|
|
3511
3743
|
let signatureValid = null;
|
|
@@ -3558,9 +3790,9 @@ function handleWritePhaseQuestions(input) {
|
|
|
3558
3790
|
}
|
|
3559
3791
|
} catch {
|
|
3560
3792
|
}
|
|
3561
|
-
const questionsDir =
|
|
3793
|
+
const questionsDir = join7(cwd, ".stackwright", "questions");
|
|
3562
3794
|
mkdirSync4(questionsDir, { recursive: true });
|
|
3563
|
-
const filePath =
|
|
3795
|
+
const filePath = join7(questionsDir, `${phase}.json`);
|
|
3564
3796
|
const payload = {
|
|
3565
3797
|
version: "1.0",
|
|
3566
3798
|
phase,
|
|
@@ -3600,14 +3832,14 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3600
3832
|
};
|
|
3601
3833
|
}
|
|
3602
3834
|
try {
|
|
3603
|
-
const answersPath =
|
|
3835
|
+
const answersPath = join7(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3604
3836
|
let answers = {};
|
|
3605
|
-
if (
|
|
3837
|
+
if (existsSync7(answersPath)) {
|
|
3606
3838
|
answers = JSON.parse(safeReadSync(answersPath));
|
|
3607
3839
|
}
|
|
3608
3840
|
let buildContextText = "";
|
|
3609
|
-
const buildContextPath =
|
|
3610
|
-
if (
|
|
3841
|
+
const buildContextPath = join7(cwd, ".stackwright", "build-context.json");
|
|
3842
|
+
if (existsSync7(buildContextPath)) {
|
|
3611
3843
|
try {
|
|
3612
3844
|
const bcRaw = JSON.parse(safeReadSync(buildContextPath));
|
|
3613
3845
|
if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
|
|
@@ -3622,8 +3854,8 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3622
3854
|
const missingDependencies = [];
|
|
3623
3855
|
for (const dep of deps) {
|
|
3624
3856
|
const artifactFile = PHASE_ARTIFACT[dep];
|
|
3625
|
-
const artifactPath =
|
|
3626
|
-
if (
|
|
3857
|
+
const artifactPath = join7(cwd, ".stackwright", "artifacts", artifactFile);
|
|
3858
|
+
if (existsSync7(artifactPath)) {
|
|
3627
3859
|
const rawContent = safeReadSync(artifactPath);
|
|
3628
3860
|
const rawBytes = Buffer.from(rawContent, "utf-8");
|
|
3629
3861
|
const content = JSON.parse(rawContent);
|
|
@@ -3688,8 +3920,8 @@ ${JSON.stringify(content, null, 2)}`
|
|
|
3688
3920
|
parts.push("BUILD_CONTEXT:", buildContextText, "");
|
|
3689
3921
|
}
|
|
3690
3922
|
if (phase === "auth") {
|
|
3691
|
-
const initContextPath =
|
|
3692
|
-
if (
|
|
3923
|
+
const initContextPath = join7(cwd, ".stackwright", "init-context.json");
|
|
3924
|
+
if (existsSync7(initContextPath)) {
|
|
3693
3925
|
try {
|
|
3694
3926
|
const initContext = JSON.parse(safeReadSync(initContextPath));
|
|
3695
3927
|
if (initContext.devOnly === true || initContext.nonInteractive === true) {
|
|
@@ -4140,7 +4372,87 @@ function _validateArtifactInner(input) {
|
|
|
4140
4372
|
};
|
|
4141
4373
|
return { text: JSON.stringify(result), isError: false };
|
|
4142
4374
|
}
|
|
4375
|
+
if (phase === "auth") {
|
|
4376
|
+
const authArtifactPath = `.stackwright/artifacts/${PHASE_ARTIFACT["auth"]}`;
|
|
4377
|
+
const authShapeResult = validateAuthConfig(artifact, authArtifactPath);
|
|
4378
|
+
if (!authShapeResult.valid) {
|
|
4379
|
+
const result = {
|
|
4380
|
+
valid: false,
|
|
4381
|
+
phase,
|
|
4382
|
+
violation: "schema-mismatch",
|
|
4383
|
+
retryPrompt: authShapeResult.retryPrompt
|
|
4384
|
+
};
|
|
4385
|
+
return { text: JSON.stringify(result), isError: false };
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
if (phase === "auth") {
|
|
4389
|
+
const rawUncovered = artifact["uncoveredSlugs"];
|
|
4390
|
+
if (Array.isArray(rawUncovered) && rawUncovered.length > 0) {
|
|
4391
|
+
const otterFromSource = (source) => {
|
|
4392
|
+
if (source === "geo-manifest.json") return "geo-otter";
|
|
4393
|
+
if (source === "dashboard-manifest.json") return "dashboard-otter";
|
|
4394
|
+
if (source === "pages-manifest.json") return "page-otter";
|
|
4395
|
+
return source || "(unknown)";
|
|
4396
|
+
};
|
|
4397
|
+
const slugLines = rawUncovered.map((entry) => {
|
|
4398
|
+
const e = entry ?? {};
|
|
4399
|
+
const source = typeof e["source"] === "string" ? e["source"] : "(unknown)";
|
|
4400
|
+
const slug = typeof e["slug"] === "string" ? e["slug"] : "(unknown)";
|
|
4401
|
+
return ` ${source} \u2192 ${slug} (emitted by ${otterFromSource(source)})`;
|
|
4402
|
+
});
|
|
4403
|
+
const retryPrompt = [
|
|
4404
|
+
"Auth-config cannot be finalized: the following page slugs have no auth declaration in their emitting otter's manifest:",
|
|
4405
|
+
"",
|
|
4406
|
+
...slugLines,
|
|
4407
|
+
"",
|
|
4408
|
+
"This is a HARD build-gate failure \u2014 auth-otter cannot self-correct because the root cause is upstream.",
|
|
4409
|
+
"",
|
|
4410
|
+
"Fix: update the offending emitter otter to declare `authRequired: true` + `requiredRoles: [...]`",
|
|
4411
|
+
"(and optionally `tier`, `authRationale`) in the manifest entry for each listed slug.",
|
|
4412
|
+
"",
|
|
4413
|
+
"For map pages: geo-otter (see swp-1114).",
|
|
4414
|
+
"For dashboard pages: dashboard-otter (see swp-52lk).",
|
|
4415
|
+
"For page pages: page-otter (already does this \u2014 check for regressions).",
|
|
4416
|
+
"For workflow pages: form-wizard-otter emits routes but auth is derived from the pages-manifest",
|
|
4417
|
+
"workflow-wrapper entry \u2014 check page-otter didn't skip a workflow-wrapper.",
|
|
4418
|
+
"",
|
|
4419
|
+
"After updating the offending manifest, re-run the pipeline from the affected phase."
|
|
4420
|
+
].join("\n");
|
|
4421
|
+
const result = {
|
|
4422
|
+
valid: false,
|
|
4423
|
+
phase,
|
|
4424
|
+
violation: "uncovered-slugs",
|
|
4425
|
+
retryPrompt
|
|
4426
|
+
};
|
|
4427
|
+
return { text: JSON.stringify(result), isError: false };
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4143
4430
|
const PHASE_ZOD_VALIDATORS = {
|
|
4431
|
+
// 5.0 Theme defaultColorMode hygiene (swp-0ryn / swp-mw97)
|
|
4432
|
+
//
|
|
4433
|
+
// DHL raft runs produced `defaultColorMode: "auto"` (not a valid value) and
|
|
4434
|
+
// `defaultColorMode: "dark"` when the designer said "respect OS preference".
|
|
4435
|
+
// This validator catches the `auto` literal at write-time so the otter
|
|
4436
|
+
// self-corrects before the artifact lands on disk.
|
|
4437
|
+
//
|
|
4438
|
+
// Valid values: 'light' | 'dark' | 'system'
|
|
4439
|
+
// Omitting the key entirely is also valid (app shell honors prefers-color-scheme).
|
|
4440
|
+
theme: (artifact2) => {
|
|
4441
|
+
const dcm = artifact2["defaultColorMode"];
|
|
4442
|
+
if (dcm === void 0 || dcm === null) {
|
|
4443
|
+
return { success: true };
|
|
4444
|
+
}
|
|
4445
|
+
const VALID_COLOR_MODES = ["light", "dark", "system"];
|
|
4446
|
+
if (!VALID_COLOR_MODES.includes(dcm)) {
|
|
4447
|
+
return {
|
|
4448
|
+
success: false,
|
|
4449
|
+
error: {
|
|
4450
|
+
message: `theme-tokens.json: \`defaultColorMode: "${String(dcm)}"\` is not a valid value. Valid values are \`light\`, \`dark\`, or \`system\`. Use \`system\` for OS-following behavior (respects \`prefers-color-scheme\`). Alternatively, OMIT the \`defaultColorMode\` key entirely when the designer has specified in design-language.json operationalNotes that OS preference should be respected (trigger phrases: "respect OS preference", "no forced default").`
|
|
4451
|
+
}
|
|
4452
|
+
};
|
|
4453
|
+
}
|
|
4454
|
+
return { success: true };
|
|
4455
|
+
},
|
|
4144
4456
|
workflow: (artifact2) => {
|
|
4145
4457
|
const workflowData = artifact2["workflow"];
|
|
4146
4458
|
if (!workflowData) {
|
|
@@ -4190,33 +4502,60 @@ function _validateArtifactInner(input) {
|
|
|
4190
4502
|
return { success: true };
|
|
4191
4503
|
},
|
|
4192
4504
|
// swp-4071 — opt-in cross-reference validator for the services phase.
|
|
4193
|
-
//
|
|
4194
|
-
//
|
|
4505
|
+
// Collects serviceHooks from ALL workflow-config-*.json artifacts (swp-x8sm fan-out)
|
|
4506
|
+
// plus the legacy single-file workflow-config.json (backward compat).
|
|
4507
|
+
// If any workflow declared serviceHooks, every ref must resolve to a flow or
|
|
4508
|
+
// state-machine in this services-config artifact.
|
|
4195
4509
|
// Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
|
|
4196
4510
|
services: (artifact2) => {
|
|
4197
|
-
const
|
|
4198
|
-
|
|
4199
|
-
|
|
4511
|
+
const artifactsDir = join7(cwd, ".stackwright", "artifacts");
|
|
4512
|
+
const workflowArtifactFiles = [];
|
|
4513
|
+
if (existsSync7(artifactsDir)) {
|
|
4514
|
+
try {
|
|
4515
|
+
const entries = readdirSync5(artifactsDir);
|
|
4516
|
+
for (const entry of entries) {
|
|
4517
|
+
if (entry.startsWith("workflow-config-") && entry.endsWith(".json")) {
|
|
4518
|
+
workflowArtifactFiles.push(join7(artifactsDir, entry));
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
} catch {
|
|
4522
|
+
}
|
|
4200
4523
|
}
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
}
|
|
4524
|
+
const legacyPath = join7(artifactsDir, "workflow-config.json");
|
|
4525
|
+
if (existsSync7(legacyPath)) {
|
|
4526
|
+
workflowArtifactFiles.push(legacyPath);
|
|
4527
|
+
}
|
|
4528
|
+
if (workflowArtifactFiles.length === 0) {
|
|
4205
4529
|
return { success: true };
|
|
4206
4530
|
}
|
|
4207
|
-
const declaredHooks =
|
|
4208
|
-
|
|
4531
|
+
const declaredHooks = [];
|
|
4532
|
+
for (const filePath of workflowArtifactFiles) {
|
|
4533
|
+
try {
|
|
4534
|
+
const parsed = JSON.parse(readFileSync7(filePath, "utf-8"));
|
|
4535
|
+
if (Array.isArray(parsed.serviceHooks)) {
|
|
4536
|
+
declaredHooks.push(...parsed.serviceHooks);
|
|
4537
|
+
}
|
|
4538
|
+
} catch {
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
const seenRefs = /* @__PURE__ */ new Set();
|
|
4542
|
+
const uniqueHooks = declaredHooks.filter((h) => {
|
|
4543
|
+
if (seenRefs.has(h.ref)) return false;
|
|
4544
|
+
seenRefs.add(h.ref);
|
|
4545
|
+
return true;
|
|
4546
|
+
});
|
|
4547
|
+
if (uniqueHooks.length === 0) {
|
|
4209
4548
|
return { success: true };
|
|
4210
4549
|
}
|
|
4211
4550
|
const flows = artifact2["flows"] ?? [];
|
|
4212
4551
|
const workflows = artifact2["workflows"] ?? [];
|
|
4213
4552
|
const flowNames = /* @__PURE__ */ new Set([...flows.map((f) => f.name), ...workflows.map((w) => w.name)]);
|
|
4214
|
-
const unresolved =
|
|
4553
|
+
const unresolved = uniqueHooks.filter((h) => !flowNames.has(h.ref));
|
|
4215
4554
|
if (unresolved.length > 0) {
|
|
4216
4555
|
return {
|
|
4217
4556
|
success: false,
|
|
4218
4557
|
error: {
|
|
4219
|
-
message: `Workflow declared ${
|
|
4558
|
+
message: `Workflow(s) declared ${uniqueHooks.length} service hook(s) but ${unresolved.length} unresolved by services-config: ` + unresolved.map((h) => `${h.ref} (${h.kind ?? "unknown"}: ${h.purpose ?? "no purpose"})`).join("; ") + ". Either add matching flows to services-config OR remove the hooks from the workflow YAML."
|
|
4220
4559
|
}
|
|
4221
4560
|
};
|
|
4222
4561
|
}
|
|
@@ -4282,11 +4621,55 @@ function _validateArtifactInner(input) {
|
|
|
4282
4621
|
return { text: JSON.stringify(result), isError: false };
|
|
4283
4622
|
}
|
|
4284
4623
|
}
|
|
4624
|
+
if (phase === "api") {
|
|
4625
|
+
const apiAuthResult = validateApiIntegration(artifact, input.integrationName);
|
|
4626
|
+
if (!apiAuthResult.valid) {
|
|
4627
|
+
const result = {
|
|
4628
|
+
valid: false,
|
|
4629
|
+
phase,
|
|
4630
|
+
violation: "schema-mismatch",
|
|
4631
|
+
retryPrompt: apiAuthResult.retryPrompt
|
|
4632
|
+
};
|
|
4633
|
+
return { text: JSON.stringify(result), isError: false };
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
if (phase === "workflow" && input.workflowName !== void 0) {
|
|
4637
|
+
if (!isValidSlug(input.workflowName)) {
|
|
4638
|
+
const result = {
|
|
4639
|
+
valid: false,
|
|
4640
|
+
phase,
|
|
4641
|
+
violation: "schema-mismatch",
|
|
4642
|
+
retryPrompt: `workflowName "${input.workflowName}" is not a valid slug. Use lowercase kebab-case (a-z, 0-9, hyphens only, no spaces or path separators). Example: "patient-evacuation", "ambulance-crew-dispatch".`
|
|
4643
|
+
};
|
|
4644
|
+
return { text: JSON.stringify(result), isError: false };
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
let authOrphanWarning;
|
|
4648
|
+
if (phase === "auth") {
|
|
4649
|
+
const authCfg = artifact["authConfig"];
|
|
4650
|
+
const routes = authCfg?.["protectedRoutes"];
|
|
4651
|
+
if (Array.isArray(routes) && routes.length > 0) {
|
|
4652
|
+
const artifactsDir = join7(cwd, ".stackwright", "artifacts");
|
|
4653
|
+
const knownSlugs = extractManifestSlugs(artifactsDir);
|
|
4654
|
+
const orphanPatterns = [];
|
|
4655
|
+
for (const route of routes) {
|
|
4656
|
+
const pattern = typeof route === "string" ? route : route.pattern ?? "";
|
|
4657
|
+
if (!pattern) continue;
|
|
4658
|
+
const baseSlug = extractPatternBaseSlug(pattern);
|
|
4659
|
+
if (baseSlug && !knownSlugs.has(baseSlug)) {
|
|
4660
|
+
orphanPatterns.push(pattern);
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
if (orphanPatterns.length > 0) {
|
|
4664
|
+
authOrphanWarning = `The following protectedRoute patterns don't match any known page slug: ${orphanPatterns.map((p) => `"${p}"`).join(", ")}. If these are intentional cross-cutting rules (e.g., /admin/:path*, /audit/:path*), keep them; otherwise remove.`;
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4285
4668
|
try {
|
|
4286
|
-
const artifactsDir =
|
|
4669
|
+
const artifactsDir = join7(cwd, ".stackwright", "artifacts");
|
|
4287
4670
|
mkdirSync4(artifactsDir, { recursive: true });
|
|
4288
|
-
const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : PHASE_ARTIFACT[phase];
|
|
4289
|
-
const artifactPath =
|
|
4671
|
+
const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : phase === "workflow" && input.workflowName ? `workflow-config-${input.workflowName}.json` : PHASE_ARTIFACT[phase];
|
|
4672
|
+
const artifactPath = join7(artifactsDir, artifactFile);
|
|
4290
4673
|
const serialized = JSON.stringify(artifact, null, 2) + "\n";
|
|
4291
4674
|
const artifactBytes = Buffer.from(serialized, "utf-8");
|
|
4292
4675
|
safeWriteSync(artifactPath, serialized);
|
|
@@ -4309,7 +4692,8 @@ function _validateArtifactInner(input) {
|
|
|
4309
4692
|
valid: true,
|
|
4310
4693
|
phase,
|
|
4311
4694
|
artifactPath,
|
|
4312
|
-
summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}
|
|
4695
|
+
summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`,
|
|
4696
|
+
...authOrphanWarning ? { retryPrompt: authOrphanWarning } : {}
|
|
4313
4697
|
};
|
|
4314
4698
|
return { text: JSON.stringify(result), isError: false };
|
|
4315
4699
|
} catch (err) {
|
|
@@ -4505,9 +4889,9 @@ function registerPipelineTools(server2) {
|
|
|
4505
4889
|
isError: true
|
|
4506
4890
|
};
|
|
4507
4891
|
}
|
|
4508
|
-
const questionsDir =
|
|
4892
|
+
const questionsDir = join7(process.cwd(), ".stackwright", "questions");
|
|
4509
4893
|
mkdirSync4(questionsDir, { recursive: true });
|
|
4510
|
-
const outPath =
|
|
4894
|
+
const outPath = join7(questionsDir, `${phase}.json`);
|
|
4511
4895
|
writeFileSync4(
|
|
4512
4896
|
outPath,
|
|
4513
4897
|
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
@@ -4547,15 +4931,19 @@ function registerPipelineTools(server2) {
|
|
|
4547
4931
|
),
|
|
4548
4932
|
integrationName: z13.string().optional().describe(
|
|
4549
4933
|
'When phase="api": write to api-config-<integrationName>.json instead of api-config.json. Enables concurrent per-spec fan-out without collision (swp-530y).'
|
|
4934
|
+
),
|
|
4935
|
+
workflowName: z13.string().optional().describe(
|
|
4936
|
+
`When phase="workflow": write to workflow-config-<workflowName>.json instead of workflow-config.json. Enables concurrent per-workflow fan-out without collision (swp-x8sm). Must be a kebab-case slug matching the workflow's id field.`
|
|
4550
4937
|
)
|
|
4551
4938
|
},
|
|
4552
|
-
async ({ phase, responseText, artifact, integrationName }) => {
|
|
4939
|
+
async ({ phase, responseText, artifact, integrationName, workflowName }) => {
|
|
4553
4940
|
if (artifact) {
|
|
4554
4941
|
return res(
|
|
4555
4942
|
handleValidateArtifact({
|
|
4556
4943
|
phase,
|
|
4557
4944
|
artifact,
|
|
4558
|
-
...integrationName ? { integrationName } : {}
|
|
4945
|
+
...integrationName ? { integrationName } : {},
|
|
4946
|
+
...workflowName ? { workflowName } : {}
|
|
4559
4947
|
})
|
|
4560
4948
|
);
|
|
4561
4949
|
}
|
|
@@ -4563,7 +4951,8 @@ function registerPipelineTools(server2) {
|
|
|
4563
4951
|
handleValidateArtifact({
|
|
4564
4952
|
phase,
|
|
4565
4953
|
responseText: responseText ?? "",
|
|
4566
|
-
...integrationName ? { integrationName } : {}
|
|
4954
|
+
...integrationName ? { integrationName } : {},
|
|
4955
|
+
...workflowName ? { workflowName } : {}
|
|
4567
4956
|
})
|
|
4568
4957
|
);
|
|
4569
4958
|
}
|
|
@@ -4595,8 +4984,8 @@ function registerPipelineTools(server2) {
|
|
|
4595
4984
|
|
|
4596
4985
|
// src/tools/safe-write.ts
|
|
4597
4986
|
import { z as z14 } from "zod";
|
|
4598
|
-
import { writeFileSync as writeFileSync5, readFileSync as
|
|
4599
|
-
import { normalize, isAbsolute, dirname, join as
|
|
4987
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
|
|
4988
|
+
import { normalize, isAbsolute, dirname, join as join8 } from "path";
|
|
4600
4989
|
import { emit as emit2 } from "@stackwright-pro/telemetry";
|
|
4601
4990
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
4602
4991
|
"stackwright-pro-designer-otter": [
|
|
@@ -4608,6 +4997,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
4608
4997
|
prefix: "stackwright.theme.",
|
|
4609
4998
|
suffix: ".yml",
|
|
4610
4999
|
description: "Theme config YAML (merged at build time)"
|
|
5000
|
+
},
|
|
5001
|
+
{
|
|
5002
|
+
prefix: "stackwright.yml",
|
|
5003
|
+
suffix: "",
|
|
5004
|
+
description: "Stackwright config (themeName + customTheme writeback)"
|
|
4611
5005
|
}
|
|
4612
5006
|
],
|
|
4613
5007
|
"stackwright-pro-auth-otter": [
|
|
@@ -4662,6 +5056,17 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
4662
5056
|
prefix: ".stackwright/artifacts/",
|
|
4663
5057
|
suffix: ".json",
|
|
4664
5058
|
description: "Scaffold manifest artifact"
|
|
5059
|
+
},
|
|
5060
|
+
// swp-89w5: scaffold-otter initialises mock backend wiring via stackwright_pro_emit_env_local
|
|
5061
|
+
{
|
|
5062
|
+
prefix: ".env.local",
|
|
5063
|
+
suffix: "",
|
|
5064
|
+
description: "Dotenv runtime config (mock backend wiring: NEXT_PUBLIC_MOCK_BACKEND + per-integration mock keys)"
|
|
5065
|
+
},
|
|
5066
|
+
{
|
|
5067
|
+
prefix: ".env.local.example",
|
|
5068
|
+
suffix: "",
|
|
5069
|
+
description: "Dotenv template (schema for real production values)"
|
|
4665
5070
|
}
|
|
4666
5071
|
],
|
|
4667
5072
|
"stackwright-pro-api-otter": [
|
|
@@ -4778,21 +5183,21 @@ function extractContentTypes(yamlContent) {
|
|
|
4778
5183
|
return types.length > 0 ? types : ["unknown"];
|
|
4779
5184
|
}
|
|
4780
5185
|
function readPageRegistry(cwd) {
|
|
4781
|
-
const regPath =
|
|
4782
|
-
if (!
|
|
5186
|
+
const regPath = join8(cwd, PAGE_REGISTRY_FILE);
|
|
5187
|
+
if (!existsSync8(regPath)) return {};
|
|
4783
5188
|
try {
|
|
4784
5189
|
const stat = lstatSync7(regPath);
|
|
4785
5190
|
if (stat.isSymbolicLink()) return {};
|
|
4786
|
-
return JSON.parse(
|
|
5191
|
+
return JSON.parse(readFileSync8(regPath, "utf-8"));
|
|
4787
5192
|
} catch {
|
|
4788
5193
|
return {};
|
|
4789
5194
|
}
|
|
4790
5195
|
}
|
|
4791
5196
|
function writePageRegistry(cwd, registry) {
|
|
4792
|
-
const dir =
|
|
5197
|
+
const dir = join8(cwd, ".stackwright");
|
|
4793
5198
|
mkdirSync5(dir, { recursive: true });
|
|
4794
|
-
const regPath =
|
|
4795
|
-
if (
|
|
5199
|
+
const regPath = join8(cwd, PAGE_REGISTRY_FILE);
|
|
5200
|
+
if (existsSync8(regPath)) {
|
|
4796
5201
|
const stat = lstatSync7(regPath);
|
|
4797
5202
|
if (stat.isSymbolicLink()) {
|
|
4798
5203
|
throw new Error("Refusing to write page registry through symlink");
|
|
@@ -4964,8 +5369,8 @@ function _safeWriteInner(input) {
|
|
|
4964
5369
|
return { text: JSON.stringify(result), isError: true };
|
|
4965
5370
|
}
|
|
4966
5371
|
const normalized = normalize(filePath);
|
|
4967
|
-
const fullPath =
|
|
4968
|
-
if (
|
|
5372
|
+
const fullPath = join8(cwd, normalized);
|
|
5373
|
+
if (existsSync8(fullPath)) {
|
|
4969
5374
|
try {
|
|
4970
5375
|
const stat = lstatSync7(fullPath);
|
|
4971
5376
|
if (stat.isSymbolicLink()) {
|
|
@@ -5095,8 +5500,8 @@ function registerSafeWriteTools(server2) {
|
|
|
5095
5500
|
|
|
5096
5501
|
// src/tools/auth.ts
|
|
5097
5502
|
import { z as z15 } from "zod";
|
|
5098
|
-
import { readFileSync as
|
|
5099
|
-
import { join as
|
|
5503
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
5504
|
+
import { join as join9 } from "path";
|
|
5100
5505
|
function buildHierarchy(roles) {
|
|
5101
5506
|
const h = {};
|
|
5102
5507
|
for (let i = 0; i < roles.length - 1; i++) {
|
|
@@ -5123,9 +5528,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
|
|
|
5123
5528
|
}
|
|
5124
5529
|
function detectNextMajorVersion(cwd) {
|
|
5125
5530
|
try {
|
|
5126
|
-
const pkgPath =
|
|
5127
|
-
if (!
|
|
5128
|
-
const pkg = JSON.parse(
|
|
5531
|
+
const pkgPath = join9(cwd, "package.json");
|
|
5532
|
+
if (!existsSync9(pkgPath)) return null;
|
|
5533
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
5129
5534
|
const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
5130
5535
|
if (!nextVersion) return null;
|
|
5131
5536
|
const match = nextVersion.match(/(\d+)/);
|
|
@@ -5789,7 +6194,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5789
6194
|
auditRetentionDays,
|
|
5790
6195
|
normalizedRoutes
|
|
5791
6196
|
);
|
|
5792
|
-
writeFileSync6(
|
|
6197
|
+
writeFileSync6(join9(cwd, conventionFile), authFileContent, "utf8");
|
|
5793
6198
|
filesWritten.push(conventionFile);
|
|
5794
6199
|
} catch (err) {
|
|
5795
6200
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5809,9 +6214,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5809
6214
|
if (!devOnly) {
|
|
5810
6215
|
try {
|
|
5811
6216
|
const envBlock = generateEnvBlock(effectiveMethod, params);
|
|
5812
|
-
const envPath =
|
|
5813
|
-
if (
|
|
5814
|
-
const existing =
|
|
6217
|
+
const envPath = join9(cwd, ".env.example");
|
|
6218
|
+
if (existsSync9(envPath)) {
|
|
6219
|
+
const existing = readFileSync9(envPath, "utf8");
|
|
5815
6220
|
writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
5816
6221
|
} else {
|
|
5817
6222
|
writeFileSync6(envPath, envBlock, "utf8");
|
|
@@ -5832,10 +6237,10 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5832
6237
|
}
|
|
5833
6238
|
if (devOnly) {
|
|
5834
6239
|
try {
|
|
5835
|
-
const mockAuthDir =
|
|
6240
|
+
const mockAuthDir = join9(cwd, "lib");
|
|
5836
6241
|
mkdirSync6(mockAuthDir, { recursive: true });
|
|
5837
6242
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
5838
|
-
writeFileSync6(
|
|
6243
|
+
writeFileSync6(join9(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
5839
6244
|
filesWritten.push("lib/mock-auth.ts");
|
|
5840
6245
|
} catch (err) {
|
|
5841
6246
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5853,9 +6258,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5853
6258
|
};
|
|
5854
6259
|
}
|
|
5855
6260
|
try {
|
|
5856
|
-
const pkgPath =
|
|
5857
|
-
if (
|
|
5858
|
-
const existingPkg =
|
|
6261
|
+
const pkgPath = join9(cwd, "package.json");
|
|
6262
|
+
if (existsSync9(pkgPath)) {
|
|
6263
|
+
const existingPkg = readFileSync9(pkgPath, "utf8");
|
|
5859
6264
|
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
5860
6265
|
writeFileSync6(pkgPath, updatedPkg, "utf8");
|
|
5861
6266
|
filesWritten.push("package.json");
|
|
@@ -5898,7 +6303,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5898
6303
|
normalizedRoutes,
|
|
5899
6304
|
useProxy
|
|
5900
6305
|
);
|
|
5901
|
-
writeFileSync6(
|
|
6306
|
+
writeFileSync6(join9(cwd, "stackwright.auth.yml"), authYaml, "utf8");
|
|
5902
6307
|
filesWritten.push("stackwright.auth.yml");
|
|
5903
6308
|
} catch (err) {
|
|
5904
6309
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6011,8 +6416,8 @@ function registerAuthTools(server2) {
|
|
|
6011
6416
|
|
|
6012
6417
|
// src/integrity.ts
|
|
6013
6418
|
import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
6014
|
-
import { readFileSync as
|
|
6015
|
-
import { join as
|
|
6419
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync6, lstatSync as lstatSync8 } from "fs";
|
|
6420
|
+
import { join as join10, basename } from "path";
|
|
6016
6421
|
var _checksums = /* @__PURE__ */ new Map([
|
|
6017
6422
|
[
|
|
6018
6423
|
"stackwright-pro-api-otter.json",
|
|
@@ -6020,11 +6425,11 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6020
6425
|
],
|
|
6021
6426
|
[
|
|
6022
6427
|
"stackwright-pro-auth-otter.json",
|
|
6023
|
-
"
|
|
6428
|
+
"69fa03c5532ef617c86fe7af41e8174c168d282baf602b1b269a7e933ea86b7c"
|
|
6024
6429
|
],
|
|
6025
6430
|
[
|
|
6026
6431
|
"stackwright-pro-dashboard-otter.json",
|
|
6027
|
-
"
|
|
6432
|
+
"f55081bf4d6545e5435128919f6f9233956ba37671fc9f85300f240ad2497ab6"
|
|
6028
6433
|
],
|
|
6029
6434
|
[
|
|
6030
6435
|
"stackwright-pro-data-otter.json",
|
|
@@ -6040,19 +6445,19 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6040
6445
|
],
|
|
6041
6446
|
[
|
|
6042
6447
|
"stackwright-pro-foreman-otter.json",
|
|
6043
|
-
"
|
|
6448
|
+
"88c4101154d44a7c5470ded13562402a39323ee570ed3a90a73b8d85d7fd2817"
|
|
6044
6449
|
],
|
|
6045
6450
|
[
|
|
6046
6451
|
"stackwright-pro-form-wizard-otter.json",
|
|
6047
|
-
"
|
|
6452
|
+
"cb350297a0068ead2424e703e8c775d91c0b87249ea6904f43b903baf8a84b79"
|
|
6048
6453
|
],
|
|
6049
6454
|
[
|
|
6050
6455
|
"stackwright-pro-geo-otter.json",
|
|
6051
|
-
"
|
|
6456
|
+
"3032fbd27de36c0cdc0e19e46a32d771208d8e86714482f5a368d46342c8317a"
|
|
6052
6457
|
],
|
|
6053
6458
|
[
|
|
6054
6459
|
"stackwright-pro-page-otter.json",
|
|
6055
|
-
"
|
|
6460
|
+
"cc0db24b00f0130f8b4ec8d385c73ea1bcf5d57ba45aca6b986bcbd5da328930"
|
|
6056
6461
|
],
|
|
6057
6462
|
[
|
|
6058
6463
|
"stackwright-pro-polish-otter.json",
|
|
@@ -6064,15 +6469,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6064
6469
|
],
|
|
6065
6470
|
[
|
|
6066
6471
|
"stackwright-pro-scaffold-otter.json",
|
|
6067
|
-
"
|
|
6472
|
+
"90272bab51bd8ed9e25c0fb6481cdd4252386850f64192edfe1e2eff44166e2d"
|
|
6068
6473
|
],
|
|
6069
6474
|
[
|
|
6070
6475
|
"stackwright-pro-theme-otter.json",
|
|
6071
|
-
"
|
|
6476
|
+
"afb47f8381907145c0e098bdcaae4dd9f5c4ff825a8e88d00a218d2f6858820b"
|
|
6072
6477
|
],
|
|
6073
6478
|
[
|
|
6074
6479
|
"stackwright-services-otter.json",
|
|
6075
|
-
"
|
|
6480
|
+
"4351348806aeb98404a2bacb044209ca411be5e35e2f2c3d03eb4d28a07fde78"
|
|
6076
6481
|
]
|
|
6077
6482
|
]);
|
|
6078
6483
|
Object.freeze(_checksums);
|
|
@@ -6120,7 +6525,7 @@ function verifyOtterFile(filePath) {
|
|
|
6120
6525
|
}
|
|
6121
6526
|
let raw;
|
|
6122
6527
|
try {
|
|
6123
|
-
raw =
|
|
6528
|
+
raw = readFileSync10(filePath);
|
|
6124
6529
|
} catch (err) {
|
|
6125
6530
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6126
6531
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -6170,7 +6575,7 @@ function verifyAllOtters(otterDir) {
|
|
|
6170
6575
|
const unknown = [];
|
|
6171
6576
|
let entries;
|
|
6172
6577
|
try {
|
|
6173
|
-
entries =
|
|
6578
|
+
entries = readdirSync6(otterDir);
|
|
6174
6579
|
} catch (err) {
|
|
6175
6580
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6176
6581
|
return {
|
|
@@ -6181,7 +6586,7 @@ function verifyAllOtters(otterDir) {
|
|
|
6181
6586
|
}
|
|
6182
6587
|
const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
|
|
6183
6588
|
for (const filename of otterFiles) {
|
|
6184
|
-
const filePath =
|
|
6589
|
+
const filePath = join10(otterDir, filename);
|
|
6185
6590
|
try {
|
|
6186
6591
|
if (lstatSync8(filePath).isSymbolicLink()) {
|
|
6187
6592
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
@@ -6209,7 +6614,7 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
|
|
|
6209
6614
|
function resolveOtterDir() {
|
|
6210
6615
|
const cwd = process.cwd();
|
|
6211
6616
|
for (const relative2 of DEFAULT_SEARCH_PATHS) {
|
|
6212
|
-
const candidate =
|
|
6617
|
+
const candidate = join10(cwd, relative2);
|
|
6213
6618
|
try {
|
|
6214
6619
|
lstatSync8(candidate);
|
|
6215
6620
|
return candidate;
|
|
@@ -6290,13 +6695,13 @@ function registerIntegrityTools(server2) {
|
|
|
6290
6695
|
|
|
6291
6696
|
// src/tools/domain.ts
|
|
6292
6697
|
import { z as z16 } from "zod";
|
|
6293
|
-
import { readFileSync as
|
|
6294
|
-
import { join as
|
|
6698
|
+
import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
6699
|
+
import { join as join11 } from "path";
|
|
6295
6700
|
function handleListCollections(input) {
|
|
6296
6701
|
const cwd = input._cwd ?? process.cwd();
|
|
6297
6702
|
const sources = [
|
|
6298
6703
|
{
|
|
6299
|
-
path:
|
|
6704
|
+
path: join11(cwd, ".stackwright", "artifacts", "data-config.json"),
|
|
6300
6705
|
source: "data-config.json",
|
|
6301
6706
|
parse: (raw) => {
|
|
6302
6707
|
const parsed = JSON.parse(raw);
|
|
@@ -6307,15 +6712,15 @@ function handleListCollections(input) {
|
|
|
6307
6712
|
}
|
|
6308
6713
|
},
|
|
6309
6714
|
{
|
|
6310
|
-
path:
|
|
6715
|
+
path: join11(cwd, "stackwright.yml"),
|
|
6311
6716
|
source: "stackwright.yml",
|
|
6312
6717
|
parse: extractCollectionsFromYaml
|
|
6313
6718
|
}
|
|
6314
6719
|
];
|
|
6315
6720
|
for (const { path: path5, source, parse } of sources) {
|
|
6316
|
-
if (!
|
|
6721
|
+
if (!existsSync10(path5)) continue;
|
|
6317
6722
|
try {
|
|
6318
|
-
const collections = parse(
|
|
6723
|
+
const collections = parse(readFileSync11(path5, "utf8"));
|
|
6319
6724
|
return {
|
|
6320
6725
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
6321
6726
|
isError: false
|
|
@@ -6466,8 +6871,8 @@ function handleValidateWorkflow(input) {
|
|
|
6466
6871
|
if (input.workflow && Object.keys(input.workflow).length > 0) {
|
|
6467
6872
|
raw = input.workflow;
|
|
6468
6873
|
} else {
|
|
6469
|
-
const artifactPath =
|
|
6470
|
-
if (!
|
|
6874
|
+
const artifactPath = join11(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
6875
|
+
if (!existsSync10(artifactPath)) {
|
|
6471
6876
|
return fail([
|
|
6472
6877
|
{
|
|
6473
6878
|
code: "NO_WORKFLOW",
|
|
@@ -6476,7 +6881,7 @@ function handleValidateWorkflow(input) {
|
|
|
6476
6881
|
]);
|
|
6477
6882
|
}
|
|
6478
6883
|
try {
|
|
6479
|
-
raw = JSON.parse(
|
|
6884
|
+
raw = JSON.parse(readFileSync11(artifactPath, "utf8"));
|
|
6480
6885
|
} catch (err) {
|
|
6481
6886
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
6482
6887
|
}
|
|
@@ -6914,16 +7319,16 @@ function registerProPageTools(server2) {
|
|
|
6914
7319
|
|
|
6915
7320
|
// src/tools/scaffold-cleanup.ts
|
|
6916
7321
|
import {
|
|
6917
|
-
existsSync as
|
|
7322
|
+
existsSync as existsSync12,
|
|
6918
7323
|
lstatSync as lstatSync9,
|
|
6919
7324
|
unlinkSync as unlinkSync2,
|
|
6920
|
-
readFileSync as
|
|
7325
|
+
readFileSync as readFileSync12,
|
|
6921
7326
|
writeFileSync as writeFileSync8,
|
|
6922
|
-
readdirSync as
|
|
7327
|
+
readdirSync as readdirSync7,
|
|
6923
7328
|
rmdirSync,
|
|
6924
7329
|
mkdirSync as mkdirSync8
|
|
6925
7330
|
} from "fs";
|
|
6926
|
-
import { join as
|
|
7331
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
6927
7332
|
var SCAFFOLD_FILES_TO_DELETE = [
|
|
6928
7333
|
"content/posts/getting-started.yaml",
|
|
6929
7334
|
"content/posts/hello-world.yaml"
|
|
@@ -6948,12 +7353,12 @@ var FALLBACK_COLORS = {
|
|
|
6948
7353
|
mutedForeground: "#737373"
|
|
6949
7354
|
};
|
|
6950
7355
|
function readThemeColors(cwd) {
|
|
6951
|
-
const tokenPath =
|
|
6952
|
-
if (!
|
|
7356
|
+
const tokenPath = join13(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
7357
|
+
if (!existsSync12(tokenPath)) return { ...FALLBACK_COLORS };
|
|
6953
7358
|
const stat = lstatSync9(tokenPath);
|
|
6954
7359
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
6955
7360
|
try {
|
|
6956
|
-
const raw = JSON.parse(
|
|
7361
|
+
const raw = JSON.parse(readFileSync12(tokenPath, "utf-8"));
|
|
6957
7362
|
const css = raw?.cssVariables ?? {};
|
|
6958
7363
|
const toHsl = (hslValue) => {
|
|
6959
7364
|
if (!hslValue || typeof hslValue !== "string") return null;
|
|
@@ -7013,15 +7418,15 @@ function generateNotFoundPage(colors) {
|
|
|
7013
7418
|
`;
|
|
7014
7419
|
}
|
|
7015
7420
|
function isSafePath(fullPath) {
|
|
7016
|
-
if (!
|
|
7421
|
+
if (!existsSync12(fullPath)) return true;
|
|
7017
7422
|
const stat = lstatSync9(fullPath);
|
|
7018
7423
|
return !stat.isSymbolicLink();
|
|
7019
7424
|
}
|
|
7020
7425
|
function isDirEmpty(dirPath) {
|
|
7021
|
-
if (!
|
|
7426
|
+
if (!existsSync12(dirPath)) return false;
|
|
7022
7427
|
const stat = lstatSync9(dirPath);
|
|
7023
7428
|
if (!stat.isDirectory()) return false;
|
|
7024
|
-
return
|
|
7429
|
+
return readdirSync7(dirPath).length === 0;
|
|
7025
7430
|
}
|
|
7026
7431
|
function handleCleanupScaffold(_cwd) {
|
|
7027
7432
|
const cwd = _cwd ?? process.cwd();
|
|
@@ -7034,8 +7439,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7034
7439
|
cleanupWarnings: []
|
|
7035
7440
|
};
|
|
7036
7441
|
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
7037
|
-
const fullPath =
|
|
7038
|
-
if (!
|
|
7442
|
+
const fullPath = join13(cwd, relPath);
|
|
7443
|
+
if (!existsSync12(fullPath)) {
|
|
7039
7444
|
result.skipped.push(relPath);
|
|
7040
7445
|
continue;
|
|
7041
7446
|
}
|
|
@@ -7052,13 +7457,13 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7052
7457
|
}
|
|
7053
7458
|
{
|
|
7054
7459
|
const relPath = "pages/getting-started/content.yml";
|
|
7055
|
-
const fullPath =
|
|
7056
|
-
if (!
|
|
7460
|
+
const fullPath = join13(cwd, relPath);
|
|
7461
|
+
if (!existsSync12(fullPath)) {
|
|
7057
7462
|
result.skipped.push(relPath);
|
|
7058
7463
|
} else if (!isSafePath(fullPath)) {
|
|
7059
7464
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
7060
7465
|
} else {
|
|
7061
|
-
const content =
|
|
7466
|
+
const content = readFileSync12(fullPath, "utf-8");
|
|
7062
7467
|
const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
|
|
7063
7468
|
(marker) => content.includes(marker)
|
|
7064
7469
|
);
|
|
@@ -7078,16 +7483,16 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7078
7483
|
}
|
|
7079
7484
|
{
|
|
7080
7485
|
const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
|
|
7081
|
-
const fullPath =
|
|
7082
|
-
const postsDir =
|
|
7083
|
-
if (!
|
|
7486
|
+
const fullPath = join13(cwd, relPath);
|
|
7487
|
+
const postsDir = join13(cwd, "content/posts");
|
|
7488
|
+
if (!existsSync12(fullPath)) {
|
|
7084
7489
|
result.skipped.push(relPath);
|
|
7085
7490
|
} else if (!isSafePath(fullPath)) {
|
|
7086
7491
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
7087
|
-
} else if (!
|
|
7492
|
+
} else if (!existsSync12(postsDir)) {
|
|
7088
7493
|
result.skipped.push(relPath);
|
|
7089
7494
|
} else {
|
|
7090
|
-
const userPostFiles =
|
|
7495
|
+
const userPostFiles = readdirSync7(postsDir).filter(
|
|
7091
7496
|
(f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
7092
7497
|
);
|
|
7093
7498
|
if (userPostFiles.length === 0) {
|
|
@@ -7105,8 +7510,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7105
7510
|
}
|
|
7106
7511
|
}
|
|
7107
7512
|
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
7108
|
-
const fullDir =
|
|
7109
|
-
if (!
|
|
7513
|
+
const fullDir = join13(cwd, relDir);
|
|
7514
|
+
if (!existsSync12(fullDir)) continue;
|
|
7110
7515
|
if (!isSafePath(fullDir)) {
|
|
7111
7516
|
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
7112
7517
|
continue;
|
|
@@ -7121,8 +7526,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7121
7526
|
}
|
|
7122
7527
|
}
|
|
7123
7528
|
{
|
|
7124
|
-
const fullPath =
|
|
7125
|
-
if (!
|
|
7529
|
+
const fullPath = join13(cwd, NOT_FOUND_PATH);
|
|
7530
|
+
if (!existsSync12(fullPath)) {
|
|
7126
7531
|
result.skipped.push(NOT_FOUND_PATH);
|
|
7127
7532
|
} else if (!isSafePath(fullPath)) {
|
|
7128
7533
|
result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
|
|
@@ -7643,14 +8048,14 @@ ${results.join("\n")}`;
|
|
|
7643
8048
|
|
|
7644
8049
|
// src/tools/strip-legacy-integrations.ts
|
|
7645
8050
|
import { z as z21 } from "zod";
|
|
7646
|
-
import { existsSync as
|
|
7647
|
-
import { join as
|
|
8051
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
8052
|
+
import { join as join14 } from "path";
|
|
7648
8053
|
function handleStripLegacyIntegrations(input) {
|
|
7649
8054
|
const { projectRoot } = input;
|
|
7650
|
-
const ymlPath =
|
|
7651
|
-
const yamlPath =
|
|
7652
|
-
if (!
|
|
7653
|
-
if (
|
|
8055
|
+
const ymlPath = join14(projectRoot, "stackwright.yml");
|
|
8056
|
+
const yamlPath = join14(projectRoot, "stackwright.yaml");
|
|
8057
|
+
if (!existsSync13(ymlPath)) {
|
|
8058
|
+
if (existsSync13(yamlPath)) {
|
|
7654
8059
|
return {
|
|
7655
8060
|
changed: false,
|
|
7656
8061
|
removedEntries: 0,
|
|
@@ -7663,7 +8068,7 @@ function handleStripLegacyIntegrations(input) {
|
|
|
7663
8068
|
message: "stackwright.yml not found \u2014 no-op."
|
|
7664
8069
|
};
|
|
7665
8070
|
}
|
|
7666
|
-
const raw =
|
|
8071
|
+
const raw = readFileSync13(ymlPath, "utf-8");
|
|
7667
8072
|
const isCRLF = raw.includes("\r\n");
|
|
7668
8073
|
const content = isCRLF ? raw.replace(/\r\n/g, "\n") : raw;
|
|
7669
8074
|
const lines = content.split("\n");
|
|
@@ -7718,15 +8123,15 @@ function registerStripLegacyIntegrationsTool(server2) {
|
|
|
7718
8123
|
|
|
7719
8124
|
// src/tools/emitters.ts
|
|
7720
8125
|
import { z as z22 } from "zod";
|
|
7721
|
-
import { readFileSync as
|
|
7722
|
-
import { join as
|
|
8126
|
+
import { readFileSync as readFileSync14, existsSync as existsSync14, readdirSync as readdirSync8 } from "fs";
|
|
8127
|
+
import { join as join15 } from "path";
|
|
7723
8128
|
import yaml from "js-yaml";
|
|
7724
8129
|
import { emitEnvLocal, emitProvidersFile, emitMockPorts } from "@stackwright-pro/emitters";
|
|
7725
8130
|
function readIntegrations(cwd) {
|
|
7726
|
-
const filePath =
|
|
7727
|
-
if (!
|
|
8131
|
+
const filePath = join15(cwd, "stackwright.integrations.yml");
|
|
8132
|
+
if (!existsSync14(filePath)) return [];
|
|
7728
8133
|
try {
|
|
7729
|
-
const raw = yaml.load(
|
|
8134
|
+
const raw = yaml.load(readFileSync14(filePath, "utf-8"));
|
|
7730
8135
|
if (!raw?.integrations || !Array.isArray(raw.integrations)) return [];
|
|
7731
8136
|
return raw.integrations.filter((i) => typeof i.name === "string").map((i) => ({
|
|
7732
8137
|
name: i.name,
|
|
@@ -7737,15 +8142,15 @@ function readIntegrations(cwd) {
|
|
|
7737
8142
|
}
|
|
7738
8143
|
}
|
|
7739
8144
|
function readServiceTokenRefs(cwd) {
|
|
7740
|
-
const servicesDir =
|
|
7741
|
-
if (!
|
|
8145
|
+
const servicesDir = join15(cwd, "services");
|
|
8146
|
+
if (!existsSync14(servicesDir)) return [];
|
|
7742
8147
|
try {
|
|
7743
|
-
const files =
|
|
8148
|
+
const files = readdirSync8(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
7744
8149
|
const refs = [];
|
|
7745
8150
|
for (const file of files) {
|
|
7746
8151
|
try {
|
|
7747
8152
|
const raw = yaml.load(
|
|
7748
|
-
|
|
8153
|
+
readFileSync14(join15(servicesDir, file), "utf-8")
|
|
7749
8154
|
);
|
|
7750
8155
|
if (raw?.tokenEnv && typeof raw.tokenEnv === "string") {
|
|
7751
8156
|
refs.push({
|
|
@@ -7895,8 +8300,8 @@ This is the third concrete instance of the swp-zf9y deterministic emitter patter
|
|
|
7895
8300
|
|
|
7896
8301
|
// src/tools/list-specs.ts
|
|
7897
8302
|
import { z as z23 } from "zod";
|
|
7898
|
-
import { readdirSync as
|
|
7899
|
-
import { join as
|
|
8303
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync15 } from "fs";
|
|
8304
|
+
import { join as join16, extname, basename as basename2 } from "path";
|
|
7900
8305
|
var STRIP_SUFFIXES = ["-openapi", "-asyncapi", "-api", "-spec"];
|
|
7901
8306
|
var ALLOWED_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
|
|
7902
8307
|
function deriveIntegrationName(fileName) {
|
|
@@ -7918,18 +8323,18 @@ function detectFormat(snippet) {
|
|
|
7918
8323
|
}
|
|
7919
8324
|
function handleListSpecs(input) {
|
|
7920
8325
|
const { projectRoot } = input;
|
|
7921
|
-
const specsDir =
|
|
8326
|
+
const specsDir = join16(projectRoot, "specs");
|
|
7922
8327
|
const empty = { projectRoot, specs: [], totalCount: 0 };
|
|
7923
|
-
if (!
|
|
8328
|
+
if (!existsSync15(specsDir)) return empty;
|
|
7924
8329
|
let entries;
|
|
7925
8330
|
try {
|
|
7926
|
-
entries =
|
|
8331
|
+
entries = readdirSync9(specsDir);
|
|
7927
8332
|
} catch {
|
|
7928
8333
|
return empty;
|
|
7929
8334
|
}
|
|
7930
8335
|
const specs = [];
|
|
7931
8336
|
for (const entry of entries) {
|
|
7932
|
-
const fullPath =
|
|
8337
|
+
const fullPath = join16(specsDir, entry);
|
|
7933
8338
|
let stat;
|
|
7934
8339
|
try {
|
|
7935
8340
|
stat = statSync2(fullPath);
|
|
@@ -7940,7 +8345,7 @@ function handleListSpecs(input) {
|
|
|
7940
8345
|
if (!ALLOWED_EXTS.has(extname(entry).toLowerCase())) continue;
|
|
7941
8346
|
let snippet = "";
|
|
7942
8347
|
try {
|
|
7943
|
-
snippet =
|
|
8348
|
+
snippet = readFileSync15(fullPath, "utf-8").slice(0, 2048);
|
|
7944
8349
|
} catch {
|
|
7945
8350
|
}
|
|
7946
8351
|
specs.push({
|
|
@@ -7972,9 +8377,9 @@ function registerListSpecsTool(server2) {
|
|
|
7972
8377
|
|
|
7973
8378
|
// src/tools/consolidate-integrations.ts
|
|
7974
8379
|
import { z as z24 } from "zod";
|
|
7975
|
-
import { readdirSync as
|
|
8380
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
|
|
7976
8381
|
import { lockSync as lockSync2 } from "proper-lockfile";
|
|
7977
|
-
import { join as
|
|
8382
|
+
import { join as join17, basename as basename3 } from "path";
|
|
7978
8383
|
import yaml2 from "js-yaml";
|
|
7979
8384
|
var BASE_MOCK_PORT = 4011;
|
|
7980
8385
|
var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
|
|
@@ -7987,13 +8392,13 @@ function deriveNameFromFilename(filename) {
|
|
|
7987
8392
|
}
|
|
7988
8393
|
function handleConsolidateIntegrations(input) {
|
|
7989
8394
|
const { projectRoot } = input;
|
|
7990
|
-
const artifactsDir =
|
|
7991
|
-
if (!
|
|
8395
|
+
const artifactsDir = join17(projectRoot, ".stackwright", "artifacts");
|
|
8396
|
+
if (!existsSync16(artifactsDir)) {
|
|
7992
8397
|
return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
|
|
7993
8398
|
}
|
|
7994
8399
|
let entries;
|
|
7995
8400
|
try {
|
|
7996
|
-
entries =
|
|
8401
|
+
entries = readdirSync10(artifactsDir).filter(
|
|
7997
8402
|
(f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
|
|
7998
8403
|
);
|
|
7999
8404
|
} catch {
|
|
@@ -8004,14 +8409,22 @@ function handleConsolidateIntegrations(input) {
|
|
|
8004
8409
|
}
|
|
8005
8410
|
entries.sort();
|
|
8006
8411
|
const integrationsByName = /* @__PURE__ */ new Map();
|
|
8412
|
+
const skippedIntegrationsList = [];
|
|
8007
8413
|
entries.forEach((filename, index) => {
|
|
8008
8414
|
let artifact = {};
|
|
8009
8415
|
try {
|
|
8010
|
-
const raw =
|
|
8416
|
+
const raw = readFileSync16(join17(artifactsDir, filename), "utf-8");
|
|
8011
8417
|
artifact = JSON.parse(raw);
|
|
8012
8418
|
} catch {
|
|
8013
8419
|
}
|
|
8014
8420
|
const name = artifact.integrationName ?? deriveNameFromFilename(filename);
|
|
8421
|
+
if (artifact.skipped && artifact.skipped.length > 0) {
|
|
8422
|
+
skippedIntegrationsList.push({
|
|
8423
|
+
name,
|
|
8424
|
+
reason: artifact.skipped[0]?.reason ?? "explicitly skipped by api-otter"
|
|
8425
|
+
});
|
|
8426
|
+
return;
|
|
8427
|
+
}
|
|
8015
8428
|
const spec = artifact.integrationSpec ?? artifact.specPath ?? `./specs/${name}.yaml`;
|
|
8016
8429
|
const mockUrl = artifact.mockUrl ?? `http://localhost:${BASE_MOCK_PORT + index}`;
|
|
8017
8430
|
const baseUrl = artifact.baseUrl ?? "";
|
|
@@ -8032,20 +8445,20 @@ function handleConsolidateIntegrations(input) {
|
|
|
8032
8445
|
integrationsByName.set(name, entry);
|
|
8033
8446
|
});
|
|
8034
8447
|
const integrations = Array.from(integrationsByName.values());
|
|
8035
|
-
const dedupedCount = entries.length - integrations.length;
|
|
8036
|
-
const integrationsYmlPath =
|
|
8448
|
+
const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
|
|
8449
|
+
const integrationsYmlPath = join17(projectRoot, "stackwright.integrations.yml");
|
|
8037
8450
|
let existing = {};
|
|
8038
|
-
if (
|
|
8451
|
+
if (existsSync16(integrationsYmlPath)) {
|
|
8039
8452
|
try {
|
|
8040
|
-
const raw =
|
|
8453
|
+
const raw = readFileSync16(integrationsYmlPath, "utf-8");
|
|
8041
8454
|
existing = yaml2.load(raw) ?? {};
|
|
8042
8455
|
} catch {
|
|
8043
8456
|
existing = {};
|
|
8044
8457
|
}
|
|
8045
8458
|
}
|
|
8046
8459
|
const merged = { ...existing, integrations };
|
|
8047
|
-
mkdirSync9(
|
|
8048
|
-
if (!
|
|
8460
|
+
mkdirSync9(join17(projectRoot), { recursive: true });
|
|
8461
|
+
if (!existsSync16(integrationsYmlPath)) {
|
|
8049
8462
|
writeFileSync10(integrationsYmlPath, "", "utf-8");
|
|
8050
8463
|
}
|
|
8051
8464
|
const release = lockSync2(integrationsYmlPath, {
|
|
@@ -8064,7 +8477,11 @@ function handleConsolidateIntegrations(input) {
|
|
|
8064
8477
|
written: true,
|
|
8065
8478
|
integrationCount: integrations.length,
|
|
8066
8479
|
path: "stackwright.integrations.yml",
|
|
8067
|
-
...dedupedCount > 0 ? { dedupedCount } : {}
|
|
8480
|
+
...dedupedCount > 0 ? { dedupedCount } : {},
|
|
8481
|
+
...skippedIntegrationsList.length > 0 ? {
|
|
8482
|
+
skippedCount: skippedIntegrationsList.length,
|
|
8483
|
+
skippedIntegrations: skippedIntegrationsList
|
|
8484
|
+
} : {}
|
|
8068
8485
|
};
|
|
8069
8486
|
}
|
|
8070
8487
|
function registerConsolidateIntegrationsTool(server2) {
|
|
@@ -8138,7 +8555,7 @@ var package_default = {
|
|
|
8138
8555
|
"test:coverage": "vitest run --coverage"
|
|
8139
8556
|
},
|
|
8140
8557
|
name: "@stackwright-pro/mcp",
|
|
8141
|
-
version: "0.2.0-alpha.
|
|
8558
|
+
version: "0.2.0-alpha.109",
|
|
8142
8559
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
8143
8560
|
license: "SEE LICENSE IN LICENSE",
|
|
8144
8561
|
main: "./dist/server.js",
|