@stackwright-pro/mcp 0.2.0-alpha.106 → 0.2.0-alpha.108
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 +619 -227
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +566 -174
- package/dist/server.mjs.map +1 -1
- package/package.json +7 -7
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,6 +4372,61 @@ 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 = {
|
|
4144
4431
|
workflow: (artifact2) => {
|
|
4145
4432
|
const workflowData = artifact2["workflow"];
|
|
@@ -4190,33 +4477,60 @@ function _validateArtifactInner(input) {
|
|
|
4190
4477
|
return { success: true };
|
|
4191
4478
|
},
|
|
4192
4479
|
// swp-4071 — opt-in cross-reference validator for the services phase.
|
|
4193
|
-
//
|
|
4194
|
-
//
|
|
4480
|
+
// Collects serviceHooks from ALL workflow-config-*.json artifacts (swp-x8sm fan-out)
|
|
4481
|
+
// plus the legacy single-file workflow-config.json (backward compat).
|
|
4482
|
+
// If any workflow declared serviceHooks, every ref must resolve to a flow or
|
|
4483
|
+
// state-machine in this services-config artifact.
|
|
4195
4484
|
// Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
|
|
4196
4485
|
services: (artifact2) => {
|
|
4197
|
-
const
|
|
4198
|
-
|
|
4199
|
-
|
|
4486
|
+
const artifactsDir = join7(cwd, ".stackwright", "artifacts");
|
|
4487
|
+
const workflowArtifactFiles = [];
|
|
4488
|
+
if (existsSync7(artifactsDir)) {
|
|
4489
|
+
try {
|
|
4490
|
+
const entries = readdirSync5(artifactsDir);
|
|
4491
|
+
for (const entry of entries) {
|
|
4492
|
+
if (entry.startsWith("workflow-config-") && entry.endsWith(".json")) {
|
|
4493
|
+
workflowArtifactFiles.push(join7(artifactsDir, entry));
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
} catch {
|
|
4497
|
+
}
|
|
4200
4498
|
}
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
}
|
|
4499
|
+
const legacyPath = join7(artifactsDir, "workflow-config.json");
|
|
4500
|
+
if (existsSync7(legacyPath)) {
|
|
4501
|
+
workflowArtifactFiles.push(legacyPath);
|
|
4502
|
+
}
|
|
4503
|
+
if (workflowArtifactFiles.length === 0) {
|
|
4205
4504
|
return { success: true };
|
|
4206
4505
|
}
|
|
4207
|
-
const declaredHooks =
|
|
4208
|
-
|
|
4506
|
+
const declaredHooks = [];
|
|
4507
|
+
for (const filePath of workflowArtifactFiles) {
|
|
4508
|
+
try {
|
|
4509
|
+
const parsed = JSON.parse(readFileSync7(filePath, "utf-8"));
|
|
4510
|
+
if (Array.isArray(parsed.serviceHooks)) {
|
|
4511
|
+
declaredHooks.push(...parsed.serviceHooks);
|
|
4512
|
+
}
|
|
4513
|
+
} catch {
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
const seenRefs = /* @__PURE__ */ new Set();
|
|
4517
|
+
const uniqueHooks = declaredHooks.filter((h) => {
|
|
4518
|
+
if (seenRefs.has(h.ref)) return false;
|
|
4519
|
+
seenRefs.add(h.ref);
|
|
4520
|
+
return true;
|
|
4521
|
+
});
|
|
4522
|
+
if (uniqueHooks.length === 0) {
|
|
4209
4523
|
return { success: true };
|
|
4210
4524
|
}
|
|
4211
4525
|
const flows = artifact2["flows"] ?? [];
|
|
4212
4526
|
const workflows = artifact2["workflows"] ?? [];
|
|
4213
4527
|
const flowNames = /* @__PURE__ */ new Set([...flows.map((f) => f.name), ...workflows.map((w) => w.name)]);
|
|
4214
|
-
const unresolved =
|
|
4528
|
+
const unresolved = uniqueHooks.filter((h) => !flowNames.has(h.ref));
|
|
4215
4529
|
if (unresolved.length > 0) {
|
|
4216
4530
|
return {
|
|
4217
4531
|
success: false,
|
|
4218
4532
|
error: {
|
|
4219
|
-
message: `Workflow declared ${
|
|
4533
|
+
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
4534
|
}
|
|
4221
4535
|
};
|
|
4222
4536
|
}
|
|
@@ -4282,11 +4596,55 @@ function _validateArtifactInner(input) {
|
|
|
4282
4596
|
return { text: JSON.stringify(result), isError: false };
|
|
4283
4597
|
}
|
|
4284
4598
|
}
|
|
4599
|
+
if (phase === "api") {
|
|
4600
|
+
const apiAuthResult = validateApiIntegration(artifact, input.integrationName);
|
|
4601
|
+
if (!apiAuthResult.valid) {
|
|
4602
|
+
const result = {
|
|
4603
|
+
valid: false,
|
|
4604
|
+
phase,
|
|
4605
|
+
violation: "schema-mismatch",
|
|
4606
|
+
retryPrompt: apiAuthResult.retryPrompt
|
|
4607
|
+
};
|
|
4608
|
+
return { text: JSON.stringify(result), isError: false };
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
if (phase === "workflow" && input.workflowName !== void 0) {
|
|
4612
|
+
if (!isValidSlug(input.workflowName)) {
|
|
4613
|
+
const result = {
|
|
4614
|
+
valid: false,
|
|
4615
|
+
phase,
|
|
4616
|
+
violation: "schema-mismatch",
|
|
4617
|
+
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".`
|
|
4618
|
+
};
|
|
4619
|
+
return { text: JSON.stringify(result), isError: false };
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
let authOrphanWarning;
|
|
4623
|
+
if (phase === "auth") {
|
|
4624
|
+
const authCfg = artifact["authConfig"];
|
|
4625
|
+
const routes = authCfg?.["protectedRoutes"];
|
|
4626
|
+
if (Array.isArray(routes) && routes.length > 0) {
|
|
4627
|
+
const artifactsDir = join7(cwd, ".stackwright", "artifacts");
|
|
4628
|
+
const knownSlugs = extractManifestSlugs(artifactsDir);
|
|
4629
|
+
const orphanPatterns = [];
|
|
4630
|
+
for (const route of routes) {
|
|
4631
|
+
const pattern = typeof route === "string" ? route : route.pattern ?? "";
|
|
4632
|
+
if (!pattern) continue;
|
|
4633
|
+
const baseSlug = extractPatternBaseSlug(pattern);
|
|
4634
|
+
if (baseSlug && !knownSlugs.has(baseSlug)) {
|
|
4635
|
+
orphanPatterns.push(pattern);
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
if (orphanPatterns.length > 0) {
|
|
4639
|
+
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.`;
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4285
4643
|
try {
|
|
4286
|
-
const artifactsDir =
|
|
4644
|
+
const artifactsDir = join7(cwd, ".stackwright", "artifacts");
|
|
4287
4645
|
mkdirSync4(artifactsDir, { recursive: true });
|
|
4288
|
-
const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : PHASE_ARTIFACT[phase];
|
|
4289
|
-
const artifactPath =
|
|
4646
|
+
const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : phase === "workflow" && input.workflowName ? `workflow-config-${input.workflowName}.json` : PHASE_ARTIFACT[phase];
|
|
4647
|
+
const artifactPath = join7(artifactsDir, artifactFile);
|
|
4290
4648
|
const serialized = JSON.stringify(artifact, null, 2) + "\n";
|
|
4291
4649
|
const artifactBytes = Buffer.from(serialized, "utf-8");
|
|
4292
4650
|
safeWriteSync(artifactPath, serialized);
|
|
@@ -4309,7 +4667,8 @@ function _validateArtifactInner(input) {
|
|
|
4309
4667
|
valid: true,
|
|
4310
4668
|
phase,
|
|
4311
4669
|
artifactPath,
|
|
4312
|
-
summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}
|
|
4670
|
+
summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`,
|
|
4671
|
+
...authOrphanWarning ? { retryPrompt: authOrphanWarning } : {}
|
|
4313
4672
|
};
|
|
4314
4673
|
return { text: JSON.stringify(result), isError: false };
|
|
4315
4674
|
} catch (err) {
|
|
@@ -4505,9 +4864,9 @@ function registerPipelineTools(server2) {
|
|
|
4505
4864
|
isError: true
|
|
4506
4865
|
};
|
|
4507
4866
|
}
|
|
4508
|
-
const questionsDir =
|
|
4867
|
+
const questionsDir = join7(process.cwd(), ".stackwright", "questions");
|
|
4509
4868
|
mkdirSync4(questionsDir, { recursive: true });
|
|
4510
|
-
const outPath =
|
|
4869
|
+
const outPath = join7(questionsDir, `${phase}.json`);
|
|
4511
4870
|
writeFileSync4(
|
|
4512
4871
|
outPath,
|
|
4513
4872
|
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
@@ -4547,15 +4906,19 @@ function registerPipelineTools(server2) {
|
|
|
4547
4906
|
),
|
|
4548
4907
|
integrationName: z13.string().optional().describe(
|
|
4549
4908
|
'When phase="api": write to api-config-<integrationName>.json instead of api-config.json. Enables concurrent per-spec fan-out without collision (swp-530y).'
|
|
4909
|
+
),
|
|
4910
|
+
workflowName: z13.string().optional().describe(
|
|
4911
|
+
`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
4912
|
)
|
|
4551
4913
|
},
|
|
4552
|
-
async ({ phase, responseText, artifact, integrationName }) => {
|
|
4914
|
+
async ({ phase, responseText, artifact, integrationName, workflowName }) => {
|
|
4553
4915
|
if (artifact) {
|
|
4554
4916
|
return res(
|
|
4555
4917
|
handleValidateArtifact({
|
|
4556
4918
|
phase,
|
|
4557
4919
|
artifact,
|
|
4558
|
-
...integrationName ? { integrationName } : {}
|
|
4920
|
+
...integrationName ? { integrationName } : {},
|
|
4921
|
+
...workflowName ? { workflowName } : {}
|
|
4559
4922
|
})
|
|
4560
4923
|
);
|
|
4561
4924
|
}
|
|
@@ -4563,7 +4926,8 @@ function registerPipelineTools(server2) {
|
|
|
4563
4926
|
handleValidateArtifact({
|
|
4564
4927
|
phase,
|
|
4565
4928
|
responseText: responseText ?? "",
|
|
4566
|
-
...integrationName ? { integrationName } : {}
|
|
4929
|
+
...integrationName ? { integrationName } : {},
|
|
4930
|
+
...workflowName ? { workflowName } : {}
|
|
4567
4931
|
})
|
|
4568
4932
|
);
|
|
4569
4933
|
}
|
|
@@ -4595,8 +4959,8 @@ function registerPipelineTools(server2) {
|
|
|
4595
4959
|
|
|
4596
4960
|
// src/tools/safe-write.ts
|
|
4597
4961
|
import { z as z14 } from "zod";
|
|
4598
|
-
import { writeFileSync as writeFileSync5, readFileSync as
|
|
4599
|
-
import { normalize, isAbsolute, dirname, join as
|
|
4962
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
|
|
4963
|
+
import { normalize, isAbsolute, dirname, join as join8 } from "path";
|
|
4600
4964
|
import { emit as emit2 } from "@stackwright-pro/telemetry";
|
|
4601
4965
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
4602
4966
|
"stackwright-pro-designer-otter": [
|
|
@@ -4608,6 +4972,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
4608
4972
|
prefix: "stackwright.theme.",
|
|
4609
4973
|
suffix: ".yml",
|
|
4610
4974
|
description: "Theme config YAML (merged at build time)"
|
|
4975
|
+
},
|
|
4976
|
+
{
|
|
4977
|
+
prefix: "stackwright.yml",
|
|
4978
|
+
suffix: "",
|
|
4979
|
+
description: "Stackwright config (themeName + customTheme writeback)"
|
|
4611
4980
|
}
|
|
4612
4981
|
],
|
|
4613
4982
|
"stackwright-pro-auth-otter": [
|
|
@@ -4662,6 +5031,17 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
4662
5031
|
prefix: ".stackwright/artifacts/",
|
|
4663
5032
|
suffix: ".json",
|
|
4664
5033
|
description: "Scaffold manifest artifact"
|
|
5034
|
+
},
|
|
5035
|
+
// swp-89w5: scaffold-otter initialises mock backend wiring via stackwright_pro_emit_env_local
|
|
5036
|
+
{
|
|
5037
|
+
prefix: ".env.local",
|
|
5038
|
+
suffix: "",
|
|
5039
|
+
description: "Dotenv runtime config (mock backend wiring: NEXT_PUBLIC_MOCK_BACKEND + per-integration mock keys)"
|
|
5040
|
+
},
|
|
5041
|
+
{
|
|
5042
|
+
prefix: ".env.local.example",
|
|
5043
|
+
suffix: "",
|
|
5044
|
+
description: "Dotenv template (schema for real production values)"
|
|
4665
5045
|
}
|
|
4666
5046
|
],
|
|
4667
5047
|
"stackwright-pro-api-otter": [
|
|
@@ -4778,21 +5158,21 @@ function extractContentTypes(yamlContent) {
|
|
|
4778
5158
|
return types.length > 0 ? types : ["unknown"];
|
|
4779
5159
|
}
|
|
4780
5160
|
function readPageRegistry(cwd) {
|
|
4781
|
-
const regPath =
|
|
4782
|
-
if (!
|
|
5161
|
+
const regPath = join8(cwd, PAGE_REGISTRY_FILE);
|
|
5162
|
+
if (!existsSync8(regPath)) return {};
|
|
4783
5163
|
try {
|
|
4784
5164
|
const stat = lstatSync7(regPath);
|
|
4785
5165
|
if (stat.isSymbolicLink()) return {};
|
|
4786
|
-
return JSON.parse(
|
|
5166
|
+
return JSON.parse(readFileSync8(regPath, "utf-8"));
|
|
4787
5167
|
} catch {
|
|
4788
5168
|
return {};
|
|
4789
5169
|
}
|
|
4790
5170
|
}
|
|
4791
5171
|
function writePageRegistry(cwd, registry) {
|
|
4792
|
-
const dir =
|
|
5172
|
+
const dir = join8(cwd, ".stackwright");
|
|
4793
5173
|
mkdirSync5(dir, { recursive: true });
|
|
4794
|
-
const regPath =
|
|
4795
|
-
if (
|
|
5174
|
+
const regPath = join8(cwd, PAGE_REGISTRY_FILE);
|
|
5175
|
+
if (existsSync8(regPath)) {
|
|
4796
5176
|
const stat = lstatSync7(regPath);
|
|
4797
5177
|
if (stat.isSymbolicLink()) {
|
|
4798
5178
|
throw new Error("Refusing to write page registry through symlink");
|
|
@@ -4964,8 +5344,8 @@ function _safeWriteInner(input) {
|
|
|
4964
5344
|
return { text: JSON.stringify(result), isError: true };
|
|
4965
5345
|
}
|
|
4966
5346
|
const normalized = normalize(filePath);
|
|
4967
|
-
const fullPath =
|
|
4968
|
-
if (
|
|
5347
|
+
const fullPath = join8(cwd, normalized);
|
|
5348
|
+
if (existsSync8(fullPath)) {
|
|
4969
5349
|
try {
|
|
4970
5350
|
const stat = lstatSync7(fullPath);
|
|
4971
5351
|
if (stat.isSymbolicLink()) {
|
|
@@ -5095,8 +5475,8 @@ function registerSafeWriteTools(server2) {
|
|
|
5095
5475
|
|
|
5096
5476
|
// src/tools/auth.ts
|
|
5097
5477
|
import { z as z15 } from "zod";
|
|
5098
|
-
import { readFileSync as
|
|
5099
|
-
import { join as
|
|
5478
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
5479
|
+
import { join as join9 } from "path";
|
|
5100
5480
|
function buildHierarchy(roles) {
|
|
5101
5481
|
const h = {};
|
|
5102
5482
|
for (let i = 0; i < roles.length - 1; i++) {
|
|
@@ -5123,9 +5503,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
|
|
|
5123
5503
|
}
|
|
5124
5504
|
function detectNextMajorVersion(cwd) {
|
|
5125
5505
|
try {
|
|
5126
|
-
const pkgPath =
|
|
5127
|
-
if (!
|
|
5128
|
-
const pkg = JSON.parse(
|
|
5506
|
+
const pkgPath = join9(cwd, "package.json");
|
|
5507
|
+
if (!existsSync9(pkgPath)) return null;
|
|
5508
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
5129
5509
|
const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
5130
5510
|
if (!nextVersion) return null;
|
|
5131
5511
|
const match = nextVersion.match(/(\d+)/);
|
|
@@ -5789,7 +6169,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5789
6169
|
auditRetentionDays,
|
|
5790
6170
|
normalizedRoutes
|
|
5791
6171
|
);
|
|
5792
|
-
writeFileSync6(
|
|
6172
|
+
writeFileSync6(join9(cwd, conventionFile), authFileContent, "utf8");
|
|
5793
6173
|
filesWritten.push(conventionFile);
|
|
5794
6174
|
} catch (err) {
|
|
5795
6175
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5809,9 +6189,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5809
6189
|
if (!devOnly) {
|
|
5810
6190
|
try {
|
|
5811
6191
|
const envBlock = generateEnvBlock(effectiveMethod, params);
|
|
5812
|
-
const envPath =
|
|
5813
|
-
if (
|
|
5814
|
-
const existing =
|
|
6192
|
+
const envPath = join9(cwd, ".env.example");
|
|
6193
|
+
if (existsSync9(envPath)) {
|
|
6194
|
+
const existing = readFileSync9(envPath, "utf8");
|
|
5815
6195
|
writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
5816
6196
|
} else {
|
|
5817
6197
|
writeFileSync6(envPath, envBlock, "utf8");
|
|
@@ -5832,10 +6212,10 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5832
6212
|
}
|
|
5833
6213
|
if (devOnly) {
|
|
5834
6214
|
try {
|
|
5835
|
-
const mockAuthDir =
|
|
6215
|
+
const mockAuthDir = join9(cwd, "lib");
|
|
5836
6216
|
mkdirSync6(mockAuthDir, { recursive: true });
|
|
5837
6217
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
5838
|
-
writeFileSync6(
|
|
6218
|
+
writeFileSync6(join9(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
5839
6219
|
filesWritten.push("lib/mock-auth.ts");
|
|
5840
6220
|
} catch (err) {
|
|
5841
6221
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5853,9 +6233,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5853
6233
|
};
|
|
5854
6234
|
}
|
|
5855
6235
|
try {
|
|
5856
|
-
const pkgPath =
|
|
5857
|
-
if (
|
|
5858
|
-
const existingPkg =
|
|
6236
|
+
const pkgPath = join9(cwd, "package.json");
|
|
6237
|
+
if (existsSync9(pkgPath)) {
|
|
6238
|
+
const existingPkg = readFileSync9(pkgPath, "utf8");
|
|
5859
6239
|
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
5860
6240
|
writeFileSync6(pkgPath, updatedPkg, "utf8");
|
|
5861
6241
|
filesWritten.push("package.json");
|
|
@@ -5898,7 +6278,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5898
6278
|
normalizedRoutes,
|
|
5899
6279
|
useProxy
|
|
5900
6280
|
);
|
|
5901
|
-
writeFileSync6(
|
|
6281
|
+
writeFileSync6(join9(cwd, "stackwright.auth.yml"), authYaml, "utf8");
|
|
5902
6282
|
filesWritten.push("stackwright.auth.yml");
|
|
5903
6283
|
} catch (err) {
|
|
5904
6284
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6011,8 +6391,8 @@ function registerAuthTools(server2) {
|
|
|
6011
6391
|
|
|
6012
6392
|
// src/integrity.ts
|
|
6013
6393
|
import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
6014
|
-
import { readFileSync as
|
|
6015
|
-
import { join as
|
|
6394
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync6, lstatSync as lstatSync8 } from "fs";
|
|
6395
|
+
import { join as join10, basename } from "path";
|
|
6016
6396
|
var _checksums = /* @__PURE__ */ new Map([
|
|
6017
6397
|
[
|
|
6018
6398
|
"stackwright-pro-api-otter.json",
|
|
@@ -6020,15 +6400,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6020
6400
|
],
|
|
6021
6401
|
[
|
|
6022
6402
|
"stackwright-pro-auth-otter.json",
|
|
6023
|
-
"
|
|
6403
|
+
"69fa03c5532ef617c86fe7af41e8174c168d282baf602b1b269a7e933ea86b7c"
|
|
6024
6404
|
],
|
|
6025
6405
|
[
|
|
6026
6406
|
"stackwright-pro-dashboard-otter.json",
|
|
6027
|
-
"
|
|
6407
|
+
"f55081bf4d6545e5435128919f6f9233956ba37671fc9f85300f240ad2497ab6"
|
|
6028
6408
|
],
|
|
6029
6409
|
[
|
|
6030
6410
|
"stackwright-pro-data-otter.json",
|
|
6031
|
-
"
|
|
6411
|
+
"be4e3d6f530ae2d6b79d6412e8d2d6defb3caffddedf5be4f928976a72206074"
|
|
6032
6412
|
],
|
|
6033
6413
|
[
|
|
6034
6414
|
"stackwright-pro-designer-otter.json",
|
|
@@ -6040,19 +6420,19 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6040
6420
|
],
|
|
6041
6421
|
[
|
|
6042
6422
|
"stackwright-pro-foreman-otter.json",
|
|
6043
|
-
"
|
|
6423
|
+
"88c4101154d44a7c5470ded13562402a39323ee570ed3a90a73b8d85d7fd2817"
|
|
6044
6424
|
],
|
|
6045
6425
|
[
|
|
6046
6426
|
"stackwright-pro-form-wizard-otter.json",
|
|
6047
|
-
"
|
|
6427
|
+
"cb350297a0068ead2424e703e8c775d91c0b87249ea6904f43b903baf8a84b79"
|
|
6048
6428
|
],
|
|
6049
6429
|
[
|
|
6050
6430
|
"stackwright-pro-geo-otter.json",
|
|
6051
|
-
"
|
|
6431
|
+
"3032fbd27de36c0cdc0e19e46a32d771208d8e86714482f5a368d46342c8317a"
|
|
6052
6432
|
],
|
|
6053
6433
|
[
|
|
6054
6434
|
"stackwright-pro-page-otter.json",
|
|
6055
|
-
"
|
|
6435
|
+
"cc0db24b00f0130f8b4ec8d385c73ea1bcf5d57ba45aca6b986bcbd5da328930"
|
|
6056
6436
|
],
|
|
6057
6437
|
[
|
|
6058
6438
|
"stackwright-pro-polish-otter.json",
|
|
@@ -6064,7 +6444,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6064
6444
|
],
|
|
6065
6445
|
[
|
|
6066
6446
|
"stackwright-pro-scaffold-otter.json",
|
|
6067
|
-
"
|
|
6447
|
+
"90272bab51bd8ed9e25c0fb6481cdd4252386850f64192edfe1e2eff44166e2d"
|
|
6068
6448
|
],
|
|
6069
6449
|
[
|
|
6070
6450
|
"stackwright-pro-theme-otter.json",
|
|
@@ -6072,7 +6452,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6072
6452
|
],
|
|
6073
6453
|
[
|
|
6074
6454
|
"stackwright-services-otter.json",
|
|
6075
|
-
"
|
|
6455
|
+
"4351348806aeb98404a2bacb044209ca411be5e35e2f2c3d03eb4d28a07fde78"
|
|
6076
6456
|
]
|
|
6077
6457
|
]);
|
|
6078
6458
|
Object.freeze(_checksums);
|
|
@@ -6120,7 +6500,7 @@ function verifyOtterFile(filePath) {
|
|
|
6120
6500
|
}
|
|
6121
6501
|
let raw;
|
|
6122
6502
|
try {
|
|
6123
|
-
raw =
|
|
6503
|
+
raw = readFileSync10(filePath);
|
|
6124
6504
|
} catch (err) {
|
|
6125
6505
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6126
6506
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -6170,7 +6550,7 @@ function verifyAllOtters(otterDir) {
|
|
|
6170
6550
|
const unknown = [];
|
|
6171
6551
|
let entries;
|
|
6172
6552
|
try {
|
|
6173
|
-
entries =
|
|
6553
|
+
entries = readdirSync6(otterDir);
|
|
6174
6554
|
} catch (err) {
|
|
6175
6555
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6176
6556
|
return {
|
|
@@ -6181,7 +6561,7 @@ function verifyAllOtters(otterDir) {
|
|
|
6181
6561
|
}
|
|
6182
6562
|
const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
|
|
6183
6563
|
for (const filename of otterFiles) {
|
|
6184
|
-
const filePath =
|
|
6564
|
+
const filePath = join10(otterDir, filename);
|
|
6185
6565
|
try {
|
|
6186
6566
|
if (lstatSync8(filePath).isSymbolicLink()) {
|
|
6187
6567
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
@@ -6209,7 +6589,7 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
|
|
|
6209
6589
|
function resolveOtterDir() {
|
|
6210
6590
|
const cwd = process.cwd();
|
|
6211
6591
|
for (const relative2 of DEFAULT_SEARCH_PATHS) {
|
|
6212
|
-
const candidate =
|
|
6592
|
+
const candidate = join10(cwd, relative2);
|
|
6213
6593
|
try {
|
|
6214
6594
|
lstatSync8(candidate);
|
|
6215
6595
|
return candidate;
|
|
@@ -6290,13 +6670,13 @@ function registerIntegrityTools(server2) {
|
|
|
6290
6670
|
|
|
6291
6671
|
// src/tools/domain.ts
|
|
6292
6672
|
import { z as z16 } from "zod";
|
|
6293
|
-
import { readFileSync as
|
|
6294
|
-
import { join as
|
|
6673
|
+
import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
6674
|
+
import { join as join11 } from "path";
|
|
6295
6675
|
function handleListCollections(input) {
|
|
6296
6676
|
const cwd = input._cwd ?? process.cwd();
|
|
6297
6677
|
const sources = [
|
|
6298
6678
|
{
|
|
6299
|
-
path:
|
|
6679
|
+
path: join11(cwd, ".stackwright", "artifacts", "data-config.json"),
|
|
6300
6680
|
source: "data-config.json",
|
|
6301
6681
|
parse: (raw) => {
|
|
6302
6682
|
const parsed = JSON.parse(raw);
|
|
@@ -6307,15 +6687,15 @@ function handleListCollections(input) {
|
|
|
6307
6687
|
}
|
|
6308
6688
|
},
|
|
6309
6689
|
{
|
|
6310
|
-
path:
|
|
6690
|
+
path: join11(cwd, "stackwright.yml"),
|
|
6311
6691
|
source: "stackwright.yml",
|
|
6312
6692
|
parse: extractCollectionsFromYaml
|
|
6313
6693
|
}
|
|
6314
6694
|
];
|
|
6315
6695
|
for (const { path: path5, source, parse } of sources) {
|
|
6316
|
-
if (!
|
|
6696
|
+
if (!existsSync10(path5)) continue;
|
|
6317
6697
|
try {
|
|
6318
|
-
const collections = parse(
|
|
6698
|
+
const collections = parse(readFileSync11(path5, "utf8"));
|
|
6319
6699
|
return {
|
|
6320
6700
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
6321
6701
|
isError: false
|
|
@@ -6466,8 +6846,8 @@ function handleValidateWorkflow(input) {
|
|
|
6466
6846
|
if (input.workflow && Object.keys(input.workflow).length > 0) {
|
|
6467
6847
|
raw = input.workflow;
|
|
6468
6848
|
} else {
|
|
6469
|
-
const artifactPath =
|
|
6470
|
-
if (!
|
|
6849
|
+
const artifactPath = join11(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
6850
|
+
if (!existsSync10(artifactPath)) {
|
|
6471
6851
|
return fail([
|
|
6472
6852
|
{
|
|
6473
6853
|
code: "NO_WORKFLOW",
|
|
@@ -6476,7 +6856,7 @@ function handleValidateWorkflow(input) {
|
|
|
6476
6856
|
]);
|
|
6477
6857
|
}
|
|
6478
6858
|
try {
|
|
6479
|
-
raw = JSON.parse(
|
|
6859
|
+
raw = JSON.parse(readFileSync11(artifactPath, "utf8"));
|
|
6480
6860
|
} catch (err) {
|
|
6481
6861
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
6482
6862
|
}
|
|
@@ -6914,16 +7294,16 @@ function registerProPageTools(server2) {
|
|
|
6914
7294
|
|
|
6915
7295
|
// src/tools/scaffold-cleanup.ts
|
|
6916
7296
|
import {
|
|
6917
|
-
existsSync as
|
|
7297
|
+
existsSync as existsSync12,
|
|
6918
7298
|
lstatSync as lstatSync9,
|
|
6919
7299
|
unlinkSync as unlinkSync2,
|
|
6920
|
-
readFileSync as
|
|
7300
|
+
readFileSync as readFileSync12,
|
|
6921
7301
|
writeFileSync as writeFileSync8,
|
|
6922
|
-
readdirSync as
|
|
7302
|
+
readdirSync as readdirSync7,
|
|
6923
7303
|
rmdirSync,
|
|
6924
7304
|
mkdirSync as mkdirSync8
|
|
6925
7305
|
} from "fs";
|
|
6926
|
-
import { join as
|
|
7306
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
6927
7307
|
var SCAFFOLD_FILES_TO_DELETE = [
|
|
6928
7308
|
"content/posts/getting-started.yaml",
|
|
6929
7309
|
"content/posts/hello-world.yaml"
|
|
@@ -6948,12 +7328,12 @@ var FALLBACK_COLORS = {
|
|
|
6948
7328
|
mutedForeground: "#737373"
|
|
6949
7329
|
};
|
|
6950
7330
|
function readThemeColors(cwd) {
|
|
6951
|
-
const tokenPath =
|
|
6952
|
-
if (!
|
|
7331
|
+
const tokenPath = join13(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
7332
|
+
if (!existsSync12(tokenPath)) return { ...FALLBACK_COLORS };
|
|
6953
7333
|
const stat = lstatSync9(tokenPath);
|
|
6954
7334
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
6955
7335
|
try {
|
|
6956
|
-
const raw = JSON.parse(
|
|
7336
|
+
const raw = JSON.parse(readFileSync12(tokenPath, "utf-8"));
|
|
6957
7337
|
const css = raw?.cssVariables ?? {};
|
|
6958
7338
|
const toHsl = (hslValue) => {
|
|
6959
7339
|
if (!hslValue || typeof hslValue !== "string") return null;
|
|
@@ -7013,15 +7393,15 @@ function generateNotFoundPage(colors) {
|
|
|
7013
7393
|
`;
|
|
7014
7394
|
}
|
|
7015
7395
|
function isSafePath(fullPath) {
|
|
7016
|
-
if (!
|
|
7396
|
+
if (!existsSync12(fullPath)) return true;
|
|
7017
7397
|
const stat = lstatSync9(fullPath);
|
|
7018
7398
|
return !stat.isSymbolicLink();
|
|
7019
7399
|
}
|
|
7020
7400
|
function isDirEmpty(dirPath) {
|
|
7021
|
-
if (!
|
|
7401
|
+
if (!existsSync12(dirPath)) return false;
|
|
7022
7402
|
const stat = lstatSync9(dirPath);
|
|
7023
7403
|
if (!stat.isDirectory()) return false;
|
|
7024
|
-
return
|
|
7404
|
+
return readdirSync7(dirPath).length === 0;
|
|
7025
7405
|
}
|
|
7026
7406
|
function handleCleanupScaffold(_cwd) {
|
|
7027
7407
|
const cwd = _cwd ?? process.cwd();
|
|
@@ -7034,8 +7414,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7034
7414
|
cleanupWarnings: []
|
|
7035
7415
|
};
|
|
7036
7416
|
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
7037
|
-
const fullPath =
|
|
7038
|
-
if (!
|
|
7417
|
+
const fullPath = join13(cwd, relPath);
|
|
7418
|
+
if (!existsSync12(fullPath)) {
|
|
7039
7419
|
result.skipped.push(relPath);
|
|
7040
7420
|
continue;
|
|
7041
7421
|
}
|
|
@@ -7052,13 +7432,13 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7052
7432
|
}
|
|
7053
7433
|
{
|
|
7054
7434
|
const relPath = "pages/getting-started/content.yml";
|
|
7055
|
-
const fullPath =
|
|
7056
|
-
if (!
|
|
7435
|
+
const fullPath = join13(cwd, relPath);
|
|
7436
|
+
if (!existsSync12(fullPath)) {
|
|
7057
7437
|
result.skipped.push(relPath);
|
|
7058
7438
|
} else if (!isSafePath(fullPath)) {
|
|
7059
7439
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
7060
7440
|
} else {
|
|
7061
|
-
const content =
|
|
7441
|
+
const content = readFileSync12(fullPath, "utf-8");
|
|
7062
7442
|
const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
|
|
7063
7443
|
(marker) => content.includes(marker)
|
|
7064
7444
|
);
|
|
@@ -7078,16 +7458,16 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7078
7458
|
}
|
|
7079
7459
|
{
|
|
7080
7460
|
const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
|
|
7081
|
-
const fullPath =
|
|
7082
|
-
const postsDir =
|
|
7083
|
-
if (!
|
|
7461
|
+
const fullPath = join13(cwd, relPath);
|
|
7462
|
+
const postsDir = join13(cwd, "content/posts");
|
|
7463
|
+
if (!existsSync12(fullPath)) {
|
|
7084
7464
|
result.skipped.push(relPath);
|
|
7085
7465
|
} else if (!isSafePath(fullPath)) {
|
|
7086
7466
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
7087
|
-
} else if (!
|
|
7467
|
+
} else if (!existsSync12(postsDir)) {
|
|
7088
7468
|
result.skipped.push(relPath);
|
|
7089
7469
|
} else {
|
|
7090
|
-
const userPostFiles =
|
|
7470
|
+
const userPostFiles = readdirSync7(postsDir).filter(
|
|
7091
7471
|
(f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
7092
7472
|
);
|
|
7093
7473
|
if (userPostFiles.length === 0) {
|
|
@@ -7105,8 +7485,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7105
7485
|
}
|
|
7106
7486
|
}
|
|
7107
7487
|
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
7108
|
-
const fullDir =
|
|
7109
|
-
if (!
|
|
7488
|
+
const fullDir = join13(cwd, relDir);
|
|
7489
|
+
if (!existsSync12(fullDir)) continue;
|
|
7110
7490
|
if (!isSafePath(fullDir)) {
|
|
7111
7491
|
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
7112
7492
|
continue;
|
|
@@ -7121,8 +7501,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7121
7501
|
}
|
|
7122
7502
|
}
|
|
7123
7503
|
{
|
|
7124
|
-
const fullPath =
|
|
7125
|
-
if (!
|
|
7504
|
+
const fullPath = join13(cwd, NOT_FOUND_PATH);
|
|
7505
|
+
if (!existsSync12(fullPath)) {
|
|
7126
7506
|
result.skipped.push(NOT_FOUND_PATH);
|
|
7127
7507
|
} else if (!isSafePath(fullPath)) {
|
|
7128
7508
|
result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
|
|
@@ -7643,14 +8023,14 @@ ${results.join("\n")}`;
|
|
|
7643
8023
|
|
|
7644
8024
|
// src/tools/strip-legacy-integrations.ts
|
|
7645
8025
|
import { z as z21 } from "zod";
|
|
7646
|
-
import { existsSync as
|
|
7647
|
-
import { join as
|
|
8026
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
8027
|
+
import { join as join14 } from "path";
|
|
7648
8028
|
function handleStripLegacyIntegrations(input) {
|
|
7649
8029
|
const { projectRoot } = input;
|
|
7650
|
-
const ymlPath =
|
|
7651
|
-
const yamlPath =
|
|
7652
|
-
if (!
|
|
7653
|
-
if (
|
|
8030
|
+
const ymlPath = join14(projectRoot, "stackwright.yml");
|
|
8031
|
+
const yamlPath = join14(projectRoot, "stackwright.yaml");
|
|
8032
|
+
if (!existsSync13(ymlPath)) {
|
|
8033
|
+
if (existsSync13(yamlPath)) {
|
|
7654
8034
|
return {
|
|
7655
8035
|
changed: false,
|
|
7656
8036
|
removedEntries: 0,
|
|
@@ -7663,7 +8043,7 @@ function handleStripLegacyIntegrations(input) {
|
|
|
7663
8043
|
message: "stackwright.yml not found \u2014 no-op."
|
|
7664
8044
|
};
|
|
7665
8045
|
}
|
|
7666
|
-
const raw =
|
|
8046
|
+
const raw = readFileSync13(ymlPath, "utf-8");
|
|
7667
8047
|
const isCRLF = raw.includes("\r\n");
|
|
7668
8048
|
const content = isCRLF ? raw.replace(/\r\n/g, "\n") : raw;
|
|
7669
8049
|
const lines = content.split("\n");
|
|
@@ -7718,15 +8098,15 @@ function registerStripLegacyIntegrationsTool(server2) {
|
|
|
7718
8098
|
|
|
7719
8099
|
// src/tools/emitters.ts
|
|
7720
8100
|
import { z as z22 } from "zod";
|
|
7721
|
-
import { readFileSync as
|
|
7722
|
-
import { join as
|
|
8101
|
+
import { readFileSync as readFileSync14, existsSync as existsSync14, readdirSync as readdirSync8 } from "fs";
|
|
8102
|
+
import { join as join15 } from "path";
|
|
7723
8103
|
import yaml from "js-yaml";
|
|
7724
8104
|
import { emitEnvLocal, emitProvidersFile, emitMockPorts } from "@stackwright-pro/emitters";
|
|
7725
8105
|
function readIntegrations(cwd) {
|
|
7726
|
-
const filePath =
|
|
7727
|
-
if (!
|
|
8106
|
+
const filePath = join15(cwd, "stackwright.integrations.yml");
|
|
8107
|
+
if (!existsSync14(filePath)) return [];
|
|
7728
8108
|
try {
|
|
7729
|
-
const raw = yaml.load(
|
|
8109
|
+
const raw = yaml.load(readFileSync14(filePath, "utf-8"));
|
|
7730
8110
|
if (!raw?.integrations || !Array.isArray(raw.integrations)) return [];
|
|
7731
8111
|
return raw.integrations.filter((i) => typeof i.name === "string").map((i) => ({
|
|
7732
8112
|
name: i.name,
|
|
@@ -7737,15 +8117,15 @@ function readIntegrations(cwd) {
|
|
|
7737
8117
|
}
|
|
7738
8118
|
}
|
|
7739
8119
|
function readServiceTokenRefs(cwd) {
|
|
7740
|
-
const servicesDir =
|
|
7741
|
-
if (!
|
|
8120
|
+
const servicesDir = join15(cwd, "services");
|
|
8121
|
+
if (!existsSync14(servicesDir)) return [];
|
|
7742
8122
|
try {
|
|
7743
|
-
const files =
|
|
8123
|
+
const files = readdirSync8(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
7744
8124
|
const refs = [];
|
|
7745
8125
|
for (const file of files) {
|
|
7746
8126
|
try {
|
|
7747
8127
|
const raw = yaml.load(
|
|
7748
|
-
|
|
8128
|
+
readFileSync14(join15(servicesDir, file), "utf-8")
|
|
7749
8129
|
);
|
|
7750
8130
|
if (raw?.tokenEnv && typeof raw.tokenEnv === "string") {
|
|
7751
8131
|
refs.push({
|
|
@@ -7895,8 +8275,8 @@ This is the third concrete instance of the swp-zf9y deterministic emitter patter
|
|
|
7895
8275
|
|
|
7896
8276
|
// src/tools/list-specs.ts
|
|
7897
8277
|
import { z as z23 } from "zod";
|
|
7898
|
-
import { readdirSync as
|
|
7899
|
-
import { join as
|
|
8278
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync15 } from "fs";
|
|
8279
|
+
import { join as join16, extname, basename as basename2 } from "path";
|
|
7900
8280
|
var STRIP_SUFFIXES = ["-openapi", "-asyncapi", "-api", "-spec"];
|
|
7901
8281
|
var ALLOWED_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
|
|
7902
8282
|
function deriveIntegrationName(fileName) {
|
|
@@ -7918,18 +8298,18 @@ function detectFormat(snippet) {
|
|
|
7918
8298
|
}
|
|
7919
8299
|
function handleListSpecs(input) {
|
|
7920
8300
|
const { projectRoot } = input;
|
|
7921
|
-
const specsDir =
|
|
8301
|
+
const specsDir = join16(projectRoot, "specs");
|
|
7922
8302
|
const empty = { projectRoot, specs: [], totalCount: 0 };
|
|
7923
|
-
if (!
|
|
8303
|
+
if (!existsSync15(specsDir)) return empty;
|
|
7924
8304
|
let entries;
|
|
7925
8305
|
try {
|
|
7926
|
-
entries =
|
|
8306
|
+
entries = readdirSync9(specsDir);
|
|
7927
8307
|
} catch {
|
|
7928
8308
|
return empty;
|
|
7929
8309
|
}
|
|
7930
8310
|
const specs = [];
|
|
7931
8311
|
for (const entry of entries) {
|
|
7932
|
-
const fullPath =
|
|
8312
|
+
const fullPath = join16(specsDir, entry);
|
|
7933
8313
|
let stat;
|
|
7934
8314
|
try {
|
|
7935
8315
|
stat = statSync2(fullPath);
|
|
@@ -7940,7 +8320,7 @@ function handleListSpecs(input) {
|
|
|
7940
8320
|
if (!ALLOWED_EXTS.has(extname(entry).toLowerCase())) continue;
|
|
7941
8321
|
let snippet = "";
|
|
7942
8322
|
try {
|
|
7943
|
-
snippet =
|
|
8323
|
+
snippet = readFileSync15(fullPath, "utf-8").slice(0, 2048);
|
|
7944
8324
|
} catch {
|
|
7945
8325
|
}
|
|
7946
8326
|
specs.push({
|
|
@@ -7972,9 +8352,9 @@ function registerListSpecsTool(server2) {
|
|
|
7972
8352
|
|
|
7973
8353
|
// src/tools/consolidate-integrations.ts
|
|
7974
8354
|
import { z as z24 } from "zod";
|
|
7975
|
-
import { readdirSync as
|
|
8355
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
|
|
7976
8356
|
import { lockSync as lockSync2 } from "proper-lockfile";
|
|
7977
|
-
import { join as
|
|
8357
|
+
import { join as join17, basename as basename3 } from "path";
|
|
7978
8358
|
import yaml2 from "js-yaml";
|
|
7979
8359
|
var BASE_MOCK_PORT = 4011;
|
|
7980
8360
|
var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
|
|
@@ -7987,13 +8367,13 @@ function deriveNameFromFilename(filename) {
|
|
|
7987
8367
|
}
|
|
7988
8368
|
function handleConsolidateIntegrations(input) {
|
|
7989
8369
|
const { projectRoot } = input;
|
|
7990
|
-
const artifactsDir =
|
|
7991
|
-
if (!
|
|
8370
|
+
const artifactsDir = join17(projectRoot, ".stackwright", "artifacts");
|
|
8371
|
+
if (!existsSync16(artifactsDir)) {
|
|
7992
8372
|
return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
|
|
7993
8373
|
}
|
|
7994
8374
|
let entries;
|
|
7995
8375
|
try {
|
|
7996
|
-
entries =
|
|
8376
|
+
entries = readdirSync10(artifactsDir).filter(
|
|
7997
8377
|
(f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
|
|
7998
8378
|
);
|
|
7999
8379
|
} catch {
|
|
@@ -8004,14 +8384,22 @@ function handleConsolidateIntegrations(input) {
|
|
|
8004
8384
|
}
|
|
8005
8385
|
entries.sort();
|
|
8006
8386
|
const integrationsByName = /* @__PURE__ */ new Map();
|
|
8387
|
+
const skippedIntegrationsList = [];
|
|
8007
8388
|
entries.forEach((filename, index) => {
|
|
8008
8389
|
let artifact = {};
|
|
8009
8390
|
try {
|
|
8010
|
-
const raw =
|
|
8391
|
+
const raw = readFileSync16(join17(artifactsDir, filename), "utf-8");
|
|
8011
8392
|
artifact = JSON.parse(raw);
|
|
8012
8393
|
} catch {
|
|
8013
8394
|
}
|
|
8014
8395
|
const name = artifact.integrationName ?? deriveNameFromFilename(filename);
|
|
8396
|
+
if (artifact.skipped && artifact.skipped.length > 0) {
|
|
8397
|
+
skippedIntegrationsList.push({
|
|
8398
|
+
name,
|
|
8399
|
+
reason: artifact.skipped[0]?.reason ?? "explicitly skipped by api-otter"
|
|
8400
|
+
});
|
|
8401
|
+
return;
|
|
8402
|
+
}
|
|
8015
8403
|
const spec = artifact.integrationSpec ?? artifact.specPath ?? `./specs/${name}.yaml`;
|
|
8016
8404
|
const mockUrl = artifact.mockUrl ?? `http://localhost:${BASE_MOCK_PORT + index}`;
|
|
8017
8405
|
const baseUrl = artifact.baseUrl ?? "";
|
|
@@ -8032,20 +8420,20 @@ function handleConsolidateIntegrations(input) {
|
|
|
8032
8420
|
integrationsByName.set(name, entry);
|
|
8033
8421
|
});
|
|
8034
8422
|
const integrations = Array.from(integrationsByName.values());
|
|
8035
|
-
const dedupedCount = entries.length - integrations.length;
|
|
8036
|
-
const integrationsYmlPath =
|
|
8423
|
+
const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
|
|
8424
|
+
const integrationsYmlPath = join17(projectRoot, "stackwright.integrations.yml");
|
|
8037
8425
|
let existing = {};
|
|
8038
|
-
if (
|
|
8426
|
+
if (existsSync16(integrationsYmlPath)) {
|
|
8039
8427
|
try {
|
|
8040
|
-
const raw =
|
|
8428
|
+
const raw = readFileSync16(integrationsYmlPath, "utf-8");
|
|
8041
8429
|
existing = yaml2.load(raw) ?? {};
|
|
8042
8430
|
} catch {
|
|
8043
8431
|
existing = {};
|
|
8044
8432
|
}
|
|
8045
8433
|
}
|
|
8046
8434
|
const merged = { ...existing, integrations };
|
|
8047
|
-
mkdirSync9(
|
|
8048
|
-
if (!
|
|
8435
|
+
mkdirSync9(join17(projectRoot), { recursive: true });
|
|
8436
|
+
if (!existsSync16(integrationsYmlPath)) {
|
|
8049
8437
|
writeFileSync10(integrationsYmlPath, "", "utf-8");
|
|
8050
8438
|
}
|
|
8051
8439
|
const release = lockSync2(integrationsYmlPath, {
|
|
@@ -8064,7 +8452,11 @@ function handleConsolidateIntegrations(input) {
|
|
|
8064
8452
|
written: true,
|
|
8065
8453
|
integrationCount: integrations.length,
|
|
8066
8454
|
path: "stackwright.integrations.yml",
|
|
8067
|
-
...dedupedCount > 0 ? { dedupedCount } : {}
|
|
8455
|
+
...dedupedCount > 0 ? { dedupedCount } : {},
|
|
8456
|
+
...skippedIntegrationsList.length > 0 ? {
|
|
8457
|
+
skippedCount: skippedIntegrationsList.length,
|
|
8458
|
+
skippedIntegrations: skippedIntegrationsList
|
|
8459
|
+
} : {}
|
|
8068
8460
|
};
|
|
8069
8461
|
}
|
|
8070
8462
|
function registerConsolidateIntegrationsTool(server2) {
|
|
@@ -8138,7 +8530,7 @@ var package_default = {
|
|
|
8138
8530
|
"test:coverage": "vitest run --coverage"
|
|
8139
8531
|
},
|
|
8140
8532
|
name: "@stackwright-pro/mcp",
|
|
8141
|
-
version: "0.2.0-alpha.
|
|
8533
|
+
version: "0.2.0-alpha.108",
|
|
8142
8534
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
8143
8535
|
license: "SEE LICENSE IN LICENSE",
|
|
8144
8536
|
main: "./dist/server.js",
|