@stackwright-pro/mcp 0.2.0-alpha.92 → 0.2.0-alpha.97
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.d.mts +2 -1
- package/dist/integrity.d.ts +2 -1
- package/dist/integrity.js +20 -13
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +19 -13
- package/dist/integrity.mjs.map +1 -1
- package/dist/pipeline-constants.d.mts +30 -0
- package/dist/pipeline-constants.d.ts +30 -0
- package/dist/pipeline-constants.js +444 -0
- package/dist/pipeline-constants.js.map +1 -0
- package/dist/pipeline-constants.mjs +428 -0
- package/dist/pipeline-constants.mjs.map +1 -0
- package/dist/server.js +884 -315
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +845 -263
- package/dist/server.mjs.map +1 -1
- package/package.json +12 -3
package/dist/server.js
CHANGED
|
@@ -144,8 +144,8 @@ function registerDataExplorerTools(server2) {
|
|
|
144
144
|
lines.push(" endpoints:");
|
|
145
145
|
if (result.filter.include && result.filter.include.length > 0) {
|
|
146
146
|
lines.push(" include:");
|
|
147
|
-
for (const
|
|
148
|
-
lines.push(` - ${
|
|
147
|
+
for (const path4 of result.filter.include) {
|
|
148
|
+
lines.push(` - ${path4}`);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
if (result.filter.exclude && result.filter.exclude.length > 0) {
|
|
@@ -1894,8 +1894,8 @@ function registerOrchestrationTools(server2) {
|
|
|
1894
1894
|
{
|
|
1895
1895
|
buildContext: import_zod9.z.string().describe("Free-text description of what the user wants to build")
|
|
1896
1896
|
},
|
|
1897
|
-
async ({ buildContext }) => {
|
|
1898
|
-
const { text, isError } = handleSaveBuildContext({ buildContext });
|
|
1897
|
+
async ({ buildContext: buildContext2 }) => {
|
|
1898
|
+
const { text, isError } = handleSaveBuildContext({ buildContext: buildContext2 });
|
|
1899
1899
|
return {
|
|
1900
1900
|
content: [{ type: "text", text }],
|
|
1901
1901
|
isError
|
|
@@ -2023,8 +2023,8 @@ function registerOrchestrationTools(server2) {
|
|
|
2023
2023
|
|
|
2024
2024
|
// src/tools/pipeline.ts
|
|
2025
2025
|
var import_zod13 = require("zod");
|
|
2026
|
-
var
|
|
2027
|
-
var
|
|
2026
|
+
var import_fs6 = require("fs");
|
|
2027
|
+
var import_path6 = require("path");
|
|
2028
2028
|
var import_crypto3 = require("crypto");
|
|
2029
2029
|
|
|
2030
2030
|
// src/artifact-signing.ts
|
|
@@ -2686,6 +2686,224 @@ function registerGetSchemaTool(server2) {
|
|
|
2686
2686
|
);
|
|
2687
2687
|
}
|
|
2688
2688
|
|
|
2689
|
+
// src/tools/pipeline-graph.ts
|
|
2690
|
+
var import_fs5 = require("fs");
|
|
2691
|
+
var import_path5 = require("path");
|
|
2692
|
+
var MANIFEST_NAME_TO_PHASE = {
|
|
2693
|
+
"stackwright-pro-designer-otter": "designer",
|
|
2694
|
+
"stackwright-pro-theme-otter": "theme",
|
|
2695
|
+
"stackwright-pro-scaffold-otter": "scaffold",
|
|
2696
|
+
"stackwright-pro-api-otter": "api",
|
|
2697
|
+
"stackwright-pro-auth-otter": "auth",
|
|
2698
|
+
"stackwright-pro-data-otter": "data",
|
|
2699
|
+
"stackwright-pro-page-otter": "pages",
|
|
2700
|
+
"stackwright-pro-dashboard-otter": "dashboard",
|
|
2701
|
+
"stackwright-pro-form-wizard-otter": "workflow",
|
|
2702
|
+
"stackwright-pro-geo-otter": "geo",
|
|
2703
|
+
"stackwright-pro-polish-otter": "polish",
|
|
2704
|
+
"stackwright-services-otter": "services",
|
|
2705
|
+
"stackwright-pro-qa-otter": "qa"
|
|
2706
|
+
};
|
|
2707
|
+
function manifestNameToPhase(name) {
|
|
2708
|
+
return MANIFEST_NAME_TO_PHASE[name] ?? null;
|
|
2709
|
+
}
|
|
2710
|
+
var OTTER_SEARCH_PATHS = [
|
|
2711
|
+
"node_modules/@stackwright-pro/otters/src/",
|
|
2712
|
+
// production: installed package
|
|
2713
|
+
"packages/otters/src/",
|
|
2714
|
+
// monorepo: run from repo root
|
|
2715
|
+
"../otters/src/"
|
|
2716
|
+
// monorepo: run from packages/mcp/
|
|
2717
|
+
];
|
|
2718
|
+
function resolveOtterDirFromCwd() {
|
|
2719
|
+
const cwd = process.cwd();
|
|
2720
|
+
for (const relative of OTTER_SEARCH_PATHS) {
|
|
2721
|
+
const candidate = (0, import_path5.join)(cwd, relative);
|
|
2722
|
+
try {
|
|
2723
|
+
(0, import_fs5.lstatSync)(candidate);
|
|
2724
|
+
return candidate;
|
|
2725
|
+
} catch {
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
throw new Error(
|
|
2729
|
+
`Cannot locate otter directory. Searched: ${OTTER_SEARCH_PATHS.join(", ")} (relative to ${cwd}). Make sure @stackwright-pro/otters is installed.`
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
function loadOtterManifestsFromDir(otterDir) {
|
|
2733
|
+
const entries = (0, import_fs5.readdirSync)(otterDir);
|
|
2734
|
+
const manifests = [];
|
|
2735
|
+
for (const filename of entries) {
|
|
2736
|
+
if (!filename.endsWith("-otter.json")) continue;
|
|
2737
|
+
const filePath = (0, import_path5.join)(otterDir, filename);
|
|
2738
|
+
if ((0, import_fs5.lstatSync)(filePath).isSymbolicLink()) continue;
|
|
2739
|
+
try {
|
|
2740
|
+
const raw = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
2741
|
+
const manifest = JSON.parse(raw);
|
|
2742
|
+
manifests.push(manifest);
|
|
2743
|
+
} catch (err) {
|
|
2744
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2745
|
+
throw new Error(`Failed to load otter manifest "${filename}": ${msg}`, { cause: err });
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
return manifests;
|
|
2749
|
+
}
|
|
2750
|
+
function topologicalSort(dependencies) {
|
|
2751
|
+
const phases = Object.keys(dependencies);
|
|
2752
|
+
const inDegree = {};
|
|
2753
|
+
const adjList = {};
|
|
2754
|
+
for (const phase of phases) {
|
|
2755
|
+
inDegree[phase] ??= 0;
|
|
2756
|
+
adjList[phase] ??= [];
|
|
2757
|
+
}
|
|
2758
|
+
for (const phase of phases) {
|
|
2759
|
+
for (const dep of dependencies[phase] ?? []) {
|
|
2760
|
+
inDegree[phase] = (inDegree[phase] ?? 0) + 1;
|
|
2761
|
+
adjList[dep] ??= [];
|
|
2762
|
+
adjList[dep].push(phase);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
const queue = phases.filter((p) => (inDegree[p] ?? 0) === 0);
|
|
2766
|
+
const result = [];
|
|
2767
|
+
while (queue.length > 0) {
|
|
2768
|
+
const node = queue.shift();
|
|
2769
|
+
result.push(node);
|
|
2770
|
+
for (const dependent of adjList[node] ?? []) {
|
|
2771
|
+
inDegree[dependent] -= 1;
|
|
2772
|
+
if (inDegree[dependent] === 0) {
|
|
2773
|
+
queue.push(dependent);
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
if (result.length !== phases.length) {
|
|
2778
|
+
const cycleNodes = phases.filter((p) => (inDegree[p] ?? 0) > 0);
|
|
2779
|
+
throw new Error(
|
|
2780
|
+
`Pipeline dependency cycle detected involving: [${cycleNodes.join(", ")}]. Check otter pipeline declarations for circular dependencies.`
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2783
|
+
return result;
|
|
2784
|
+
}
|
|
2785
|
+
function buildPipelineGraph(manifests) {
|
|
2786
|
+
const sinkProducers = {};
|
|
2787
|
+
const artifactProducers = {};
|
|
2788
|
+
for (const m of manifests) {
|
|
2789
|
+
const phase = manifestNameToPhase(m.name);
|
|
2790
|
+
if (!phase) continue;
|
|
2791
|
+
const out = m.pipeline?.outputs;
|
|
2792
|
+
if (!out) continue;
|
|
2793
|
+
for (const sink of out.sinks ?? []) {
|
|
2794
|
+
if (sinkProducers[sink]) {
|
|
2795
|
+
throw new Error(
|
|
2796
|
+
`Sink "${sink}" is produced by both "${sinkProducers[sink]}" and "${phase}". Each sink must have exactly one producer.`
|
|
2797
|
+
);
|
|
2798
|
+
}
|
|
2799
|
+
sinkProducers[sink] = phase;
|
|
2800
|
+
}
|
|
2801
|
+
if (out.artifact) {
|
|
2802
|
+
if (artifactProducers[out.artifact]) {
|
|
2803
|
+
throw new Error(
|
|
2804
|
+
`Artifact "${out.artifact}" is produced by both "${artifactProducers[out.artifact]}" and "${phase}". Each artifact must have exactly one producer.`
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
artifactProducers[out.artifact] = phase;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
const dependencies = {};
|
|
2811
|
+
for (const m of manifests) {
|
|
2812
|
+
const phase = manifestNameToPhase(m.name);
|
|
2813
|
+
if (!phase) continue;
|
|
2814
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2815
|
+
const inp = m.pipeline?.inputs;
|
|
2816
|
+
if (inp) {
|
|
2817
|
+
for (const sink of inp.sinks ?? []) {
|
|
2818
|
+
const producer = sinkProducers[sink];
|
|
2819
|
+
if (!producer) {
|
|
2820
|
+
throw new Error(
|
|
2821
|
+
`Phase "${phase}" declares sink input "${sink}" but no otter produces it. Add an otter with outputs.sinks including "${sink}".`
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2824
|
+
if (producer !== phase) deps.add(producer);
|
|
2825
|
+
}
|
|
2826
|
+
for (const artifact of inp.artifacts ?? []) {
|
|
2827
|
+
const producer = artifactProducers[artifact];
|
|
2828
|
+
if (!producer) {
|
|
2829
|
+
throw new Error(
|
|
2830
|
+
`Phase "${phase}" declares artifact input "${artifact}" but no otter produces it. Add an otter with outputs.artifact = "${artifact}".`
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
if (producer !== phase) deps.add(producer);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
dependencies[phase] = Array.from(deps);
|
|
2837
|
+
}
|
|
2838
|
+
const order = topologicalSort(dependencies);
|
|
2839
|
+
return { dependencies, order, sinkProducers, artifactProducers };
|
|
2840
|
+
}
|
|
2841
|
+
var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
|
|
2842
|
+
function loadDistributedOtterManifests(cwd) {
|
|
2843
|
+
const results = [];
|
|
2844
|
+
for (const namespace of DISTRIBUTED_NAMESPACES) {
|
|
2845
|
+
const nsDir = (0, import_path5.join)(cwd, "node_modules", namespace);
|
|
2846
|
+
let pkgDirNames;
|
|
2847
|
+
try {
|
|
2848
|
+
pkgDirNames = (0, import_fs5.readdirSync)(nsDir);
|
|
2849
|
+
} catch {
|
|
2850
|
+
continue;
|
|
2851
|
+
}
|
|
2852
|
+
for (const pkgDirName of pkgDirNames) {
|
|
2853
|
+
const pkgDir = (0, import_path5.join)(nsDir, pkgDirName);
|
|
2854
|
+
const packageName = `${namespace}/${pkgDirName}`;
|
|
2855
|
+
let otterRelPaths;
|
|
2856
|
+
try {
|
|
2857
|
+
const raw = (0, import_fs5.readFileSync)((0, import_path5.join)(pkgDir, "package.json"), "utf-8");
|
|
2858
|
+
const pkgJson = JSON.parse(raw);
|
|
2859
|
+
const declared = pkgJson.stackwright?.otters;
|
|
2860
|
+
if (!Array.isArray(declared) || declared.length === 0) continue;
|
|
2861
|
+
otterRelPaths = declared;
|
|
2862
|
+
} catch {
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2865
|
+
for (const relPath of otterRelPaths) {
|
|
2866
|
+
const otterFilePath = (0, import_path5.join)(pkgDir, relPath);
|
|
2867
|
+
try {
|
|
2868
|
+
const raw = (0, import_fs5.readFileSync)(otterFilePath, "utf-8");
|
|
2869
|
+
const manifest = JSON.parse(raw);
|
|
2870
|
+
results.push({ manifest, packageName });
|
|
2871
|
+
} catch (err) {
|
|
2872
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2873
|
+
console.warn(
|
|
2874
|
+
`[pipeline-graph] Failed to load distributed otter at "${otterFilePath}" from "${packageName}": ${msg}`
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
return results;
|
|
2881
|
+
}
|
|
2882
|
+
function _mergeManifestPools(central, distributed) {
|
|
2883
|
+
const centralNames = new Set(central.map((m) => m.name));
|
|
2884
|
+
const merged = [...central];
|
|
2885
|
+
for (const { manifest, packageName } of distributed) {
|
|
2886
|
+
if (centralNames.has(manifest.name)) {
|
|
2887
|
+
console.warn(
|
|
2888
|
+
`[pipeline-graph] Otter "${manifest.name}" declared by both @stackwright-pro/otters and ${packageName} \u2014 using @stackwright-pro/otters version.`
|
|
2889
|
+
);
|
|
2890
|
+
} else {
|
|
2891
|
+
merged.push(manifest);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
return merged;
|
|
2895
|
+
}
|
|
2896
|
+
var _cachedGraph = null;
|
|
2897
|
+
function loadPipelineGraph() {
|
|
2898
|
+
if (_cachedGraph) return _cachedGraph;
|
|
2899
|
+
const otterDir = resolveOtterDirFromCwd();
|
|
2900
|
+
const central = loadOtterManifestsFromDir(otterDir);
|
|
2901
|
+
const distributed = loadDistributedOtterManifests(process.cwd());
|
|
2902
|
+
const manifests = _mergeManifestPools(central, distributed);
|
|
2903
|
+
_cachedGraph = buildPipelineGraph(manifests);
|
|
2904
|
+
return _cachedGraph;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2689
2907
|
// src/tools/pipeline.ts
|
|
2690
2908
|
var PHASE_ORDER = [
|
|
2691
2909
|
"designer",
|
|
@@ -2694,45 +2912,16 @@ var PHASE_ORDER = [
|
|
|
2694
2912
|
// generates app/ directory from config
|
|
2695
2913
|
"api",
|
|
2696
2914
|
"data",
|
|
2915
|
+
"auth",
|
|
2916
|
+
// moved earlier — only depends on design-language.json (designer)
|
|
2697
2917
|
"geo",
|
|
2698
2918
|
"workflow",
|
|
2699
2919
|
"services",
|
|
2700
2920
|
"pages",
|
|
2701
2921
|
"dashboard",
|
|
2702
|
-
"
|
|
2703
|
-
"
|
|
2922
|
+
"polish",
|
|
2923
|
+
"qa"
|
|
2704
2924
|
];
|
|
2705
|
-
var PHASE_DEPENDENCIES = {
|
|
2706
|
-
designer: [],
|
|
2707
|
-
theme: ["designer"],
|
|
2708
|
-
scaffold: ["designer", "theme"],
|
|
2709
|
-
// needs stackwright.yml + theme-tokens
|
|
2710
|
-
api: [],
|
|
2711
|
-
data: ["api"],
|
|
2712
|
-
geo: ["data"],
|
|
2713
|
-
// workflow: no hard deps — uses service: references with Prism mock fallback
|
|
2714
|
-
// when API artifacts aren't available; roles come from user answers
|
|
2715
|
-
workflow: [],
|
|
2716
|
-
// services: needs API endpoints to know what to wire, and optionally
|
|
2717
|
-
// workflow states as trigger sources. Produces OpenAPI specs consumed
|
|
2718
|
-
// by pages and dashboard for typed data wiring.
|
|
2719
|
-
services: ["api", "workflow"],
|
|
2720
|
-
// pages/dashboard: depend on services for typed endpoint wiring (OpenAPI
|
|
2721
|
-
// specs) and on geo so the specialist prompt includes geo-manifest.json
|
|
2722
|
-
// — page/dashboard otters see which page slugs are already claimed and
|
|
2723
|
-
// won't attempt to overwrite them (swp-73c). 'api' is still transitive
|
|
2724
|
-
// through 'data'; auth removed — page-otter has graceful fallback.
|
|
2725
|
-
pages: ["designer", "theme", "data", "services", "geo", "scaffold"],
|
|
2726
|
-
dashboard: ["designer", "theme", "data", "services", "geo", "scaffold"],
|
|
2727
|
-
// auth is the penultimate phase — runs after all content-producing phases
|
|
2728
|
-
// so it can read pages, dashboard, workflow, and geo artifacts for route
|
|
2729
|
-
// protection and RBAC wiring. Skipped upstream phases still satisfy deps
|
|
2730
|
-
// (the foreman marks them executed=true), so auth always runs.
|
|
2731
|
-
auth: ["pages", "dashboard", "workflow", "geo"],
|
|
2732
|
-
// polish is the terminal phase — runs after auth so the landing page and
|
|
2733
|
-
// nav reflect the final set of protected routes and all generated pages.
|
|
2734
|
-
polish: ["auth"]
|
|
2735
|
-
};
|
|
2736
2925
|
var PHASE_ARTIFACT = {
|
|
2737
2926
|
designer: "design-language.json",
|
|
2738
2927
|
theme: "theme-tokens.json",
|
|
@@ -2745,7 +2934,8 @@ var PHASE_ARTIFACT = {
|
|
|
2745
2934
|
workflow: "workflow-config.json",
|
|
2746
2935
|
services: "services-config.json",
|
|
2747
2936
|
polish: "polish-manifest.json",
|
|
2748
|
-
geo: "geo-manifest.json"
|
|
2937
|
+
geo: "geo-manifest.json",
|
|
2938
|
+
qa: "qa-findings.json"
|
|
2749
2939
|
};
|
|
2750
2940
|
var PHASE_TO_OTTER2 = {
|
|
2751
2941
|
designer: "stackwright-pro-designer-otter",
|
|
@@ -2759,7 +2949,8 @@ var PHASE_TO_OTTER2 = {
|
|
|
2759
2949
|
workflow: "stackwright-pro-form-wizard-otter",
|
|
2760
2950
|
services: "stackwright-services-otter",
|
|
2761
2951
|
polish: "stackwright-pro-polish-otter",
|
|
2762
|
-
geo: "stackwright-pro-geo-otter"
|
|
2952
|
+
geo: "stackwright-pro-geo-otter",
|
|
2953
|
+
qa: "stackwright-pro-qa-otter"
|
|
2763
2954
|
};
|
|
2764
2955
|
function isValidPhase2(phase) {
|
|
2765
2956
|
return PHASE_ORDER.includes(phase);
|
|
@@ -2789,11 +2980,11 @@ function createDefaultState() {
|
|
|
2789
2980
|
};
|
|
2790
2981
|
}
|
|
2791
2982
|
function statePath(cwd) {
|
|
2792
|
-
return (0,
|
|
2983
|
+
return (0, import_path6.join)(cwd, ".stackwright", "pipeline-state.json");
|
|
2793
2984
|
}
|
|
2794
2985
|
function readState(cwd) {
|
|
2795
2986
|
const p = statePath(cwd);
|
|
2796
|
-
if (!(0,
|
|
2987
|
+
if (!(0, import_fs6.existsSync)(p)) return createDefaultState();
|
|
2797
2988
|
const raw = JSON.parse(safeReadSync(p));
|
|
2798
2989
|
if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
|
|
2799
2990
|
return createDefaultState();
|
|
@@ -2801,26 +2992,26 @@ function readState(cwd) {
|
|
|
2801
2992
|
return raw;
|
|
2802
2993
|
}
|
|
2803
2994
|
function safeWriteSync(filePath, content) {
|
|
2804
|
-
if ((0,
|
|
2805
|
-
const stat = (0,
|
|
2995
|
+
if ((0, import_fs6.existsSync)(filePath)) {
|
|
2996
|
+
const stat = (0, import_fs6.lstatSync)(filePath);
|
|
2806
2997
|
if (stat.isSymbolicLink()) {
|
|
2807
2998
|
throw new Error(`Refusing to write to symlink: ${filePath}`);
|
|
2808
2999
|
}
|
|
2809
3000
|
}
|
|
2810
|
-
(0,
|
|
3001
|
+
(0, import_fs6.writeFileSync)(filePath, content);
|
|
2811
3002
|
}
|
|
2812
3003
|
function safeReadSync(filePath) {
|
|
2813
|
-
if ((0,
|
|
2814
|
-
const stat = (0,
|
|
3004
|
+
if ((0, import_fs6.existsSync)(filePath)) {
|
|
3005
|
+
const stat = (0, import_fs6.lstatSync)(filePath);
|
|
2815
3006
|
if (stat.isSymbolicLink()) {
|
|
2816
3007
|
throw new Error(`Refusing to read symlink: ${filePath}`);
|
|
2817
3008
|
}
|
|
2818
3009
|
}
|
|
2819
|
-
return (0,
|
|
3010
|
+
return (0, import_fs6.readFileSync)(filePath, "utf-8");
|
|
2820
3011
|
}
|
|
2821
3012
|
function writeState(cwd, state) {
|
|
2822
|
-
const dir = (0,
|
|
2823
|
-
(0,
|
|
3013
|
+
const dir = (0, import_path6.join)(cwd, ".stackwright");
|
|
3014
|
+
(0, import_fs6.mkdirSync)(dir, { recursive: true });
|
|
2824
3015
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2825
3016
|
safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
2826
3017
|
}
|
|
@@ -2841,8 +3032,8 @@ function handleGetPipelineState(_cwd) {
|
|
|
2841
3032
|
const cwd = _cwd ?? process.cwd();
|
|
2842
3033
|
try {
|
|
2843
3034
|
const state = readState(cwd);
|
|
2844
|
-
const keyPath = (0,
|
|
2845
|
-
if (!(0,
|
|
3035
|
+
const keyPath = (0, import_path6.join)(cwd, ".stackwright", "pipeline-keys.json");
|
|
3036
|
+
if (!(0, import_fs6.existsSync)(keyPath)) {
|
|
2846
3037
|
try {
|
|
2847
3038
|
const { fingerprint } = initPipelineKeys(cwd);
|
|
2848
3039
|
state["signingKeyFingerprint"] = fingerprint;
|
|
@@ -2945,8 +3136,8 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
2945
3136
|
isError: true
|
|
2946
3137
|
};
|
|
2947
3138
|
}
|
|
2948
|
-
const answerFile = (0,
|
|
2949
|
-
if (!(0,
|
|
3139
|
+
const answerFile = (0, import_path6.join)(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3140
|
+
if (!(0, import_fs6.existsSync)(answerFile)) {
|
|
2950
3141
|
return {
|
|
2951
3142
|
text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
|
|
2952
3143
|
isError: false
|
|
@@ -2970,12 +3161,12 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
2970
3161
|
}
|
|
2971
3162
|
}
|
|
2972
3163
|
try {
|
|
2973
|
-
const answersDir = (0,
|
|
3164
|
+
const answersDir = (0, import_path6.join)(cwd, ".stackwright", "answers");
|
|
2974
3165
|
const answeredPhases = [];
|
|
2975
3166
|
const missingPhases = [];
|
|
2976
3167
|
for (const phase2 of PHASE_ORDER) {
|
|
2977
|
-
const answerFile = (0,
|
|
2978
|
-
if ((0,
|
|
3168
|
+
const answerFile = (0, import_path6.join)(answersDir, `${phase2}.json`);
|
|
3169
|
+
if ((0, import_fs6.existsSync)(answerFile)) {
|
|
2979
3170
|
try {
|
|
2980
3171
|
const raw = safeReadSync(answerFile);
|
|
2981
3172
|
const parsed = JSON.parse(raw);
|
|
@@ -3008,6 +3199,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3008
3199
|
const cwd = _cwd ?? process.cwd();
|
|
3009
3200
|
try {
|
|
3010
3201
|
const state = readState(cwd);
|
|
3202
|
+
const graph = loadPipelineGraph();
|
|
3011
3203
|
const completed = [];
|
|
3012
3204
|
const ready = [];
|
|
3013
3205
|
const blocked = [];
|
|
@@ -3017,7 +3209,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3017
3209
|
completed.push(phase);
|
|
3018
3210
|
continue;
|
|
3019
3211
|
}
|
|
3020
|
-
const deps =
|
|
3212
|
+
const deps = graph.dependencies[phase] ?? [];
|
|
3021
3213
|
const unmetDeps = deps.filter((dep) => !state.phases[dep]?.executed);
|
|
3022
3214
|
if (unmetDeps.length === 0) {
|
|
3023
3215
|
ready.push(phase);
|
|
@@ -3043,7 +3235,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3043
3235
|
function handleListArtifacts(_cwd) {
|
|
3044
3236
|
const cwd = _cwd ?? process.cwd();
|
|
3045
3237
|
try {
|
|
3046
|
-
const artifactsDir = (0,
|
|
3238
|
+
const artifactsDir = (0, import_path6.join)(cwd, ".stackwright", "artifacts");
|
|
3047
3239
|
let manifest = null;
|
|
3048
3240
|
try {
|
|
3049
3241
|
manifest = loadSignatureManifest(cwd);
|
|
@@ -3053,8 +3245,8 @@ function handleListArtifacts(_cwd) {
|
|
|
3053
3245
|
let completedCount = 0;
|
|
3054
3246
|
for (const phase of PHASE_ORDER) {
|
|
3055
3247
|
const expectedFile = PHASE_ARTIFACT[phase];
|
|
3056
|
-
const fullPath = (0,
|
|
3057
|
-
const exists = (0,
|
|
3248
|
+
const fullPath = (0, import_path6.join)(artifactsDir, expectedFile);
|
|
3249
|
+
const exists = (0, import_fs6.existsSync)(fullPath);
|
|
3058
3250
|
if (exists) completedCount++;
|
|
3059
3251
|
let signed = false;
|
|
3060
3252
|
let signatureValid = null;
|
|
@@ -3107,9 +3299,9 @@ function handleWritePhaseQuestions(input) {
|
|
|
3107
3299
|
}
|
|
3108
3300
|
} catch {
|
|
3109
3301
|
}
|
|
3110
|
-
const questionsDir = (0,
|
|
3111
|
-
(0,
|
|
3112
|
-
const filePath = (0,
|
|
3302
|
+
const questionsDir = (0, import_path6.join)(cwd, ".stackwright", "questions");
|
|
3303
|
+
(0, import_fs6.mkdirSync)(questionsDir, { recursive: true });
|
|
3304
|
+
const filePath = (0, import_path6.join)(questionsDir, `${phase}.json`);
|
|
3113
3305
|
const payload = {
|
|
3114
3306
|
version: "1.0",
|
|
3115
3307
|
phase,
|
|
@@ -3149,14 +3341,14 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3149
3341
|
};
|
|
3150
3342
|
}
|
|
3151
3343
|
try {
|
|
3152
|
-
const answersPath = (0,
|
|
3344
|
+
const answersPath = (0, import_path6.join)(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3153
3345
|
let answers = {};
|
|
3154
|
-
if ((0,
|
|
3346
|
+
if ((0, import_fs6.existsSync)(answersPath)) {
|
|
3155
3347
|
answers = JSON.parse(safeReadSync(answersPath));
|
|
3156
3348
|
}
|
|
3157
3349
|
let buildContextText = "";
|
|
3158
|
-
const buildContextPath = (0,
|
|
3159
|
-
if ((0,
|
|
3350
|
+
const buildContextPath = (0, import_path6.join)(cwd, ".stackwright", "build-context.json");
|
|
3351
|
+
if ((0, import_fs6.existsSync)(buildContextPath)) {
|
|
3160
3352
|
try {
|
|
3161
3353
|
const bcRaw = JSON.parse(safeReadSync(buildContextPath));
|
|
3162
3354
|
if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
|
|
@@ -3165,13 +3357,14 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3165
3357
|
} catch {
|
|
3166
3358
|
}
|
|
3167
3359
|
}
|
|
3168
|
-
const
|
|
3360
|
+
const graph = loadPipelineGraph();
|
|
3361
|
+
const deps = graph.dependencies[phase] ?? [];
|
|
3169
3362
|
const artifactSections = [];
|
|
3170
3363
|
const missingDependencies = [];
|
|
3171
3364
|
for (const dep of deps) {
|
|
3172
3365
|
const artifactFile = PHASE_ARTIFACT[dep];
|
|
3173
|
-
const artifactPath = (0,
|
|
3174
|
-
if ((0,
|
|
3366
|
+
const artifactPath = (0, import_path6.join)(cwd, ".stackwright", "artifacts", artifactFile);
|
|
3367
|
+
if ((0, import_fs6.existsSync)(artifactPath)) {
|
|
3175
3368
|
const rawContent = safeReadSync(artifactPath);
|
|
3176
3369
|
const rawBytes = Buffer.from(rawContent, "utf-8");
|
|
3177
3370
|
const content = JSON.parse(rawContent);
|
|
@@ -3236,8 +3429,8 @@ ${JSON.stringify(content, null, 2)}`
|
|
|
3236
3429
|
parts.push("BUILD_CONTEXT:", buildContextText, "");
|
|
3237
3430
|
}
|
|
3238
3431
|
if (phase === "auth") {
|
|
3239
|
-
const initContextPath = (0,
|
|
3240
|
-
if ((0,
|
|
3432
|
+
const initContextPath = (0, import_path6.join)(cwd, ".stackwright", "init-context.json");
|
|
3433
|
+
if ((0, import_fs6.existsSync)(initContextPath)) {
|
|
3241
3434
|
try {
|
|
3242
3435
|
const initContext = JSON.parse(safeReadSync(initContextPath));
|
|
3243
3436
|
if (initContext.devOnly === true || initContext.nonInteractive === true) {
|
|
@@ -3301,7 +3494,10 @@ var PHASE_REQUIRED_KEYS = {
|
|
|
3301
3494
|
workflow: ["version", "generatedBy"],
|
|
3302
3495
|
services: ["version", "generatedBy", "flows"],
|
|
3303
3496
|
polish: ["version", "generatedBy"],
|
|
3304
|
-
geo: ["version", "generatedBy", "geoCollections"]
|
|
3497
|
+
geo: ["version", "generatedBy", "geoCollections"],
|
|
3498
|
+
// qa: skipped=true path only needs version+generatedBy+skipped;
|
|
3499
|
+
// skipped=false path also needs summary+findings — Zod validator enforces the conditional.
|
|
3500
|
+
qa: ["version", "generatedBy", "skipped"]
|
|
3305
3501
|
};
|
|
3306
3502
|
var PHASE_ARTIFACT_SCHEMA = {
|
|
3307
3503
|
designer: JSON.stringify(
|
|
@@ -3591,6 +3787,41 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3591
3787
|
},
|
|
3592
3788
|
null,
|
|
3593
3789
|
2
|
|
3790
|
+
),
|
|
3791
|
+
qa: JSON.stringify(
|
|
3792
|
+
{
|
|
3793
|
+
version: "1.0",
|
|
3794
|
+
generatedBy: "stackwright-pro-qa-otter",
|
|
3795
|
+
// skipped: true path — when dev server is unreachable at audit time
|
|
3796
|
+
// skipped: false path — full audit completed
|
|
3797
|
+
skipped: false,
|
|
3798
|
+
skipReason: null,
|
|
3799
|
+
wcagLevel: "<AA|AAA>",
|
|
3800
|
+
summary: {
|
|
3801
|
+
routesAudited: 3,
|
|
3802
|
+
serious: 0,
|
|
3803
|
+
moderate: 1,
|
|
3804
|
+
minor: 0
|
|
3805
|
+
},
|
|
3806
|
+
findings: [
|
|
3807
|
+
{
|
|
3808
|
+
id: "qa-001",
|
|
3809
|
+
route: "/dashboard",
|
|
3810
|
+
severity: "<serious|moderate|minor>",
|
|
3811
|
+
category: "<visual|a11y|design-contract|runtime>",
|
|
3812
|
+
finding: "Human-readable description of what was observed",
|
|
3813
|
+
evidence: {
|
|
3814
|
+
screenshot: ".stackwright/qa/screenshots/dashboard-light.png",
|
|
3815
|
+
consoleErrors: [],
|
|
3816
|
+
axeViolations: []
|
|
3817
|
+
},
|
|
3818
|
+
suggested_otters: ["stackwright-pro-theme-otter"],
|
|
3819
|
+
suggested_fix: "Specific, concrete next step the repair otter should take"
|
|
3820
|
+
}
|
|
3821
|
+
]
|
|
3822
|
+
},
|
|
3823
|
+
null,
|
|
3824
|
+
2
|
|
3594
3825
|
)
|
|
3595
3826
|
};
|
|
3596
3827
|
function handleValidateArtifact(input) {
|
|
@@ -3704,13 +3935,13 @@ function handleValidateArtifact(input) {
|
|
|
3704
3935
|
// to a flow or state-machine in this services-config artifact.
|
|
3705
3936
|
// Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
|
|
3706
3937
|
services: (artifact2) => {
|
|
3707
|
-
const workflowArtifactPath = (0,
|
|
3708
|
-
if (!(0,
|
|
3938
|
+
const workflowArtifactPath = (0, import_path6.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
3939
|
+
if (!(0, import_fs6.existsSync)(workflowArtifactPath)) {
|
|
3709
3940
|
return { success: true };
|
|
3710
3941
|
}
|
|
3711
3942
|
let workflowArtifact;
|
|
3712
3943
|
try {
|
|
3713
|
-
workflowArtifact = JSON.parse((0,
|
|
3944
|
+
workflowArtifact = JSON.parse((0, import_fs6.readFileSync)(workflowArtifactPath, "utf-8"));
|
|
3714
3945
|
} catch {
|
|
3715
3946
|
return { success: true };
|
|
3716
3947
|
}
|
|
@@ -3731,6 +3962,40 @@ function handleValidateArtifact(input) {
|
|
|
3731
3962
|
};
|
|
3732
3963
|
}
|
|
3733
3964
|
return { success: true };
|
|
3965
|
+
},
|
|
3966
|
+
// qa artifact — permissive validator: requires version + generatedBy + skipped.
|
|
3967
|
+
// When skipped=true: skipReason must be a string.
|
|
3968
|
+
// When skipped=false: findings must be an array and summary must be an object.
|
|
3969
|
+
qa: (artifact2) => {
|
|
3970
|
+
const skipped = artifact2["skipped"];
|
|
3971
|
+
if (typeof skipped !== "boolean") {
|
|
3972
|
+
return {
|
|
3973
|
+
success: false,
|
|
3974
|
+
error: { message: '"skipped" must be a boolean (true or false).' }
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
if (skipped === true) {
|
|
3978
|
+
if (typeof artifact2["skipReason"] !== "string") {
|
|
3979
|
+
return {
|
|
3980
|
+
success: false,
|
|
3981
|
+
error: { message: 'When skipped=true, "skipReason" must be a string.' }
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3984
|
+
return { success: true };
|
|
3985
|
+
}
|
|
3986
|
+
if (!Array.isArray(artifact2["findings"])) {
|
|
3987
|
+
return {
|
|
3988
|
+
success: false,
|
|
3989
|
+
error: { message: 'When skipped=false, "findings" must be an array.' }
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3992
|
+
if (typeof artifact2["summary"] !== "object" || artifact2["summary"] === null) {
|
|
3993
|
+
return {
|
|
3994
|
+
success: false,
|
|
3995
|
+
error: { message: 'When skipped=false, "summary" must be an object.' }
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
return { success: true };
|
|
3734
3999
|
}
|
|
3735
4000
|
};
|
|
3736
4001
|
const zodValidator = PHASE_ZOD_VALIDATORS[phase];
|
|
@@ -3747,10 +4012,10 @@ function handleValidateArtifact(input) {
|
|
|
3747
4012
|
}
|
|
3748
4013
|
}
|
|
3749
4014
|
try {
|
|
3750
|
-
const artifactsDir = (0,
|
|
3751
|
-
(0,
|
|
4015
|
+
const artifactsDir = (0, import_path6.join)(cwd, ".stackwright", "artifacts");
|
|
4016
|
+
(0, import_fs6.mkdirSync)(artifactsDir, { recursive: true });
|
|
3752
4017
|
const artifactFile = PHASE_ARTIFACT[phase];
|
|
3753
|
-
const artifactPath = (0,
|
|
4018
|
+
const artifactPath = (0, import_path6.join)(artifactsDir, artifactFile);
|
|
3754
4019
|
const serialized = JSON.stringify(artifact, null, 2) + "\n";
|
|
3755
4020
|
const artifactBytes = Buffer.from(serialized, "utf-8");
|
|
3756
4021
|
safeWriteSync(artifactPath, serialized);
|
|
@@ -3867,10 +4132,10 @@ function registerPipelineTools(server2) {
|
|
|
3867
4132
|
isError: true
|
|
3868
4133
|
};
|
|
3869
4134
|
}
|
|
3870
|
-
const questionsDir = (0,
|
|
3871
|
-
(0,
|
|
3872
|
-
const outPath = (0,
|
|
3873
|
-
(0,
|
|
4135
|
+
const questionsDir = (0, import_path6.join)(process.cwd(), ".stackwright", "questions");
|
|
4136
|
+
(0, import_fs6.mkdirSync)(questionsDir, { recursive: true });
|
|
4137
|
+
const outPath = (0, import_path6.join)(questionsDir, `${phase}.json`);
|
|
4138
|
+
(0, import_fs6.writeFileSync)(
|
|
3874
4139
|
outPath,
|
|
3875
4140
|
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
3876
4141
|
);
|
|
@@ -3921,8 +4186,8 @@ function registerPipelineTools(server2) {
|
|
|
3921
4186
|
|
|
3922
4187
|
// src/tools/safe-write.ts
|
|
3923
4188
|
var import_zod14 = require("zod");
|
|
3924
|
-
var
|
|
3925
|
-
var
|
|
4189
|
+
var import_fs7 = require("fs");
|
|
4190
|
+
var import_path7 = require("path");
|
|
3926
4191
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
3927
4192
|
"stackwright-pro-designer-otter": [
|
|
3928
4193
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Design language artifact" }
|
|
@@ -3937,8 +4202,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3937
4202
|
],
|
|
3938
4203
|
"stackwright-pro-auth-otter": [
|
|
3939
4204
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
|
|
3940
|
-
{
|
|
3941
|
-
|
|
4205
|
+
{
|
|
4206
|
+
prefix: "stackwright.auth.",
|
|
4207
|
+
suffix: ".yml",
|
|
4208
|
+
description: "Auth config YAML (stackwright.auth.yml \u2014 compiled to _auth.json)"
|
|
4209
|
+
},
|
|
3942
4210
|
{
|
|
3943
4211
|
prefix: ".env",
|
|
3944
4212
|
suffix: "",
|
|
@@ -3957,7 +4225,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3957
4225
|
],
|
|
3958
4226
|
"stackwright-pro-data-otter": [
|
|
3959
4227
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Data config artifact" },
|
|
3960
|
-
{
|
|
4228
|
+
{
|
|
4229
|
+
prefix: "stackwright.collections.",
|
|
4230
|
+
suffix: ".yml",
|
|
4231
|
+
description: "Collections config YAML (stackwright.collections.yml \u2014 compiled to _collections.json)"
|
|
4232
|
+
}
|
|
3961
4233
|
],
|
|
3962
4234
|
"stackwright-pro-page-otter": [
|
|
3963
4235
|
{ prefix: "pages/", suffix: "/content.yml", description: "Page content YAML" },
|
|
@@ -3983,7 +4255,12 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3983
4255
|
}
|
|
3984
4256
|
],
|
|
3985
4257
|
"stackwright-pro-api-otter": [
|
|
3986
|
-
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" }
|
|
4258
|
+
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" },
|
|
4259
|
+
{
|
|
4260
|
+
prefix: "stackwright.integrations.",
|
|
4261
|
+
suffix: ".yml",
|
|
4262
|
+
description: "Integrations config YAML (stackwright.integrations.yml \u2014 compiled to _integrations.json)"
|
|
4263
|
+
}
|
|
3987
4264
|
],
|
|
3988
4265
|
"stackwright-pro-geo-otter": [
|
|
3989
4266
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Geo manifest artifact" },
|
|
@@ -4006,6 +4283,25 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
4006
4283
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Polish artifact" },
|
|
4007
4284
|
{ prefix: "README.md", suffix: "", description: "Project README rewrite" }
|
|
4008
4285
|
],
|
|
4286
|
+
// QA otter writes ONLY to the qa/ directory and the qa-findings artifact.
|
|
4287
|
+
// It never writes .tsx, .ts, .yml, or .json outside these paths.
|
|
4288
|
+
"stackwright-pro-qa-otter": [
|
|
4289
|
+
{
|
|
4290
|
+
prefix: ".stackwright/artifacts/qa-findings.json",
|
|
4291
|
+
suffix: "",
|
|
4292
|
+
description: "QA findings artifact"
|
|
4293
|
+
},
|
|
4294
|
+
{
|
|
4295
|
+
prefix: ".stackwright/qa/",
|
|
4296
|
+
suffix: ".md",
|
|
4297
|
+
description: "QA report markdown"
|
|
4298
|
+
},
|
|
4299
|
+
{
|
|
4300
|
+
prefix: ".stackwright/qa/screenshots/",
|
|
4301
|
+
suffix: ".png",
|
|
4302
|
+
description: "Route screenshots for evidence"
|
|
4303
|
+
}
|
|
4304
|
+
],
|
|
4009
4305
|
// domain-expert-otter is a reader/answerer only — it writes via stackwright_pro_save_phase_answers,
|
|
4010
4306
|
// not via safe_write. Entry required here because the MCP tool availability guard boilerplate
|
|
4011
4307
|
// in its system prompt mentions stackwright_pro_safe_write (triggering the coherence test).
|
|
@@ -4046,7 +4342,7 @@ function getMaxBytesForPath(filePath) {
|
|
|
4046
4342
|
}
|
|
4047
4343
|
var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
|
|
4048
4344
|
function extractPageSlug(filePath) {
|
|
4049
|
-
const match = (0,
|
|
4345
|
+
const match = (0, import_path7.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
|
|
4050
4346
|
return match?.[1] ?? null;
|
|
4051
4347
|
}
|
|
4052
4348
|
function extractContentTypes(yamlContent) {
|
|
@@ -4062,27 +4358,27 @@ function extractContentTypes(yamlContent) {
|
|
|
4062
4358
|
return types.length > 0 ? types : ["unknown"];
|
|
4063
4359
|
}
|
|
4064
4360
|
function readPageRegistry(cwd) {
|
|
4065
|
-
const regPath = (0,
|
|
4066
|
-
if (!(0,
|
|
4361
|
+
const regPath = (0, import_path7.join)(cwd, PAGE_REGISTRY_FILE);
|
|
4362
|
+
if (!(0, import_fs7.existsSync)(regPath)) return {};
|
|
4067
4363
|
try {
|
|
4068
|
-
const stat = (0,
|
|
4364
|
+
const stat = (0, import_fs7.lstatSync)(regPath);
|
|
4069
4365
|
if (stat.isSymbolicLink()) return {};
|
|
4070
|
-
return JSON.parse((0,
|
|
4366
|
+
return JSON.parse((0, import_fs7.readFileSync)(regPath, "utf-8"));
|
|
4071
4367
|
} catch {
|
|
4072
4368
|
return {};
|
|
4073
4369
|
}
|
|
4074
4370
|
}
|
|
4075
4371
|
function writePageRegistry(cwd, registry) {
|
|
4076
|
-
const dir = (0,
|
|
4077
|
-
(0,
|
|
4078
|
-
const regPath = (0,
|
|
4079
|
-
if ((0,
|
|
4080
|
-
const stat = (0,
|
|
4372
|
+
const dir = (0, import_path7.join)(cwd, ".stackwright");
|
|
4373
|
+
(0, import_fs7.mkdirSync)(dir, { recursive: true });
|
|
4374
|
+
const regPath = (0, import_path7.join)(cwd, PAGE_REGISTRY_FILE);
|
|
4375
|
+
if ((0, import_fs7.existsSync)(regPath)) {
|
|
4376
|
+
const stat = (0, import_fs7.lstatSync)(regPath);
|
|
4081
4377
|
if (stat.isSymbolicLink()) {
|
|
4082
4378
|
throw new Error("Refusing to write page registry through symlink");
|
|
4083
4379
|
}
|
|
4084
4380
|
}
|
|
4085
|
-
(0,
|
|
4381
|
+
(0, import_fs7.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
|
|
4086
4382
|
}
|
|
4087
4383
|
function checkPageOwnership(cwd, slug, callerOtter) {
|
|
4088
4384
|
const registry = readPageRegistry(cwd);
|
|
@@ -4097,15 +4393,15 @@ function checkPageOwnership(cwd, slug, callerOtter) {
|
|
|
4097
4393
|
};
|
|
4098
4394
|
}
|
|
4099
4395
|
var NEXTJS_BRACKET_SEGMENT_RE = /\[\[\.{3}[a-zA-Z_][a-zA-Z0-9_]*\]\]|\[\.{3}[a-zA-Z_][a-zA-Z0-9_]*\]|\[[a-zA-Z_][a-zA-Z0-9_]*\]/g;
|
|
4100
|
-
function stripNextjsBracketSegments(
|
|
4101
|
-
return
|
|
4396
|
+
function stripNextjsBracketSegments(path4) {
|
|
4397
|
+
return path4.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
|
|
4102
4398
|
}
|
|
4103
4399
|
function checkPathAllowed(callerOtter, filePath) {
|
|
4104
|
-
const normalized = (0,
|
|
4400
|
+
const normalized = (0, import_path7.normalize)(filePath);
|
|
4105
4401
|
if (stripNextjsBracketSegments(normalized).includes("..")) {
|
|
4106
4402
|
return { allowed: false, error: 'Path traversal detected: ".." segments are not allowed' };
|
|
4107
4403
|
}
|
|
4108
|
-
if ((0,
|
|
4404
|
+
if ((0, import_path7.isAbsolute)(normalized)) {
|
|
4109
4405
|
return {
|
|
4110
4406
|
allowed: false,
|
|
4111
4407
|
error: "Absolute paths are not allowed \u2014 use paths relative to project root"
|
|
@@ -4247,11 +4543,11 @@ function handleSafeWrite(input) {
|
|
|
4247
4543
|
};
|
|
4248
4544
|
return { text: JSON.stringify(result), isError: true };
|
|
4249
4545
|
}
|
|
4250
|
-
const normalized = (0,
|
|
4251
|
-
const fullPath = (0,
|
|
4252
|
-
if ((0,
|
|
4546
|
+
const normalized = (0, import_path7.normalize)(filePath);
|
|
4547
|
+
const fullPath = (0, import_path7.join)(cwd, normalized);
|
|
4548
|
+
if ((0, import_fs7.existsSync)(fullPath)) {
|
|
4253
4549
|
try {
|
|
4254
|
-
const stat = (0,
|
|
4550
|
+
const stat = (0, import_fs7.lstatSync)(fullPath);
|
|
4255
4551
|
if (stat.isSymbolicLink()) {
|
|
4256
4552
|
const result = {
|
|
4257
4553
|
success: false,
|
|
@@ -4292,9 +4588,9 @@ function handleSafeWrite(input) {
|
|
|
4292
4588
|
}
|
|
4293
4589
|
try {
|
|
4294
4590
|
if (createDirectories) {
|
|
4295
|
-
(0,
|
|
4591
|
+
(0, import_fs7.mkdirSync)((0, import_path7.dirname)(fullPath), { recursive: true });
|
|
4296
4592
|
}
|
|
4297
|
-
(0,
|
|
4593
|
+
(0, import_fs7.writeFileSync)(fullPath, content, { encoding: "utf-8" });
|
|
4298
4594
|
if (pageSlug) {
|
|
4299
4595
|
try {
|
|
4300
4596
|
const registry = readPageRegistry(cwd);
|
|
@@ -4354,8 +4650,8 @@ function registerSafeWriteTools(server2) {
|
|
|
4354
4650
|
|
|
4355
4651
|
// src/tools/auth.ts
|
|
4356
4652
|
var import_zod15 = require("zod");
|
|
4357
|
-
var
|
|
4358
|
-
var
|
|
4653
|
+
var import_fs8 = require("fs");
|
|
4654
|
+
var import_path8 = require("path");
|
|
4359
4655
|
function buildHierarchy(roles) {
|
|
4360
4656
|
const h = {};
|
|
4361
4657
|
for (let i = 0; i < roles.length - 1; i++) {
|
|
@@ -4369,7 +4665,7 @@ function hierarchyToYaml(hierarchy, indent) {
|
|
|
4369
4665
|
return entries.map(([role, subs]) => `${indent}${role}: [${subs.join(", ")}]`).join("\n");
|
|
4370
4666
|
}
|
|
4371
4667
|
function rolesToYaml(roles, indent) {
|
|
4372
|
-
return roles.map((r) => `${indent}- ${r}`).join("\n");
|
|
4668
|
+
return roles.map((r) => `${indent}- name: ${r}`).join("\n");
|
|
4373
4669
|
}
|
|
4374
4670
|
function normalizeRoutes(routes, defaultRole) {
|
|
4375
4671
|
return routes.map(
|
|
@@ -4382,9 +4678,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
|
|
|
4382
4678
|
}
|
|
4383
4679
|
function detectNextMajorVersion(cwd) {
|
|
4384
4680
|
try {
|
|
4385
|
-
const pkgPath = (0,
|
|
4386
|
-
if (!(0,
|
|
4387
|
-
const pkg = JSON.parse((0,
|
|
4681
|
+
const pkgPath = (0, import_path8.join)(cwd, "package.json");
|
|
4682
|
+
if (!(0, import_fs8.existsSync)(pkgPath)) return null;
|
|
4683
|
+
const pkg = JSON.parse((0, import_fs8.readFileSync)(pkgPath, "utf8"));
|
|
4388
4684
|
const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
4389
4685
|
if (!nextVersion) return null;
|
|
4390
4686
|
const match = nextVersion.match(/(\d+)/);
|
|
@@ -4393,25 +4689,6 @@ function detectNextMajorVersion(cwd) {
|
|
|
4393
4689
|
return null;
|
|
4394
4690
|
}
|
|
4395
4691
|
}
|
|
4396
|
-
function upsertAuthBlock(existing, authYaml) {
|
|
4397
|
-
const lines = existing.split("\n");
|
|
4398
|
-
let authStart = -1;
|
|
4399
|
-
let authEnd = lines.length;
|
|
4400
|
-
for (let i = 0; i < lines.length; i++) {
|
|
4401
|
-
if (/^auth:/.test(lines[i])) {
|
|
4402
|
-
authStart = i;
|
|
4403
|
-
} else if (authStart >= 0 && i > authStart && /^\S/.test(lines[i]) && lines[i].trim() !== "") {
|
|
4404
|
-
authEnd = i;
|
|
4405
|
-
break;
|
|
4406
|
-
}
|
|
4407
|
-
}
|
|
4408
|
-
if (authStart < 0) {
|
|
4409
|
-
return existing.trimEnd() + "\n" + authYaml + "\n";
|
|
4410
|
-
}
|
|
4411
|
-
const before = lines.slice(0, authStart);
|
|
4412
|
-
const after = lines.slice(authEnd);
|
|
4413
|
-
return [...before, ...authYaml.trimEnd().split("\n"), ...after.length ? after : []].join("\n");
|
|
4414
|
-
}
|
|
4415
4692
|
function generateMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4416
4693
|
const rbacBlock = ` rbac: {
|
|
4417
4694
|
roles: ${JSON.stringify(roles)},
|
|
@@ -4611,33 +4888,33 @@ OAUTH2_CLIENT_SECRET=your-client-secret
|
|
|
4611
4888
|
`;
|
|
4612
4889
|
}
|
|
4613
4890
|
function generateYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
|
|
4614
|
-
const rbacSection = `
|
|
4615
|
-
|
|
4616
|
-
${rolesToYaml(roles, "
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
${hierarchyToYaml(hierarchy, "
|
|
4620
|
-
const auditSection = `
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
const routeLines = routesToYaml(protectedRoutes, "
|
|
4624
|
-
const providerLine = params.provider ? `
|
|
4891
|
+
const rbacSection = `rbac:
|
|
4892
|
+
roles:
|
|
4893
|
+
${rolesToYaml(roles, " ")}
|
|
4894
|
+
defaultRole: ${defaultRole}
|
|
4895
|
+
hierarchy:
|
|
4896
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
4897
|
+
const auditSection = `audit:
|
|
4898
|
+
enabled: ${auditEnabled}
|
|
4899
|
+
retentionDays: ${auditRetentionDays}`;
|
|
4900
|
+
const routeLines = routesToYaml(protectedRoutes, " ");
|
|
4901
|
+
const providerLine = params.provider ? `provider: ${params.provider}
|
|
4625
4902
|
` : "";
|
|
4626
4903
|
if (method === "cac") {
|
|
4627
4904
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4628
4905
|
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4629
4906
|
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4630
4907
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4631
|
-
return
|
|
4632
|
-
|
|
4633
|
-
${providerLine}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4908
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4909
|
+
method: cac
|
|
4910
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4911
|
+
cac:
|
|
4912
|
+
caBundle: \${CAC_CA_BUNDLE}
|
|
4913
|
+
edipiLookup: ${edipiLookup}
|
|
4914
|
+
ocspEndpoint: \${CAC_OCSP_ENDPOINT}
|
|
4915
|
+
certHeader: ${certHeader}
|
|
4639
4916
|
${rbacSection}
|
|
4640
|
-
|
|
4917
|
+
protectedRoutes:
|
|
4641
4918
|
${routeLines}
|
|
4642
4919
|
${auditSection}
|
|
4643
4920
|
`;
|
|
@@ -4645,33 +4922,33 @@ ${auditSection}
|
|
|
4645
4922
|
if (method === "oidc") {
|
|
4646
4923
|
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4647
4924
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4648
|
-
return
|
|
4649
|
-
|
|
4650
|
-
${providerLine}
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4925
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4926
|
+
method: oidc
|
|
4927
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4928
|
+
oidc:
|
|
4929
|
+
discoveryUrl: \${OIDC_DISCOVERY_URL}
|
|
4930
|
+
clientId: \${OIDC_CLIENT_ID}
|
|
4931
|
+
clientSecret: \${OIDC_CLIENT_SECRET}
|
|
4932
|
+
scopes: ${scopes2}
|
|
4933
|
+
roleClaim: ${roleClaim}
|
|
4657
4934
|
${rbacSection}
|
|
4658
|
-
|
|
4935
|
+
protectedRoutes:
|
|
4659
4936
|
${routeLines}
|
|
4660
4937
|
${auditSection}
|
|
4661
4938
|
`;
|
|
4662
4939
|
}
|
|
4663
4940
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4664
|
-
return
|
|
4665
|
-
|
|
4666
|
-
${providerLine}
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4941
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4942
|
+
method: oauth2
|
|
4943
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4944
|
+
oauth2:
|
|
4945
|
+
authorizationUrl: \${OAUTH2_AUTH_URL}
|
|
4946
|
+
tokenUrl: \${OAUTH2_TOKEN_URL}
|
|
4947
|
+
clientId: \${OAUTH2_CLIENT_ID}
|
|
4948
|
+
clientSecret: \${OAUTH2_CLIENT_SECRET}
|
|
4949
|
+
scopes: ${scopes}
|
|
4673
4950
|
${rbacSection}
|
|
4674
|
-
|
|
4951
|
+
protectedRoutes:
|
|
4675
4952
|
${routeLines}
|
|
4676
4953
|
${auditSection}
|
|
4677
4954
|
`;
|
|
@@ -4857,35 +5134,35 @@ ${configBlock}
|
|
|
4857
5134
|
`;
|
|
4858
5135
|
}
|
|
4859
5136
|
function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
|
|
4860
|
-
const rbacSection = `
|
|
4861
|
-
|
|
4862
|
-
${rolesToYaml(roles, "
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
${hierarchyToYaml(hierarchy, "
|
|
4866
|
-
const auditSection = `
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
const routeLines = protectedRoutes.map((r) => `
|
|
4870
|
-
|
|
4871
|
-
const providerLine = params.provider ? `
|
|
5137
|
+
const rbacSection = `rbac:
|
|
5138
|
+
roles:
|
|
5139
|
+
${rolesToYaml(roles, " ")}
|
|
5140
|
+
defaultRole: ${defaultRole}
|
|
5141
|
+
hierarchy:
|
|
5142
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
5143
|
+
const auditSection = `audit:
|
|
5144
|
+
enabled: ${auditEnabled}
|
|
5145
|
+
retentionDays: ${auditRetentionDays}`;
|
|
5146
|
+
const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
|
|
5147
|
+
requiredRole: ${r.requiredRole}`).join("\n");
|
|
5148
|
+
const providerLine = params.provider ? `provider: ${params.provider}
|
|
4872
5149
|
` : "";
|
|
4873
5150
|
if (method === "cac") {
|
|
4874
5151
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4875
5152
|
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4876
5153
|
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4877
5154
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4878
|
-
return
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
${providerLine}
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
5155
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5156
|
+
method: cac
|
|
5157
|
+
devOnly: true
|
|
5158
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5159
|
+
cac:
|
|
5160
|
+
caBundle: ${caBundle}
|
|
5161
|
+
edipiLookup: ${edipiLookup}
|
|
5162
|
+
ocspEndpoint: ${ocspEndpoint}
|
|
5163
|
+
certHeader: ${certHeader}
|
|
4887
5164
|
${rbacSection}
|
|
4888
|
-
|
|
5165
|
+
protectedRoutes:
|
|
4889
5166
|
${routeLines}
|
|
4890
5167
|
${auditSection}
|
|
4891
5168
|
`;
|
|
@@ -4893,35 +5170,35 @@ ${auditSection}
|
|
|
4893
5170
|
if (method === "oidc") {
|
|
4894
5171
|
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4895
5172
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4896
|
-
return
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
${providerLine}
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
5173
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5174
|
+
method: oidc
|
|
5175
|
+
devOnly: true
|
|
5176
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5177
|
+
oidc:
|
|
5178
|
+
discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
|
|
5179
|
+
clientId: dev-mock-client
|
|
5180
|
+
clientSecret: dev-mock-secret
|
|
5181
|
+
scopes: ${scopes2}
|
|
5182
|
+
roleClaim: ${roleClaim}
|
|
4906
5183
|
${rbacSection}
|
|
4907
|
-
|
|
5184
|
+
protectedRoutes:
|
|
4908
5185
|
${routeLines}
|
|
4909
5186
|
${auditSection}
|
|
4910
5187
|
`;
|
|
4911
5188
|
}
|
|
4912
5189
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4913
|
-
return
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
${providerLine}
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
5190
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5191
|
+
method: oauth2
|
|
5192
|
+
devOnly: true
|
|
5193
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5194
|
+
oauth2:
|
|
5195
|
+
authorizationUrl: https://dev-mock-oauth2/authorize
|
|
5196
|
+
tokenUrl: https://dev-mock-oauth2/token
|
|
5197
|
+
clientId: dev-mock-client
|
|
5198
|
+
clientSecret: dev-mock-secret
|
|
5199
|
+
scopes: ${scopes}
|
|
4923
5200
|
${rbacSection}
|
|
4924
|
-
|
|
5201
|
+
protectedRoutes:
|
|
4925
5202
|
${routeLines}
|
|
4926
5203
|
${auditSection}
|
|
4927
5204
|
`;
|
|
@@ -5066,7 +5343,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5066
5343
|
auditRetentionDays,
|
|
5067
5344
|
normalizedRoutes
|
|
5068
5345
|
);
|
|
5069
|
-
(0,
|
|
5346
|
+
(0, import_fs8.writeFileSync)((0, import_path8.join)(cwd, conventionFile), authFileContent, "utf8");
|
|
5070
5347
|
filesWritten.push(conventionFile);
|
|
5071
5348
|
} catch (err) {
|
|
5072
5349
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5086,12 +5363,12 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5086
5363
|
if (!devOnly) {
|
|
5087
5364
|
try {
|
|
5088
5365
|
const envBlock = generateEnvBlock(effectiveMethod, params);
|
|
5089
|
-
const envPath = (0,
|
|
5090
|
-
if ((0,
|
|
5091
|
-
const existing = (0,
|
|
5092
|
-
(0,
|
|
5366
|
+
const envPath = (0, import_path8.join)(cwd, ".env.example");
|
|
5367
|
+
if ((0, import_fs8.existsSync)(envPath)) {
|
|
5368
|
+
const existing = (0, import_fs8.readFileSync)(envPath, "utf8");
|
|
5369
|
+
(0, import_fs8.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
5093
5370
|
} else {
|
|
5094
|
-
(0,
|
|
5371
|
+
(0, import_fs8.writeFileSync)(envPath, envBlock, "utf8");
|
|
5095
5372
|
}
|
|
5096
5373
|
filesWritten.push(".env.example");
|
|
5097
5374
|
} catch (err) {
|
|
@@ -5109,10 +5386,10 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5109
5386
|
}
|
|
5110
5387
|
if (devOnly) {
|
|
5111
5388
|
try {
|
|
5112
|
-
const mockAuthDir = (0,
|
|
5113
|
-
(0,
|
|
5389
|
+
const mockAuthDir = (0, import_path8.join)(cwd, "lib");
|
|
5390
|
+
(0, import_fs8.mkdirSync)(mockAuthDir, { recursive: true });
|
|
5114
5391
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
5115
|
-
(0,
|
|
5392
|
+
(0, import_fs8.writeFileSync)((0, import_path8.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
5116
5393
|
filesWritten.push("lib/mock-auth.ts");
|
|
5117
5394
|
} catch (err) {
|
|
5118
5395
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5130,11 +5407,11 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5130
5407
|
};
|
|
5131
5408
|
}
|
|
5132
5409
|
try {
|
|
5133
|
-
const pkgPath = (0,
|
|
5134
|
-
if ((0,
|
|
5135
|
-
const existingPkg = (0,
|
|
5410
|
+
const pkgPath = (0, import_path8.join)(cwd, "package.json");
|
|
5411
|
+
if ((0, import_fs8.existsSync)(pkgPath)) {
|
|
5412
|
+
const existingPkg = (0, import_fs8.readFileSync)(pkgPath, "utf8");
|
|
5136
5413
|
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
5137
|
-
(0,
|
|
5414
|
+
(0, import_fs8.writeFileSync)(pkgPath, updatedPkg, "utf8");
|
|
5138
5415
|
filesWritten.push("package.json");
|
|
5139
5416
|
}
|
|
5140
5417
|
} catch (err) {
|
|
@@ -5175,21 +5452,18 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5175
5452
|
normalizedRoutes,
|
|
5176
5453
|
useProxy
|
|
5177
5454
|
);
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
(0, import_fs7.writeFileSync)(ymlPath, authYaml, "utf8");
|
|
5181
|
-
} else {
|
|
5182
|
-
const existing = (0, import_fs7.readFileSync)(ymlPath, "utf8");
|
|
5183
|
-
(0, import_fs7.writeFileSync)(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
|
|
5184
|
-
}
|
|
5185
|
-
filesWritten.push("stackwright.yml");
|
|
5455
|
+
(0, import_fs8.writeFileSync)((0, import_path8.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
|
|
5456
|
+
filesWritten.push("stackwright.auth.yml");
|
|
5186
5457
|
} catch (err) {
|
|
5187
5458
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5188
5459
|
return {
|
|
5189
5460
|
content: [
|
|
5190
5461
|
{
|
|
5191
5462
|
type: "text",
|
|
5192
|
-
text: JSON.stringify({
|
|
5463
|
+
text: JSON.stringify({
|
|
5464
|
+
success: false,
|
|
5465
|
+
error: `Failed writing stackwright.auth.yml: ${msg}`
|
|
5466
|
+
})
|
|
5193
5467
|
}
|
|
5194
5468
|
],
|
|
5195
5469
|
isError: true
|
|
@@ -5234,7 +5508,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5234
5508
|
function registerAuthTools(server2) {
|
|
5235
5509
|
server2.tool(
|
|
5236
5510
|
"stackwright_pro_configure_auth",
|
|
5237
|
-
"Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` from a secure template,
|
|
5511
|
+
"Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` (or `proxy.ts` for Next.js >=16) from a secure template, writes a standalone `stackwright.auth.yml` sibling file, and generates `.env.example` with required environment variables. \u26A0\uFE0F For CAC/PKI: generated `middleware.ts` carries a SECURITY REVIEW REQUIRED comment \u2014 certificate chain validation must be verified by a DoD security officer before production deployment. This is the ONLY approved path to generating `middleware.ts`. Never write TypeScript auth files directly.",
|
|
5238
5512
|
{
|
|
5239
5513
|
method: import_zod15.z.enum(["cac", "oidc", "oauth2", "none"]),
|
|
5240
5514
|
provider: import_zod15.z.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
|
|
@@ -5285,28 +5559,28 @@ function registerAuthTools(server2) {
|
|
|
5285
5559
|
|
|
5286
5560
|
// src/integrity.ts
|
|
5287
5561
|
var import_crypto4 = require("crypto");
|
|
5288
|
-
var
|
|
5289
|
-
var
|
|
5562
|
+
var import_fs9 = require("fs");
|
|
5563
|
+
var import_path9 = require("path");
|
|
5290
5564
|
var _checksums = /* @__PURE__ */ new Map([
|
|
5291
5565
|
[
|
|
5292
5566
|
"stackwright-pro-api-otter.json",
|
|
5293
|
-
"
|
|
5567
|
+
"822b35d7a330ed8ac0b42a63b0f70a41885aa9b5ea23cc5b8b998b549da4c05c"
|
|
5294
5568
|
],
|
|
5295
5569
|
[
|
|
5296
5570
|
"stackwright-pro-auth-otter.json",
|
|
5297
|
-
"
|
|
5571
|
+
"d6cd5732667018d99456be77d5955e454da7a0999a0330b5f03a483aa1b9b33f"
|
|
5298
5572
|
],
|
|
5299
5573
|
[
|
|
5300
5574
|
"stackwright-pro-dashboard-otter.json",
|
|
5301
|
-
"
|
|
5575
|
+
"19268b1ab423bfbb628ebfbc9f6767d5c0bfedd03963c6d445a2724a8bb78f9c"
|
|
5302
5576
|
],
|
|
5303
5577
|
[
|
|
5304
5578
|
"stackwright-pro-data-otter.json",
|
|
5305
|
-
"
|
|
5579
|
+
"bb66eaab31c6872536dca4d3ff145724a46356946dd0665cc4f0346714d3bacb"
|
|
5306
5580
|
],
|
|
5307
5581
|
[
|
|
5308
5582
|
"stackwright-pro-designer-otter.json",
|
|
5309
|
-
"
|
|
5583
|
+
"b54121c6a2ab5b1ad68c1262c58b2e04e6315feacd6bb8de8e78eb36f2fa6be6"
|
|
5310
5584
|
],
|
|
5311
5585
|
[
|
|
5312
5586
|
"stackwright-pro-domain-expert-otter.json",
|
|
@@ -5314,35 +5588,39 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
5314
5588
|
],
|
|
5315
5589
|
[
|
|
5316
5590
|
"stackwright-pro-foreman-otter.json",
|
|
5317
|
-
"
|
|
5591
|
+
"abb1cc5e40a8c5ff98057273f43a9e90e2791a15951090cd4d98407c0c3618e3"
|
|
5318
5592
|
],
|
|
5319
5593
|
[
|
|
5320
5594
|
"stackwright-pro-form-wizard-otter.json",
|
|
5321
|
-
"
|
|
5595
|
+
"975ad68e8fb6fe5a10373be278946fa4d9d7ec2238a0b2bd9e4a412e5b1fdd6d"
|
|
5322
5596
|
],
|
|
5323
5597
|
[
|
|
5324
5598
|
"stackwright-pro-geo-otter.json",
|
|
5325
|
-
"
|
|
5599
|
+
"eef152e5b58947c8eb1ffec2c37acffc39f84b61be4c6cb827310f052221935b"
|
|
5326
5600
|
],
|
|
5327
5601
|
[
|
|
5328
5602
|
"stackwright-pro-page-otter.json",
|
|
5329
|
-
"
|
|
5603
|
+
"ddfa497a4d4980080fa2918aec4d6dfd78d5c28ec6426b4b52f4f26fccaddabb"
|
|
5330
5604
|
],
|
|
5331
5605
|
[
|
|
5332
5606
|
"stackwright-pro-polish-otter.json",
|
|
5333
|
-
"
|
|
5607
|
+
"5e6532c1fe0737ed83b1f46dd55642e1076adb6ef23d4de636523eb3d88d3087"
|
|
5608
|
+
],
|
|
5609
|
+
[
|
|
5610
|
+
"stackwright-pro-qa-otter.json",
|
|
5611
|
+
"ecb1e76170723fce43f515044e304d9da32253dadecbf29bf08c90bb8f375f77"
|
|
5334
5612
|
],
|
|
5335
5613
|
[
|
|
5336
5614
|
"stackwright-pro-scaffold-otter.json",
|
|
5337
|
-
"
|
|
5615
|
+
"96ac7754b54c15732206cd4d8043b558d0c465757a0bc70fae6b498d6f02f22a"
|
|
5338
5616
|
],
|
|
5339
5617
|
[
|
|
5340
5618
|
"stackwright-pro-theme-otter.json",
|
|
5341
|
-
"
|
|
5619
|
+
"fb62e56642f7f94c50ce173f3e1ce978b3f20ab1567e87403b901d6dd991a7db"
|
|
5342
5620
|
],
|
|
5343
5621
|
[
|
|
5344
5622
|
"stackwright-services-otter.json",
|
|
5345
|
-
"
|
|
5623
|
+
"0a5dac670482871c718099767b30bf23d8ec57e942bd40a00ce295926d82c6bd"
|
|
5346
5624
|
]
|
|
5347
5625
|
]);
|
|
5348
5626
|
Object.freeze(_checksums);
|
|
@@ -5355,6 +5633,7 @@ for (const [name, digest] of CANONICAL_CHECKSUMS) {
|
|
|
5355
5633
|
);
|
|
5356
5634
|
}
|
|
5357
5635
|
}
|
|
5636
|
+
var CANONICAL_OTTERS = Object.freeze([...CANONICAL_CHECKSUMS.keys()]);
|
|
5358
5637
|
var MAX_OTTER_BYTES = 1 * 1024 * 1024;
|
|
5359
5638
|
function computeSha256(data) {
|
|
5360
5639
|
return (0, import_crypto4.createHash)("sha256").update(data).digest("hex");
|
|
@@ -5364,14 +5643,14 @@ function safeEqual(a, b) {
|
|
|
5364
5643
|
return (0, import_crypto4.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
|
|
5365
5644
|
}
|
|
5366
5645
|
function verifyOtterFile(filePath) {
|
|
5367
|
-
const filename = (0,
|
|
5646
|
+
const filename = (0, import_path9.basename)(filePath);
|
|
5368
5647
|
const expected = CANONICAL_CHECKSUMS.get(filename);
|
|
5369
5648
|
if (expected === void 0) {
|
|
5370
5649
|
return { verified: false, filename, error: `Unknown otter file: not in canonical set` };
|
|
5371
5650
|
}
|
|
5372
5651
|
let stat;
|
|
5373
5652
|
try {
|
|
5374
|
-
stat = (0,
|
|
5653
|
+
stat = (0, import_fs9.lstatSync)(filePath);
|
|
5375
5654
|
} catch (err) {
|
|
5376
5655
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5377
5656
|
return { verified: false, filename, error: `Cannot stat file: ${msg}` };
|
|
@@ -5389,7 +5668,7 @@ function verifyOtterFile(filePath) {
|
|
|
5389
5668
|
}
|
|
5390
5669
|
let raw;
|
|
5391
5670
|
try {
|
|
5392
|
-
raw = (0,
|
|
5671
|
+
raw = (0, import_fs9.readFileSync)(filePath);
|
|
5393
5672
|
} catch (err) {
|
|
5394
5673
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5395
5674
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -5439,7 +5718,7 @@ function verifyAllOtters(otterDir) {
|
|
|
5439
5718
|
const unknown = [];
|
|
5440
5719
|
let entries;
|
|
5441
5720
|
try {
|
|
5442
|
-
entries = (0,
|
|
5721
|
+
entries = (0, import_fs9.readdirSync)(otterDir);
|
|
5443
5722
|
} catch (err) {
|
|
5444
5723
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5445
5724
|
return {
|
|
@@ -5450,9 +5729,9 @@ function verifyAllOtters(otterDir) {
|
|
|
5450
5729
|
}
|
|
5451
5730
|
const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
|
|
5452
5731
|
for (const filename of otterFiles) {
|
|
5453
|
-
const filePath = (0,
|
|
5732
|
+
const filePath = (0, import_path9.join)(otterDir, filename);
|
|
5454
5733
|
try {
|
|
5455
|
-
if ((0,
|
|
5734
|
+
if ((0, import_fs9.lstatSync)(filePath).isSymbolicLink()) {
|
|
5456
5735
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
5457
5736
|
continue;
|
|
5458
5737
|
}
|
|
@@ -5478,9 +5757,9 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
|
|
|
5478
5757
|
function resolveOtterDir() {
|
|
5479
5758
|
const cwd = process.cwd();
|
|
5480
5759
|
for (const relative of DEFAULT_SEARCH_PATHS) {
|
|
5481
|
-
const candidate = (0,
|
|
5760
|
+
const candidate = (0, import_path9.join)(cwd, relative);
|
|
5482
5761
|
try {
|
|
5483
|
-
(0,
|
|
5762
|
+
(0, import_fs9.lstatSync)(candidate);
|
|
5484
5763
|
return candidate;
|
|
5485
5764
|
} catch {
|
|
5486
5765
|
}
|
|
@@ -5559,13 +5838,13 @@ function registerIntegrityTools(server2) {
|
|
|
5559
5838
|
|
|
5560
5839
|
// src/tools/domain.ts
|
|
5561
5840
|
var import_zod16 = require("zod");
|
|
5562
|
-
var
|
|
5563
|
-
var
|
|
5841
|
+
var import_fs10 = require("fs");
|
|
5842
|
+
var import_path10 = require("path");
|
|
5564
5843
|
function handleListCollections(input) {
|
|
5565
5844
|
const cwd = input._cwd ?? process.cwd();
|
|
5566
5845
|
const sources = [
|
|
5567
5846
|
{
|
|
5568
|
-
path: (0,
|
|
5847
|
+
path: (0, import_path10.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
|
|
5569
5848
|
source: "data-config.json",
|
|
5570
5849
|
parse: (raw) => {
|
|
5571
5850
|
const parsed = JSON.parse(raw);
|
|
@@ -5576,15 +5855,15 @@ function handleListCollections(input) {
|
|
|
5576
5855
|
}
|
|
5577
5856
|
},
|
|
5578
5857
|
{
|
|
5579
|
-
path: (0,
|
|
5858
|
+
path: (0, import_path10.join)(cwd, "stackwright.yml"),
|
|
5580
5859
|
source: "stackwright.yml",
|
|
5581
5860
|
parse: extractCollectionsFromYaml
|
|
5582
5861
|
}
|
|
5583
5862
|
];
|
|
5584
|
-
for (const { path:
|
|
5585
|
-
if (!(0,
|
|
5863
|
+
for (const { path: path4, source, parse } of sources) {
|
|
5864
|
+
if (!(0, import_fs10.existsSync)(path4)) continue;
|
|
5586
5865
|
try {
|
|
5587
|
-
const collections = parse((0,
|
|
5866
|
+
const collections = parse((0, import_fs10.readFileSync)(path4, "utf8"));
|
|
5588
5867
|
return {
|
|
5589
5868
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
5590
5869
|
isError: false
|
|
@@ -5735,8 +6014,8 @@ function handleValidateWorkflow(input) {
|
|
|
5735
6014
|
if (input.workflow && Object.keys(input.workflow).length > 0) {
|
|
5736
6015
|
raw = input.workflow;
|
|
5737
6016
|
} else {
|
|
5738
|
-
const artifactPath = (0,
|
|
5739
|
-
if (!(0,
|
|
6017
|
+
const artifactPath = (0, import_path10.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
6018
|
+
if (!(0, import_fs10.existsSync)(artifactPath)) {
|
|
5740
6019
|
return fail([
|
|
5741
6020
|
{
|
|
5742
6021
|
code: "NO_WORKFLOW",
|
|
@@ -5745,7 +6024,7 @@ function handleValidateWorkflow(input) {
|
|
|
5745
6024
|
]);
|
|
5746
6025
|
}
|
|
5747
6026
|
try {
|
|
5748
|
-
raw = JSON.parse((0,
|
|
6027
|
+
raw = JSON.parse((0, import_fs10.readFileSync)(artifactPath, "utf8"));
|
|
5749
6028
|
} catch (err) {
|
|
5750
6029
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
5751
6030
|
}
|
|
@@ -5857,12 +6136,12 @@ function handleValidateWorkflow(input) {
|
|
|
5857
6136
|
return { text: JSON.stringify({ valid: errors.length === 0, errors, warnings }), isError: false };
|
|
5858
6137
|
}
|
|
5859
6138
|
function validateTransitionTargets(step, stepId, stepIds, errors) {
|
|
5860
|
-
const check = (target,
|
|
6139
|
+
const check = (target, path4) => {
|
|
5861
6140
|
if (typeof target === "string" && !stepIds.has(target)) {
|
|
5862
6141
|
errors.push({
|
|
5863
6142
|
code: "ORPHANED_TRANSITION",
|
|
5864
6143
|
message: `Step "${stepId}" transitions to "${target}" which does not exist`,
|
|
5865
|
-
path:
|
|
6144
|
+
path: path4
|
|
5866
6145
|
});
|
|
5867
6146
|
}
|
|
5868
6147
|
};
|
|
@@ -5901,11 +6180,11 @@ function validateTransitionTargets(step, stepId, stepIds, errors) {
|
|
|
5901
6180
|
}
|
|
5902
6181
|
function collectServiceWarnings(step, stepId, warnings) {
|
|
5903
6182
|
const isService = (val) => typeof val === "string" && val.startsWith("service:");
|
|
5904
|
-
const warn = (
|
|
6183
|
+
const warn = (path4) => {
|
|
5905
6184
|
warnings.push({
|
|
5906
6185
|
code: "WARN_SERVICE_REFERENCE",
|
|
5907
|
-
message: `service: reference at "${
|
|
5908
|
-
path:
|
|
6186
|
+
message: `service: reference at "${path4}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
|
|
6187
|
+
path: path4
|
|
5909
6188
|
});
|
|
5910
6189
|
};
|
|
5911
6190
|
const onSubmit = step.on_submit;
|
|
@@ -6053,8 +6332,8 @@ function registerTypeSchemasTool(server2) {
|
|
|
6053
6332
|
}
|
|
6054
6333
|
|
|
6055
6334
|
// src/tools/scaffold-cleanup.ts
|
|
6056
|
-
var
|
|
6057
|
-
var
|
|
6335
|
+
var import_fs11 = require("fs");
|
|
6336
|
+
var import_path11 = require("path");
|
|
6058
6337
|
var SCAFFOLD_FILES_TO_DELETE = [
|
|
6059
6338
|
"content/posts/getting-started.yaml",
|
|
6060
6339
|
"content/posts/hello-world.yaml"
|
|
@@ -6079,12 +6358,12 @@ var FALLBACK_COLORS = {
|
|
|
6079
6358
|
mutedForeground: "#737373"
|
|
6080
6359
|
};
|
|
6081
6360
|
function readThemeColors(cwd) {
|
|
6082
|
-
const tokenPath = (0,
|
|
6083
|
-
if (!(0,
|
|
6084
|
-
const stat = (0,
|
|
6361
|
+
const tokenPath = (0, import_path11.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
6362
|
+
if (!(0, import_fs11.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
|
|
6363
|
+
const stat = (0, import_fs11.lstatSync)(tokenPath);
|
|
6085
6364
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
6086
6365
|
try {
|
|
6087
|
-
const raw = JSON.parse((0,
|
|
6366
|
+
const raw = JSON.parse((0, import_fs11.readFileSync)(tokenPath, "utf-8"));
|
|
6088
6367
|
const css = raw?.cssVariables ?? {};
|
|
6089
6368
|
const toHsl = (hslValue) => {
|
|
6090
6369
|
if (!hslValue || typeof hslValue !== "string") return null;
|
|
@@ -6144,15 +6423,15 @@ function generateNotFoundPage(colors) {
|
|
|
6144
6423
|
`;
|
|
6145
6424
|
}
|
|
6146
6425
|
function isSafePath(fullPath) {
|
|
6147
|
-
if (!(0,
|
|
6148
|
-
const stat = (0,
|
|
6426
|
+
if (!(0, import_fs11.existsSync)(fullPath)) return true;
|
|
6427
|
+
const stat = (0, import_fs11.lstatSync)(fullPath);
|
|
6149
6428
|
return !stat.isSymbolicLink();
|
|
6150
6429
|
}
|
|
6151
6430
|
function isDirEmpty(dirPath) {
|
|
6152
|
-
if (!(0,
|
|
6153
|
-
const stat = (0,
|
|
6431
|
+
if (!(0, import_fs11.existsSync)(dirPath)) return false;
|
|
6432
|
+
const stat = (0, import_fs11.lstatSync)(dirPath);
|
|
6154
6433
|
if (!stat.isDirectory()) return false;
|
|
6155
|
-
return (0,
|
|
6434
|
+
return (0, import_fs11.readdirSync)(dirPath).length === 0;
|
|
6156
6435
|
}
|
|
6157
6436
|
function handleCleanupScaffold(_cwd) {
|
|
6158
6437
|
const cwd = _cwd ?? process.cwd();
|
|
@@ -6165,8 +6444,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6165
6444
|
cleanupWarnings: []
|
|
6166
6445
|
};
|
|
6167
6446
|
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
6168
|
-
const fullPath = (0,
|
|
6169
|
-
if (!(0,
|
|
6447
|
+
const fullPath = (0, import_path11.join)(cwd, relPath);
|
|
6448
|
+
if (!(0, import_fs11.existsSync)(fullPath)) {
|
|
6170
6449
|
result.skipped.push(relPath);
|
|
6171
6450
|
continue;
|
|
6172
6451
|
}
|
|
@@ -6175,7 +6454,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6175
6454
|
continue;
|
|
6176
6455
|
}
|
|
6177
6456
|
try {
|
|
6178
|
-
(0,
|
|
6457
|
+
(0, import_fs11.unlinkSync)(fullPath);
|
|
6179
6458
|
result.deleted.push(relPath);
|
|
6180
6459
|
} catch (err) {
|
|
6181
6460
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -6183,19 +6462,19 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6183
6462
|
}
|
|
6184
6463
|
{
|
|
6185
6464
|
const relPath = "pages/getting-started/content.yml";
|
|
6186
|
-
const fullPath = (0,
|
|
6187
|
-
if (!(0,
|
|
6465
|
+
const fullPath = (0, import_path11.join)(cwd, relPath);
|
|
6466
|
+
if (!(0, import_fs11.existsSync)(fullPath)) {
|
|
6188
6467
|
result.skipped.push(relPath);
|
|
6189
6468
|
} else if (!isSafePath(fullPath)) {
|
|
6190
6469
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
6191
6470
|
} else {
|
|
6192
|
-
const content = (0,
|
|
6471
|
+
const content = (0, import_fs11.readFileSync)(fullPath, "utf-8");
|
|
6193
6472
|
const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
|
|
6194
6473
|
(marker) => content.includes(marker)
|
|
6195
6474
|
);
|
|
6196
6475
|
if (isScaffoldDefault) {
|
|
6197
6476
|
try {
|
|
6198
|
-
(0,
|
|
6477
|
+
(0, import_fs11.unlinkSync)(fullPath);
|
|
6199
6478
|
result.deleted.push(relPath);
|
|
6200
6479
|
} catch (err) {
|
|
6201
6480
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -6209,21 +6488,21 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6209
6488
|
}
|
|
6210
6489
|
{
|
|
6211
6490
|
const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
|
|
6212
|
-
const fullPath = (0,
|
|
6213
|
-
const postsDir = (0,
|
|
6214
|
-
if (!(0,
|
|
6491
|
+
const fullPath = (0, import_path11.join)(cwd, relPath);
|
|
6492
|
+
const postsDir = (0, import_path11.join)(cwd, "content/posts");
|
|
6493
|
+
if (!(0, import_fs11.existsSync)(fullPath)) {
|
|
6215
6494
|
result.skipped.push(relPath);
|
|
6216
6495
|
} else if (!isSafePath(fullPath)) {
|
|
6217
6496
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
6218
|
-
} else if (!(0,
|
|
6497
|
+
} else if (!(0, import_fs11.existsSync)(postsDir)) {
|
|
6219
6498
|
result.skipped.push(relPath);
|
|
6220
6499
|
} else {
|
|
6221
|
-
const userPostFiles = (0,
|
|
6500
|
+
const userPostFiles = (0, import_fs11.readdirSync)(postsDir).filter(
|
|
6222
6501
|
(f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
6223
6502
|
);
|
|
6224
6503
|
if (userPostFiles.length === 0) {
|
|
6225
6504
|
try {
|
|
6226
|
-
(0,
|
|
6505
|
+
(0, import_fs11.unlinkSync)(fullPath);
|
|
6227
6506
|
result.deleted.push(relPath);
|
|
6228
6507
|
} catch (err) {
|
|
6229
6508
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -6236,15 +6515,15 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6236
6515
|
}
|
|
6237
6516
|
}
|
|
6238
6517
|
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
6239
|
-
const fullDir = (0,
|
|
6240
|
-
if (!(0,
|
|
6518
|
+
const fullDir = (0, import_path11.join)(cwd, relDir);
|
|
6519
|
+
if (!(0, import_fs11.existsSync)(fullDir)) continue;
|
|
6241
6520
|
if (!isSafePath(fullDir)) {
|
|
6242
6521
|
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
6243
6522
|
continue;
|
|
6244
6523
|
}
|
|
6245
6524
|
if (isDirEmpty(fullDir)) {
|
|
6246
6525
|
try {
|
|
6247
|
-
(0,
|
|
6526
|
+
(0, import_fs11.rmdirSync)(fullDir);
|
|
6248
6527
|
result.prunedDirs.push(relDir);
|
|
6249
6528
|
} catch (err) {
|
|
6250
6529
|
result.errors.push(`Failed to prune directory ${relDir}: ${String(err)}`);
|
|
@@ -6252,8 +6531,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6252
6531
|
}
|
|
6253
6532
|
}
|
|
6254
6533
|
{
|
|
6255
|
-
const fullPath = (0,
|
|
6256
|
-
if (!(0,
|
|
6534
|
+
const fullPath = (0, import_path11.join)(cwd, NOT_FOUND_PATH);
|
|
6535
|
+
if (!(0, import_fs11.existsSync)(fullPath)) {
|
|
6257
6536
|
result.skipped.push(NOT_FOUND_PATH);
|
|
6258
6537
|
} else if (!isSafePath(fullPath)) {
|
|
6259
6538
|
result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
|
|
@@ -6261,9 +6540,9 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6261
6540
|
try {
|
|
6262
6541
|
const colors = readThemeColors(cwd);
|
|
6263
6542
|
const content = generateNotFoundPage(colors);
|
|
6264
|
-
const dir = (0,
|
|
6265
|
-
(0,
|
|
6266
|
-
(0,
|
|
6543
|
+
const dir = (0, import_path11.dirname)(fullPath);
|
|
6544
|
+
(0, import_fs11.mkdirSync)(dir, { recursive: true });
|
|
6545
|
+
(0, import_fs11.writeFileSync)(fullPath, content, "utf-8");
|
|
6267
6546
|
result.rewritten.push(NOT_FOUND_PATH);
|
|
6268
6547
|
} catch (err) {
|
|
6269
6548
|
result.errors.push(`Failed to rewrite ${NOT_FOUND_PATH}: ${String(err)}`);
|
|
@@ -6530,12 +6809,258 @@ function registerContrastTools(server2) {
|
|
|
6530
6809
|
);
|
|
6531
6810
|
}
|
|
6532
6811
|
|
|
6812
|
+
// src/tools/compile.ts
|
|
6813
|
+
var import_zod19 = require("zod");
|
|
6814
|
+
var import_fs12 = __toESM(require("fs"));
|
|
6815
|
+
var import_path12 = __toESM(require("path"));
|
|
6816
|
+
var import_server = require("@stackwright-pro/pulse/server");
|
|
6817
|
+
var import_server2 = require("@stackwright-pro/auth-nextjs/server");
|
|
6818
|
+
var import_server3 = require("@stackwright-pro/openapi/server");
|
|
6819
|
+
function buildContext(projectRoot) {
|
|
6820
|
+
return {
|
|
6821
|
+
projectRoot,
|
|
6822
|
+
contentOutDir: import_path12.default.join(projectRoot, "public", "stackwright-content"),
|
|
6823
|
+
imagesDir: import_path12.default.join(projectRoot, "public", "images"),
|
|
6824
|
+
siteConfig: loadSiteConfig(projectRoot)
|
|
6825
|
+
};
|
|
6826
|
+
}
|
|
6827
|
+
function loadSiteConfig(projectRoot) {
|
|
6828
|
+
try {
|
|
6829
|
+
const sitePath = import_path12.default.join(projectRoot, "public", "stackwright-content", "_site.json");
|
|
6830
|
+
return JSON.parse(import_fs12.default.readFileSync(sitePath, "utf8"));
|
|
6831
|
+
} catch {
|
|
6832
|
+
return {};
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
6835
|
+
function formatDuration(startMs) {
|
|
6836
|
+
const ms = Date.now() - startMs;
|
|
6837
|
+
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
|
|
6838
|
+
}
|
|
6839
|
+
async function tryOssCompile(fn, ctx, extra) {
|
|
6840
|
+
try {
|
|
6841
|
+
const bsPath = import_path12.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
|
|
6842
|
+
if (!import_fs12.default.existsSync(bsPath)) {
|
|
6843
|
+
return {
|
|
6844
|
+
ok: false,
|
|
6845
|
+
error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
|
|
6846
|
+
};
|
|
6847
|
+
}
|
|
6848
|
+
const bs = await import(bsPath);
|
|
6849
|
+
if (typeof bs[fn] !== "function") {
|
|
6850
|
+
return { ok: false, error: `${fn} not exported from @stackwright/build-scripts` };
|
|
6851
|
+
}
|
|
6852
|
+
await bs[fn](ctx, ...extra ? [extra] : []);
|
|
6853
|
+
return { ok: true };
|
|
6854
|
+
} catch (err) {
|
|
6855
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6858
|
+
function registerCompileTools(server2) {
|
|
6859
|
+
const projectRootArg = import_zod19.z.string().optional().describe("Absolute path to project root (defaults to cwd)");
|
|
6860
|
+
server2.tool(
|
|
6861
|
+
"stackwright_pro_compile_collections",
|
|
6862
|
+
"Compile stackwright.collections.yml \u2192 public/stackwright-content/_collections.json. Reads and validates the live-collections sidecar config file and emits the JSON sink consumed at runtime by PulseCollectionProvider.",
|
|
6863
|
+
{ projectRoot: projectRootArg },
|
|
6864
|
+
async ({ projectRoot }) => {
|
|
6865
|
+
const start = Date.now();
|
|
6866
|
+
const root = projectRoot ?? process.cwd();
|
|
6867
|
+
try {
|
|
6868
|
+
await (0, import_server.compileCollections)(buildContext(root));
|
|
6869
|
+
return {
|
|
6870
|
+
content: [
|
|
6871
|
+
{
|
|
6872
|
+
type: "text",
|
|
6873
|
+
text: ` _collections.json written (${formatDuration(start)})`
|
|
6874
|
+
}
|
|
6875
|
+
]
|
|
6876
|
+
};
|
|
6877
|
+
} catch (err) {
|
|
6878
|
+
return {
|
|
6879
|
+
content: [
|
|
6880
|
+
{
|
|
6881
|
+
type: "text",
|
|
6882
|
+
text: ` compile_collections failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6883
|
+
}
|
|
6884
|
+
]
|
|
6885
|
+
};
|
|
6886
|
+
}
|
|
6887
|
+
}
|
|
6888
|
+
);
|
|
6889
|
+
server2.tool(
|
|
6890
|
+
"stackwright_pro_compile_auth",
|
|
6891
|
+
"Compile stackwright.auth.yml \u2192 public/stackwright-content/_auth.json. Reads and validates the auth sidecar config file and emits the JSON sink consumed at app startup to configure auth middleware.",
|
|
6892
|
+
{ projectRoot: projectRootArg },
|
|
6893
|
+
async ({ projectRoot }) => {
|
|
6894
|
+
const start = Date.now();
|
|
6895
|
+
const root = projectRoot ?? process.cwd();
|
|
6896
|
+
try {
|
|
6897
|
+
await (0, import_server2.compileAuth)(buildContext(root));
|
|
6898
|
+
return {
|
|
6899
|
+
content: [
|
|
6900
|
+
{
|
|
6901
|
+
type: "text",
|
|
6902
|
+
text: ` _auth.json written (${formatDuration(start)})`
|
|
6903
|
+
}
|
|
6904
|
+
]
|
|
6905
|
+
};
|
|
6906
|
+
} catch (err) {
|
|
6907
|
+
return {
|
|
6908
|
+
content: [
|
|
6909
|
+
{
|
|
6910
|
+
type: "text",
|
|
6911
|
+
text: ` compile_auth failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6912
|
+
}
|
|
6913
|
+
]
|
|
6914
|
+
};
|
|
6915
|
+
}
|
|
6916
|
+
}
|
|
6917
|
+
);
|
|
6918
|
+
server2.tool(
|
|
6919
|
+
"stackwright_pro_compile_integrations",
|
|
6920
|
+
"Compile stackwright.integrations.yml \u2192 public/stackwright-content/_integrations.json. If no yml file exists, falls back to extracting integration metadata from _site.json (collections data is excluded \u2014 it lives in _collections.json).",
|
|
6921
|
+
{ projectRoot: projectRootArg },
|
|
6922
|
+
async ({ projectRoot }) => {
|
|
6923
|
+
const start = Date.now();
|
|
6924
|
+
const root = projectRoot ?? process.cwd();
|
|
6925
|
+
try {
|
|
6926
|
+
await (0, import_server3.compileIntegrations)(buildContext(root));
|
|
6927
|
+
return {
|
|
6928
|
+
content: [
|
|
6929
|
+
{
|
|
6930
|
+
type: "text",
|
|
6931
|
+
text: ` _integrations.json written (${formatDuration(start)})`
|
|
6932
|
+
}
|
|
6933
|
+
]
|
|
6934
|
+
};
|
|
6935
|
+
} catch (err) {
|
|
6936
|
+
return {
|
|
6937
|
+
content: [
|
|
6938
|
+
{
|
|
6939
|
+
type: "text",
|
|
6940
|
+
text: ` compile_integrations failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6941
|
+
}
|
|
6942
|
+
]
|
|
6943
|
+
};
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6946
|
+
);
|
|
6947
|
+
server2.tool(
|
|
6948
|
+
"stackwright_pro_compile_all",
|
|
6949
|
+
"Run all Pro compile sinks (collections + auth + integrations) in sequence. Equivalent to running compile_collections, compile_auth, and compile_integrations individually. Use this for a full Pro prebuild pass.",
|
|
6950
|
+
{ projectRoot: projectRootArg },
|
|
6951
|
+
async ({ projectRoot }) => {
|
|
6952
|
+
const start = Date.now();
|
|
6953
|
+
const root = projectRoot ?? process.cwd();
|
|
6954
|
+
const ctx = buildContext(root);
|
|
6955
|
+
const results = [];
|
|
6956
|
+
const errors = [];
|
|
6957
|
+
for (const [label, fn] of [
|
|
6958
|
+
["collections", () => (0, import_server.compileCollections)(ctx)],
|
|
6959
|
+
["auth", () => (0, import_server2.compileAuth)(ctx)],
|
|
6960
|
+
["integrations", () => (0, import_server3.compileIntegrations)(ctx)]
|
|
6961
|
+
]) {
|
|
6962
|
+
try {
|
|
6963
|
+
await fn();
|
|
6964
|
+
results.push(` _${label}.json`);
|
|
6965
|
+
} catch (err) {
|
|
6966
|
+
errors.push(` ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
const summary = errors.length > 0 ? ` compile_all finished with errors (${formatDuration(start)}):
|
|
6970
|
+
${results.join("\n")}
|
|
6971
|
+
Errors:
|
|
6972
|
+
${errors.join("\n")}` : ` compile_all complete (${formatDuration(start)}):
|
|
6973
|
+
${results.join("\n")}`;
|
|
6974
|
+
return { content: [{ type: "text", text: summary }] };
|
|
6975
|
+
}
|
|
6976
|
+
);
|
|
6977
|
+
server2.tool(
|
|
6978
|
+
"stackwright_pro_compile_theme",
|
|
6979
|
+
"Compile stackwright.yml theme config \u2192 _theme.json. Delegates to compileTheme from @stackwright/build-scripts installed in the project.",
|
|
6980
|
+
{ projectRoot: projectRootArg },
|
|
6981
|
+
async ({ projectRoot }) => {
|
|
6982
|
+
const start = Date.now();
|
|
6983
|
+
const root = projectRoot ?? process.cwd();
|
|
6984
|
+
const { ok, error } = await tryOssCompile("compileTheme", buildContext(root));
|
|
6985
|
+
return {
|
|
6986
|
+
content: [
|
|
6987
|
+
{
|
|
6988
|
+
type: "text",
|
|
6989
|
+
text: ok ? ` _theme.json written (${formatDuration(start)})` : ` compile_theme failed: ${error}`
|
|
6990
|
+
}
|
|
6991
|
+
]
|
|
6992
|
+
};
|
|
6993
|
+
}
|
|
6994
|
+
);
|
|
6995
|
+
server2.tool(
|
|
6996
|
+
"stackwright_pro_compile_site",
|
|
6997
|
+
"Compile stackwright.yml \u2192 _site.json. Delegates to compileSite from @stackwright/build-scripts installed in the project.",
|
|
6998
|
+
{ projectRoot: projectRootArg },
|
|
6999
|
+
async ({ projectRoot }) => {
|
|
7000
|
+
const start = Date.now();
|
|
7001
|
+
const root = projectRoot ?? process.cwd();
|
|
7002
|
+
const { ok, error } = await tryOssCompile("compileSite", buildContext(root));
|
|
7003
|
+
return {
|
|
7004
|
+
content: [
|
|
7005
|
+
{
|
|
7006
|
+
type: "text",
|
|
7007
|
+
text: ok ? ` _site.json written (${formatDuration(start)})` : ` compile_site failed: ${error}`
|
|
7008
|
+
}
|
|
7009
|
+
]
|
|
7010
|
+
};
|
|
7011
|
+
}
|
|
7012
|
+
);
|
|
7013
|
+
server2.tool(
|
|
7014
|
+
"stackwright_pro_compile_pages",
|
|
7015
|
+
"Compile all pages content.yml files \u2192 page JSON files. Delegates to compilePages from @stackwright/build-scripts installed in the project.",
|
|
7016
|
+
{ projectRoot: projectRootArg },
|
|
7017
|
+
async ({ projectRoot }) => {
|
|
7018
|
+
const start = Date.now();
|
|
7019
|
+
const root = projectRoot ?? process.cwd();
|
|
7020
|
+
const { ok, error } = await tryOssCompile("compilePages", buildContext(root));
|
|
7021
|
+
return {
|
|
7022
|
+
content: [
|
|
7023
|
+
{
|
|
7024
|
+
type: "text",
|
|
7025
|
+
text: ok ? ` pages compiled (${formatDuration(start)})` : ` compile_pages failed: ${error}`
|
|
7026
|
+
}
|
|
7027
|
+
]
|
|
7028
|
+
};
|
|
7029
|
+
}
|
|
7030
|
+
);
|
|
7031
|
+
server2.tool(
|
|
7032
|
+
"stackwright_pro_compile_page",
|
|
7033
|
+
"Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
|
|
7034
|
+
{
|
|
7035
|
+
slug: import_zod19.z.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
|
|
7036
|
+
projectRoot: projectRootArg
|
|
7037
|
+
},
|
|
7038
|
+
async ({ slug, projectRoot }) => {
|
|
7039
|
+
const start = Date.now();
|
|
7040
|
+
const root = projectRoot ?? process.cwd();
|
|
7041
|
+
const { ok, error } = await tryOssCompile("compilePage", buildContext(root), { slug });
|
|
7042
|
+
return {
|
|
7043
|
+
content: [
|
|
7044
|
+
{
|
|
7045
|
+
type: "text",
|
|
7046
|
+
text: ok ? ` page "${slug}" compiled (${formatDuration(start)})` : ` compile_page failed: ${error}`
|
|
7047
|
+
}
|
|
7048
|
+
]
|
|
7049
|
+
};
|
|
7050
|
+
}
|
|
7051
|
+
);
|
|
7052
|
+
}
|
|
7053
|
+
|
|
6533
7054
|
// package.json
|
|
6534
7055
|
var package_default = {
|
|
6535
7056
|
dependencies: {
|
|
6536
7057
|
"@modelcontextprotocol/sdk": "^1.10.0",
|
|
7058
|
+
"@stackwright-pro/auth-nextjs": "workspace:*",
|
|
6537
7059
|
"@stackwright-pro/cli-data-explorer": "workspace:*",
|
|
7060
|
+
"@stackwright-pro/openapi": "workspace:*",
|
|
7061
|
+
"@stackwright-pro/pulse": "workspace:*",
|
|
6538
7062
|
"@stackwright-pro/types": "workspace:*",
|
|
7063
|
+
"@stackwright/mcp": "0.6.0-alpha.1",
|
|
6539
7064
|
"@types/js-yaml": "^4.0.9",
|
|
6540
7065
|
"js-yaml": "^4.2.0",
|
|
6541
7066
|
zod: "^4.4.3"
|
|
@@ -6555,7 +7080,7 @@ var package_default = {
|
|
|
6555
7080
|
"test:coverage": "vitest run --coverage"
|
|
6556
7081
|
},
|
|
6557
7082
|
name: "@stackwright-pro/mcp",
|
|
6558
|
-
version: "0.2.0-alpha.
|
|
7083
|
+
version: "0.2.0-alpha.97",
|
|
6559
7084
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
6560
7085
|
license: "SEE LICENSE IN LICENSE",
|
|
6561
7086
|
main: "./dist/server.js",
|
|
@@ -6579,6 +7104,11 @@ var package_default = {
|
|
|
6579
7104
|
types: "./dist/tools/type-schemas.d.ts",
|
|
6580
7105
|
import: "./dist/tools/type-schemas.mjs",
|
|
6581
7106
|
require: "./dist/tools/type-schemas.js"
|
|
7107
|
+
},
|
|
7108
|
+
"./pipeline-constants": {
|
|
7109
|
+
types: "./dist/pipeline-constants.d.ts",
|
|
7110
|
+
import: "./dist/pipeline-constants.mjs",
|
|
7111
|
+
require: "./dist/pipeline-constants.js"
|
|
6582
7112
|
}
|
|
6583
7113
|
},
|
|
6584
7114
|
files: [
|
|
@@ -6590,6 +7120,7 @@ var package_default = {
|
|
|
6590
7120
|
};
|
|
6591
7121
|
|
|
6592
7122
|
// src/server.ts
|
|
7123
|
+
var import_register = require("@stackwright/mcp/register");
|
|
6593
7124
|
var server = new import_mcp.McpServer({
|
|
6594
7125
|
name: "stackwright-pro",
|
|
6595
7126
|
version: package_default.version
|
|
@@ -6613,7 +7144,45 @@ registerValidateYamlFragmentTool(server);
|
|
|
6613
7144
|
registerGetSchemaTool(server);
|
|
6614
7145
|
registerScaffoldCleanupTools(server);
|
|
6615
7146
|
registerContrastTools(server);
|
|
7147
|
+
registerCompileTools(server);
|
|
7148
|
+
(0, import_register.registerContentTypeTools)(server);
|
|
7149
|
+
(0, import_register.registerPageTools)(server);
|
|
7150
|
+
(0, import_register.registerSiteTools)(server);
|
|
7151
|
+
(0, import_register.registerProjectTools)(server);
|
|
7152
|
+
(0, import_register.registerGitOpsTools)(server);
|
|
7153
|
+
(0, import_register.registerBoardTools)(server);
|
|
7154
|
+
(0, import_register.registerCollectionTools)(server);
|
|
7155
|
+
(0, import_register.registerIntegrationTools)(server);
|
|
7156
|
+
(0, import_register.registerComposeTools)(server);
|
|
7157
|
+
(0, import_register.registerRenderTools)(server);
|
|
7158
|
+
(0, import_register.registerA11yTools)(server);
|
|
7159
|
+
process.on("SIGINT", async () => {
|
|
7160
|
+
const forceExit = setTimeout(() => process.exit(1), 3e3);
|
|
7161
|
+
forceExit.unref();
|
|
7162
|
+
await (0, import_register.closeBrowser)();
|
|
7163
|
+
process.exit(0);
|
|
7164
|
+
});
|
|
7165
|
+
process.on("SIGTERM", async () => {
|
|
7166
|
+
const forceExit = setTimeout(() => process.exit(1), 3e3);
|
|
7167
|
+
forceExit.unref();
|
|
7168
|
+
await (0, import_register.closeBrowser)();
|
|
7169
|
+
process.exit(0);
|
|
7170
|
+
});
|
|
6616
7171
|
async function main() {
|
|
7172
|
+
const pipelineGraph = loadPipelineGraph();
|
|
7173
|
+
const phasePosition = new Map(PHASE_ORDER.map((p, i) => [p, i]));
|
|
7174
|
+
for (const [phase, deps] of Object.entries(pipelineGraph.dependencies)) {
|
|
7175
|
+
const phaseIdx = phasePosition.get(phase);
|
|
7176
|
+
if (phaseIdx === void 0) continue;
|
|
7177
|
+
for (const dep of deps) {
|
|
7178
|
+
const depIdx = phasePosition.get(dep);
|
|
7179
|
+
if (depIdx !== void 0 && depIdx >= phaseIdx) {
|
|
7180
|
+
throw new Error(
|
|
7181
|
+
`PHASE_ORDER sanity check failed: "${dep}" (position ${depIdx}) must precede "${phase}" (position ${phaseIdx}) but it doesn't. Update PHASE_ORDER in pipeline.ts to reflect the new dependency structure.`
|
|
7182
|
+
);
|
|
7183
|
+
}
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
6617
7186
|
const transport = new import_stdio.StdioServerTransport();
|
|
6618
7187
|
try {
|
|
6619
7188
|
const servicesRegisterPkg = "@stackwright-services/mcp/register";
|