@stackwright-pro/mcp 0.2.0-alpha.90 → 0.2.0-alpha.95
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 +16 -16
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +16 -16
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +845 -338
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +794 -287
- package/dist/server.mjs.map +1 -1
- package/dist/tools/type-schemas.js +1 -1
- package/dist/tools/type-schemas.js.map +1 -1
- package/dist/tools/type-schemas.mjs +1 -1
- package/dist/tools/type-schemas.mjs.map +1 -1
- package/package.json +6 -3
package/dist/server.mjs
CHANGED
|
@@ -120,8 +120,8 @@ function registerDataExplorerTools(server2) {
|
|
|
120
120
|
lines.push(" endpoints:");
|
|
121
121
|
if (result.filter.include && result.filter.include.length > 0) {
|
|
122
122
|
lines.push(" include:");
|
|
123
|
-
for (const
|
|
124
|
-
lines.push(` - ${
|
|
123
|
+
for (const path4 of result.filter.include) {
|
|
124
|
+
lines.push(` - ${path4}`);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
if (result.filter.exclude && result.filter.exclude.length > 0) {
|
|
@@ -1595,6 +1595,9 @@ var OTTER_NAME_TO_PHASE = [
|
|
|
1595
1595
|
["dashboard", "dashboard"],
|
|
1596
1596
|
["data", "data"],
|
|
1597
1597
|
["page", "pages"],
|
|
1598
|
+
// swp-4071: workflow-otter renamed to form-wizard-otter; keep 'workflow' for
|
|
1599
|
+
// any in-flight session that still uses the old name (belt-and-suspenders)
|
|
1600
|
+
["form-wizard", "workflow"],
|
|
1598
1601
|
["workflow", "workflow"],
|
|
1599
1602
|
["services", "services"],
|
|
1600
1603
|
["polish", "polish"],
|
|
@@ -1609,7 +1612,7 @@ var PHASE_TO_OTTER = {
|
|
|
1609
1612
|
pages: "stackwright-pro-page-otter",
|
|
1610
1613
|
dashboard: "stackwright-pro-dashboard-otter",
|
|
1611
1614
|
data: "stackwright-pro-data-otter",
|
|
1612
|
-
workflow: "stackwright-pro-
|
|
1615
|
+
workflow: "stackwright-pro-form-wizard-otter",
|
|
1613
1616
|
services: "stackwright-services-otter",
|
|
1614
1617
|
polish: "stackwright-pro-polish-otter",
|
|
1615
1618
|
geo: "stackwright-pro-geo-otter"
|
|
@@ -1867,8 +1870,8 @@ function registerOrchestrationTools(server2) {
|
|
|
1867
1870
|
{
|
|
1868
1871
|
buildContext: z9.string().describe("Free-text description of what the user wants to build")
|
|
1869
1872
|
},
|
|
1870
|
-
async ({ buildContext }) => {
|
|
1871
|
-
const { text, isError } = handleSaveBuildContext({ buildContext });
|
|
1873
|
+
async ({ buildContext: buildContext2 }) => {
|
|
1874
|
+
const { text, isError } = handleSaveBuildContext({ buildContext: buildContext2 });
|
|
1872
1875
|
return {
|
|
1873
1876
|
content: [{ type: "text", text }],
|
|
1874
1877
|
isError
|
|
@@ -1996,8 +1999,8 @@ function registerOrchestrationTools(server2) {
|
|
|
1996
1999
|
|
|
1997
2000
|
// src/tools/pipeline.ts
|
|
1998
2001
|
import { z as z13 } from "zod";
|
|
1999
|
-
import { readFileSync as
|
|
2000
|
-
import { join as
|
|
2002
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, lstatSync as lstatSync6 } from "fs";
|
|
2003
|
+
import { join as join5 } from "path";
|
|
2001
2004
|
import { createHash as createHash3 } from "crypto";
|
|
2002
2005
|
|
|
2003
2006
|
// src/artifact-signing.ts
|
|
@@ -2687,6 +2690,223 @@ function registerGetSchemaTool(server2) {
|
|
|
2687
2690
|
);
|
|
2688
2691
|
}
|
|
2689
2692
|
|
|
2693
|
+
// src/tools/pipeline-graph.ts
|
|
2694
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2, lstatSync as lstatSync5 } from "fs";
|
|
2695
|
+
import { join as join4 } from "path";
|
|
2696
|
+
var MANIFEST_NAME_TO_PHASE = {
|
|
2697
|
+
"stackwright-pro-designer-otter": "designer",
|
|
2698
|
+
"stackwright-pro-theme-otter": "theme",
|
|
2699
|
+
"stackwright-pro-scaffold-otter": "scaffold",
|
|
2700
|
+
"stackwright-pro-api-otter": "api",
|
|
2701
|
+
"stackwright-pro-auth-otter": "auth",
|
|
2702
|
+
"stackwright-pro-data-otter": "data",
|
|
2703
|
+
"stackwright-pro-page-otter": "pages",
|
|
2704
|
+
"stackwright-pro-dashboard-otter": "dashboard",
|
|
2705
|
+
"stackwright-pro-form-wizard-otter": "workflow",
|
|
2706
|
+
"stackwright-pro-geo-otter": "geo",
|
|
2707
|
+
"stackwright-pro-polish-otter": "polish",
|
|
2708
|
+
"stackwright-services-otter": "services"
|
|
2709
|
+
};
|
|
2710
|
+
function manifestNameToPhase(name) {
|
|
2711
|
+
return MANIFEST_NAME_TO_PHASE[name] ?? null;
|
|
2712
|
+
}
|
|
2713
|
+
var OTTER_SEARCH_PATHS = [
|
|
2714
|
+
"node_modules/@stackwright-pro/otters/src/",
|
|
2715
|
+
// production: installed package
|
|
2716
|
+
"packages/otters/src/",
|
|
2717
|
+
// monorepo: run from repo root
|
|
2718
|
+
"../otters/src/"
|
|
2719
|
+
// monorepo: run from packages/mcp/
|
|
2720
|
+
];
|
|
2721
|
+
function resolveOtterDirFromCwd() {
|
|
2722
|
+
const cwd = process.cwd();
|
|
2723
|
+
for (const relative of OTTER_SEARCH_PATHS) {
|
|
2724
|
+
const candidate = join4(cwd, relative);
|
|
2725
|
+
try {
|
|
2726
|
+
lstatSync5(candidate);
|
|
2727
|
+
return candidate;
|
|
2728
|
+
} catch {
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
throw new Error(
|
|
2732
|
+
`Cannot locate otter directory. Searched: ${OTTER_SEARCH_PATHS.join(", ")} (relative to ${cwd}). Make sure @stackwright-pro/otters is installed.`
|
|
2733
|
+
);
|
|
2734
|
+
}
|
|
2735
|
+
function loadOtterManifestsFromDir(otterDir) {
|
|
2736
|
+
const entries = readdirSync2(otterDir);
|
|
2737
|
+
const manifests = [];
|
|
2738
|
+
for (const filename of entries) {
|
|
2739
|
+
if (!filename.endsWith("-otter.json")) continue;
|
|
2740
|
+
const filePath = join4(otterDir, filename);
|
|
2741
|
+
if (lstatSync5(filePath).isSymbolicLink()) continue;
|
|
2742
|
+
try {
|
|
2743
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
2744
|
+
const manifest = JSON.parse(raw);
|
|
2745
|
+
manifests.push(manifest);
|
|
2746
|
+
} catch (err) {
|
|
2747
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2748
|
+
throw new Error(`Failed to load otter manifest "${filename}": ${msg}`, { cause: err });
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
return manifests;
|
|
2752
|
+
}
|
|
2753
|
+
function topologicalSort(dependencies) {
|
|
2754
|
+
const phases = Object.keys(dependencies);
|
|
2755
|
+
const inDegree = {};
|
|
2756
|
+
const adjList = {};
|
|
2757
|
+
for (const phase of phases) {
|
|
2758
|
+
inDegree[phase] ??= 0;
|
|
2759
|
+
adjList[phase] ??= [];
|
|
2760
|
+
}
|
|
2761
|
+
for (const phase of phases) {
|
|
2762
|
+
for (const dep of dependencies[phase] ?? []) {
|
|
2763
|
+
inDegree[phase] = (inDegree[phase] ?? 0) + 1;
|
|
2764
|
+
adjList[dep] ??= [];
|
|
2765
|
+
adjList[dep].push(phase);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
const queue = phases.filter((p) => (inDegree[p] ?? 0) === 0);
|
|
2769
|
+
const result = [];
|
|
2770
|
+
while (queue.length > 0) {
|
|
2771
|
+
const node = queue.shift();
|
|
2772
|
+
result.push(node);
|
|
2773
|
+
for (const dependent of adjList[node] ?? []) {
|
|
2774
|
+
inDegree[dependent] -= 1;
|
|
2775
|
+
if (inDegree[dependent] === 0) {
|
|
2776
|
+
queue.push(dependent);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
if (result.length !== phases.length) {
|
|
2781
|
+
const cycleNodes = phases.filter((p) => (inDegree[p] ?? 0) > 0);
|
|
2782
|
+
throw new Error(
|
|
2783
|
+
`Pipeline dependency cycle detected involving: [${cycleNodes.join(", ")}]. Check otter pipeline declarations for circular dependencies.`
|
|
2784
|
+
);
|
|
2785
|
+
}
|
|
2786
|
+
return result;
|
|
2787
|
+
}
|
|
2788
|
+
function buildPipelineGraph(manifests) {
|
|
2789
|
+
const sinkProducers = {};
|
|
2790
|
+
const artifactProducers = {};
|
|
2791
|
+
for (const m of manifests) {
|
|
2792
|
+
const phase = manifestNameToPhase(m.name);
|
|
2793
|
+
if (!phase) continue;
|
|
2794
|
+
const out = m.pipeline?.outputs;
|
|
2795
|
+
if (!out) continue;
|
|
2796
|
+
for (const sink of out.sinks ?? []) {
|
|
2797
|
+
if (sinkProducers[sink]) {
|
|
2798
|
+
throw new Error(
|
|
2799
|
+
`Sink "${sink}" is produced by both "${sinkProducers[sink]}" and "${phase}". Each sink must have exactly one producer.`
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
sinkProducers[sink] = phase;
|
|
2803
|
+
}
|
|
2804
|
+
if (out.artifact) {
|
|
2805
|
+
if (artifactProducers[out.artifact]) {
|
|
2806
|
+
throw new Error(
|
|
2807
|
+
`Artifact "${out.artifact}" is produced by both "${artifactProducers[out.artifact]}" and "${phase}". Each artifact must have exactly one producer.`
|
|
2808
|
+
);
|
|
2809
|
+
}
|
|
2810
|
+
artifactProducers[out.artifact] = phase;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
const dependencies = {};
|
|
2814
|
+
for (const m of manifests) {
|
|
2815
|
+
const phase = manifestNameToPhase(m.name);
|
|
2816
|
+
if (!phase) continue;
|
|
2817
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2818
|
+
const inp = m.pipeline?.inputs;
|
|
2819
|
+
if (inp) {
|
|
2820
|
+
for (const sink of inp.sinks ?? []) {
|
|
2821
|
+
const producer = sinkProducers[sink];
|
|
2822
|
+
if (!producer) {
|
|
2823
|
+
throw new Error(
|
|
2824
|
+
`Phase "${phase}" declares sink input "${sink}" but no otter produces it. Add an otter with outputs.sinks including "${sink}".`
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
if (producer !== phase) deps.add(producer);
|
|
2828
|
+
}
|
|
2829
|
+
for (const artifact of inp.artifacts ?? []) {
|
|
2830
|
+
const producer = artifactProducers[artifact];
|
|
2831
|
+
if (!producer) {
|
|
2832
|
+
throw new Error(
|
|
2833
|
+
`Phase "${phase}" declares artifact input "${artifact}" but no otter produces it. Add an otter with outputs.artifact = "${artifact}".`
|
|
2834
|
+
);
|
|
2835
|
+
}
|
|
2836
|
+
if (producer !== phase) deps.add(producer);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
dependencies[phase] = Array.from(deps);
|
|
2840
|
+
}
|
|
2841
|
+
const order = topologicalSort(dependencies);
|
|
2842
|
+
return { dependencies, order, sinkProducers, artifactProducers };
|
|
2843
|
+
}
|
|
2844
|
+
var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
|
|
2845
|
+
function loadDistributedOtterManifests(cwd) {
|
|
2846
|
+
const results = [];
|
|
2847
|
+
for (const namespace of DISTRIBUTED_NAMESPACES) {
|
|
2848
|
+
const nsDir = join4(cwd, "node_modules", namespace);
|
|
2849
|
+
let pkgDirNames;
|
|
2850
|
+
try {
|
|
2851
|
+
pkgDirNames = readdirSync2(nsDir);
|
|
2852
|
+
} catch {
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
for (const pkgDirName of pkgDirNames) {
|
|
2856
|
+
const pkgDir = join4(nsDir, pkgDirName);
|
|
2857
|
+
const packageName = `${namespace}/${pkgDirName}`;
|
|
2858
|
+
let otterRelPaths;
|
|
2859
|
+
try {
|
|
2860
|
+
const raw = readFileSync4(join4(pkgDir, "package.json"), "utf-8");
|
|
2861
|
+
const pkgJson = JSON.parse(raw);
|
|
2862
|
+
const declared = pkgJson.stackwright?.otters;
|
|
2863
|
+
if (!Array.isArray(declared) || declared.length === 0) continue;
|
|
2864
|
+
otterRelPaths = declared;
|
|
2865
|
+
} catch {
|
|
2866
|
+
continue;
|
|
2867
|
+
}
|
|
2868
|
+
for (const relPath of otterRelPaths) {
|
|
2869
|
+
const otterFilePath = join4(pkgDir, relPath);
|
|
2870
|
+
try {
|
|
2871
|
+
const raw = readFileSync4(otterFilePath, "utf-8");
|
|
2872
|
+
const manifest = JSON.parse(raw);
|
|
2873
|
+
results.push({ manifest, packageName });
|
|
2874
|
+
} catch (err) {
|
|
2875
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2876
|
+
console.warn(
|
|
2877
|
+
`[pipeline-graph] Failed to load distributed otter at "${otterFilePath}" from "${packageName}": ${msg}`
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
return results;
|
|
2884
|
+
}
|
|
2885
|
+
function _mergeManifestPools(central, distributed) {
|
|
2886
|
+
const centralNames = new Set(central.map((m) => m.name));
|
|
2887
|
+
const merged = [...central];
|
|
2888
|
+
for (const { manifest, packageName } of distributed) {
|
|
2889
|
+
if (centralNames.has(manifest.name)) {
|
|
2890
|
+
console.warn(
|
|
2891
|
+
`[pipeline-graph] Otter "${manifest.name}" declared by both @stackwright-pro/otters and ${packageName} \u2014 using @stackwright-pro/otters version.`
|
|
2892
|
+
);
|
|
2893
|
+
} else {
|
|
2894
|
+
merged.push(manifest);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
return merged;
|
|
2898
|
+
}
|
|
2899
|
+
var _cachedGraph = null;
|
|
2900
|
+
function loadPipelineGraph() {
|
|
2901
|
+
if (_cachedGraph) return _cachedGraph;
|
|
2902
|
+
const otterDir = resolveOtterDirFromCwd();
|
|
2903
|
+
const central = loadOtterManifestsFromDir(otterDir);
|
|
2904
|
+
const distributed = loadDistributedOtterManifests(process.cwd());
|
|
2905
|
+
const manifests = _mergeManifestPools(central, distributed);
|
|
2906
|
+
_cachedGraph = buildPipelineGraph(manifests);
|
|
2907
|
+
return _cachedGraph;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2690
2910
|
// src/tools/pipeline.ts
|
|
2691
2911
|
var PHASE_ORDER = [
|
|
2692
2912
|
"designer",
|
|
@@ -2695,45 +2915,15 @@ var PHASE_ORDER = [
|
|
|
2695
2915
|
// generates app/ directory from config
|
|
2696
2916
|
"api",
|
|
2697
2917
|
"data",
|
|
2918
|
+
"auth",
|
|
2919
|
+
// moved earlier — only depends on design-language.json (designer)
|
|
2698
2920
|
"geo",
|
|
2699
2921
|
"workflow",
|
|
2700
2922
|
"services",
|
|
2701
2923
|
"pages",
|
|
2702
2924
|
"dashboard",
|
|
2703
|
-
"auth",
|
|
2704
2925
|
"polish"
|
|
2705
2926
|
];
|
|
2706
|
-
var PHASE_DEPENDENCIES = {
|
|
2707
|
-
designer: [],
|
|
2708
|
-
theme: ["designer"],
|
|
2709
|
-
scaffold: ["designer", "theme"],
|
|
2710
|
-
// needs stackwright.yml + theme-tokens
|
|
2711
|
-
api: [],
|
|
2712
|
-
data: ["api"],
|
|
2713
|
-
geo: ["data"],
|
|
2714
|
-
// workflow: no hard deps — uses service: references with Prism mock fallback
|
|
2715
|
-
// when API artifacts aren't available; roles come from user answers
|
|
2716
|
-
workflow: [],
|
|
2717
|
-
// services: needs API endpoints to know what to wire, and optionally
|
|
2718
|
-
// workflow states as trigger sources. Produces OpenAPI specs consumed
|
|
2719
|
-
// by pages and dashboard for typed data wiring.
|
|
2720
|
-
services: ["api", "workflow"],
|
|
2721
|
-
// pages/dashboard: depend on services for typed endpoint wiring (OpenAPI
|
|
2722
|
-
// specs) and on geo so the specialist prompt includes geo-manifest.json
|
|
2723
|
-
// — page/dashboard otters see which page slugs are already claimed and
|
|
2724
|
-
// won't attempt to overwrite them (swp-73c). 'api' is still transitive
|
|
2725
|
-
// through 'data'; auth removed — page-otter has graceful fallback.
|
|
2726
|
-
pages: ["designer", "theme", "data", "services", "geo", "scaffold"],
|
|
2727
|
-
dashboard: ["designer", "theme", "data", "services", "geo", "scaffold"],
|
|
2728
|
-
// auth is the penultimate phase — runs after all content-producing phases
|
|
2729
|
-
// so it can read pages, dashboard, workflow, and geo artifacts for route
|
|
2730
|
-
// protection and RBAC wiring. Skipped upstream phases still satisfy deps
|
|
2731
|
-
// (the foreman marks them executed=true), so auth always runs.
|
|
2732
|
-
auth: ["pages", "dashboard", "workflow", "geo"],
|
|
2733
|
-
// polish is the terminal phase — runs after auth so the landing page and
|
|
2734
|
-
// nav reflect the final set of protected routes and all generated pages.
|
|
2735
|
-
polish: ["auth"]
|
|
2736
|
-
};
|
|
2737
2927
|
var PHASE_ARTIFACT = {
|
|
2738
2928
|
designer: "design-language.json",
|
|
2739
2929
|
theme: "theme-tokens.json",
|
|
@@ -2757,7 +2947,7 @@ var PHASE_TO_OTTER2 = {
|
|
|
2757
2947
|
data: "stackwright-pro-data-otter",
|
|
2758
2948
|
pages: "stackwright-pro-page-otter",
|
|
2759
2949
|
dashboard: "stackwright-pro-dashboard-otter",
|
|
2760
|
-
workflow: "stackwright-pro-
|
|
2950
|
+
workflow: "stackwright-pro-form-wizard-otter",
|
|
2761
2951
|
services: "stackwright-services-otter",
|
|
2762
2952
|
polish: "stackwright-pro-polish-otter",
|
|
2763
2953
|
geo: "stackwright-pro-geo-otter"
|
|
@@ -2790,7 +2980,7 @@ function createDefaultState() {
|
|
|
2790
2980
|
};
|
|
2791
2981
|
}
|
|
2792
2982
|
function statePath(cwd) {
|
|
2793
|
-
return
|
|
2983
|
+
return join5(cwd, ".stackwright", "pipeline-state.json");
|
|
2794
2984
|
}
|
|
2795
2985
|
function readState(cwd) {
|
|
2796
2986
|
const p = statePath(cwd);
|
|
@@ -2803,7 +2993,7 @@ function readState(cwd) {
|
|
|
2803
2993
|
}
|
|
2804
2994
|
function safeWriteSync(filePath, content) {
|
|
2805
2995
|
if (existsSync5(filePath)) {
|
|
2806
|
-
const stat =
|
|
2996
|
+
const stat = lstatSync6(filePath);
|
|
2807
2997
|
if (stat.isSymbolicLink()) {
|
|
2808
2998
|
throw new Error(`Refusing to write to symlink: ${filePath}`);
|
|
2809
2999
|
}
|
|
@@ -2812,15 +3002,15 @@ function safeWriteSync(filePath, content) {
|
|
|
2812
3002
|
}
|
|
2813
3003
|
function safeReadSync(filePath) {
|
|
2814
3004
|
if (existsSync5(filePath)) {
|
|
2815
|
-
const stat =
|
|
3005
|
+
const stat = lstatSync6(filePath);
|
|
2816
3006
|
if (stat.isSymbolicLink()) {
|
|
2817
3007
|
throw new Error(`Refusing to read symlink: ${filePath}`);
|
|
2818
3008
|
}
|
|
2819
3009
|
}
|
|
2820
|
-
return
|
|
3010
|
+
return readFileSync5(filePath, "utf-8");
|
|
2821
3011
|
}
|
|
2822
3012
|
function writeState(cwd, state) {
|
|
2823
|
-
const dir =
|
|
3013
|
+
const dir = join5(cwd, ".stackwright");
|
|
2824
3014
|
mkdirSync4(dir, { recursive: true });
|
|
2825
3015
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2826
3016
|
safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
@@ -2842,7 +3032,7 @@ function handleGetPipelineState(_cwd) {
|
|
|
2842
3032
|
const cwd = _cwd ?? process.cwd();
|
|
2843
3033
|
try {
|
|
2844
3034
|
const state = readState(cwd);
|
|
2845
|
-
const keyPath =
|
|
3035
|
+
const keyPath = join5(cwd, ".stackwright", "pipeline-keys.json");
|
|
2846
3036
|
if (!existsSync5(keyPath)) {
|
|
2847
3037
|
try {
|
|
2848
3038
|
const { fingerprint } = initPipelineKeys(cwd);
|
|
@@ -2946,7 +3136,7 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
2946
3136
|
isError: true
|
|
2947
3137
|
};
|
|
2948
3138
|
}
|
|
2949
|
-
const answerFile =
|
|
3139
|
+
const answerFile = join5(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
2950
3140
|
if (!existsSync5(answerFile)) {
|
|
2951
3141
|
return {
|
|
2952
3142
|
text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
|
|
@@ -2971,11 +3161,11 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
2971
3161
|
}
|
|
2972
3162
|
}
|
|
2973
3163
|
try {
|
|
2974
|
-
const answersDir =
|
|
3164
|
+
const answersDir = join5(cwd, ".stackwright", "answers");
|
|
2975
3165
|
const answeredPhases = [];
|
|
2976
3166
|
const missingPhases = [];
|
|
2977
3167
|
for (const phase2 of PHASE_ORDER) {
|
|
2978
|
-
const answerFile =
|
|
3168
|
+
const answerFile = join5(answersDir, `${phase2}.json`);
|
|
2979
3169
|
if (existsSync5(answerFile)) {
|
|
2980
3170
|
try {
|
|
2981
3171
|
const raw = safeReadSync(answerFile);
|
|
@@ -3009,6 +3199,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3009
3199
|
const cwd = _cwd ?? process.cwd();
|
|
3010
3200
|
try {
|
|
3011
3201
|
const state = readState(cwd);
|
|
3202
|
+
const graph = loadPipelineGraph();
|
|
3012
3203
|
const completed = [];
|
|
3013
3204
|
const ready = [];
|
|
3014
3205
|
const blocked = [];
|
|
@@ -3018,7 +3209,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3018
3209
|
completed.push(phase);
|
|
3019
3210
|
continue;
|
|
3020
3211
|
}
|
|
3021
|
-
const deps =
|
|
3212
|
+
const deps = graph.dependencies[phase] ?? [];
|
|
3022
3213
|
const unmetDeps = deps.filter((dep) => !state.phases[dep]?.executed);
|
|
3023
3214
|
if (unmetDeps.length === 0) {
|
|
3024
3215
|
ready.push(phase);
|
|
@@ -3044,7 +3235,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3044
3235
|
function handleListArtifacts(_cwd) {
|
|
3045
3236
|
const cwd = _cwd ?? process.cwd();
|
|
3046
3237
|
try {
|
|
3047
|
-
const artifactsDir =
|
|
3238
|
+
const artifactsDir = join5(cwd, ".stackwright", "artifacts");
|
|
3048
3239
|
let manifest = null;
|
|
3049
3240
|
try {
|
|
3050
3241
|
manifest = loadSignatureManifest(cwd);
|
|
@@ -3054,7 +3245,7 @@ function handleListArtifacts(_cwd) {
|
|
|
3054
3245
|
let completedCount = 0;
|
|
3055
3246
|
for (const phase of PHASE_ORDER) {
|
|
3056
3247
|
const expectedFile = PHASE_ARTIFACT[phase];
|
|
3057
|
-
const fullPath =
|
|
3248
|
+
const fullPath = join5(artifactsDir, expectedFile);
|
|
3058
3249
|
const exists = existsSync5(fullPath);
|
|
3059
3250
|
if (exists) completedCount++;
|
|
3060
3251
|
let signed = false;
|
|
@@ -3108,9 +3299,9 @@ function handleWritePhaseQuestions(input) {
|
|
|
3108
3299
|
}
|
|
3109
3300
|
} catch {
|
|
3110
3301
|
}
|
|
3111
|
-
const questionsDir =
|
|
3302
|
+
const questionsDir = join5(cwd, ".stackwright", "questions");
|
|
3112
3303
|
mkdirSync4(questionsDir, { recursive: true });
|
|
3113
|
-
const filePath =
|
|
3304
|
+
const filePath = join5(questionsDir, `${phase}.json`);
|
|
3114
3305
|
const payload = {
|
|
3115
3306
|
version: "1.0",
|
|
3116
3307
|
phase,
|
|
@@ -3150,13 +3341,13 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3150
3341
|
};
|
|
3151
3342
|
}
|
|
3152
3343
|
try {
|
|
3153
|
-
const answersPath =
|
|
3344
|
+
const answersPath = join5(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3154
3345
|
let answers = {};
|
|
3155
3346
|
if (existsSync5(answersPath)) {
|
|
3156
3347
|
answers = JSON.parse(safeReadSync(answersPath));
|
|
3157
3348
|
}
|
|
3158
3349
|
let buildContextText = "";
|
|
3159
|
-
const buildContextPath =
|
|
3350
|
+
const buildContextPath = join5(cwd, ".stackwright", "build-context.json");
|
|
3160
3351
|
if (existsSync5(buildContextPath)) {
|
|
3161
3352
|
try {
|
|
3162
3353
|
const bcRaw = JSON.parse(safeReadSync(buildContextPath));
|
|
@@ -3166,12 +3357,13 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3166
3357
|
} catch {
|
|
3167
3358
|
}
|
|
3168
3359
|
}
|
|
3169
|
-
const
|
|
3360
|
+
const graph = loadPipelineGraph();
|
|
3361
|
+
const deps = graph.dependencies[phase] ?? [];
|
|
3170
3362
|
const artifactSections = [];
|
|
3171
3363
|
const missingDependencies = [];
|
|
3172
3364
|
for (const dep of deps) {
|
|
3173
3365
|
const artifactFile = PHASE_ARTIFACT[dep];
|
|
3174
|
-
const artifactPath =
|
|
3366
|
+
const artifactPath = join5(cwd, ".stackwright", "artifacts", artifactFile);
|
|
3175
3367
|
if (existsSync5(artifactPath)) {
|
|
3176
3368
|
const rawContent = safeReadSync(artifactPath);
|
|
3177
3369
|
const rawBytes = Buffer.from(rawContent, "utf-8");
|
|
@@ -3237,7 +3429,7 @@ ${JSON.stringify(content, null, 2)}`
|
|
|
3237
3429
|
parts.push("BUILD_CONTEXT:", buildContextText, "");
|
|
3238
3430
|
}
|
|
3239
3431
|
if (phase === "auth") {
|
|
3240
|
-
const initContextPath =
|
|
3432
|
+
const initContextPath = join5(cwd, ".stackwright", "init-context.json");
|
|
3241
3433
|
if (existsSync5(initContextPath)) {
|
|
3242
3434
|
try {
|
|
3243
3435
|
const initContext = JSON.parse(safeReadSync(initContextPath));
|
|
@@ -3470,7 +3662,7 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3470
3662
|
workflow: JSON.stringify(
|
|
3471
3663
|
{
|
|
3472
3664
|
version: "1.0",
|
|
3473
|
-
generatedBy: "stackwright-pro-
|
|
3665
|
+
generatedBy: "stackwright-pro-form-wizard-otter",
|
|
3474
3666
|
// Root key is `workflow` — NOT `workflowConfig` (see swp-k7cl)
|
|
3475
3667
|
workflow: {
|
|
3476
3668
|
id: "procurement-approval",
|
|
@@ -3680,6 +3872,57 @@ function handleValidateArtifact(input) {
|
|
|
3680
3872
|
const issues = result.error.issues.slice(0, 3).map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
3681
3873
|
return { success: false, error: { message: issues } };
|
|
3682
3874
|
}
|
|
3875
|
+
const rbacRoles = authConfig["rbacRoles"];
|
|
3876
|
+
const rbacDefaultRole = authConfig["rbacDefaultRole"];
|
|
3877
|
+
const protectedRoutes = authConfig["protectedRoutes"];
|
|
3878
|
+
if (Array.isArray(rbacRoles) && rbacRoles.length > 2 && Array.isArray(protectedRoutes) && protectedRoutes.length > 0 && typeof rbacDefaultRole === "string") {
|
|
3879
|
+
const uniqueRoles = new Set(
|
|
3880
|
+
protectedRoutes.map(
|
|
3881
|
+
(r) => typeof r === "string" ? rbacDefaultRole : r.requiredRole ?? rbacDefaultRole
|
|
3882
|
+
)
|
|
3883
|
+
);
|
|
3884
|
+
if (uniqueRoles.size === 1 && uniqueRoles.has(rbacDefaultRole)) {
|
|
3885
|
+
return {
|
|
3886
|
+
success: false,
|
|
3887
|
+
error: {
|
|
3888
|
+
message: `RBAC theatre detected: all ${protectedRoutes.length} protectedRoutes require '${rbacDefaultRole}' (the defaultRole), but ${rbacRoles.length} roles are defined. This defeats the RBAC hierarchy. Each route MUST have the minimum role required for its function. See auth-otter PER-ROUTE RBAC GRANULARITY section.`
|
|
3889
|
+
}
|
|
3890
|
+
};
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
return { success: true };
|
|
3894
|
+
},
|
|
3895
|
+
// swp-4071 — opt-in cross-reference validator for the services phase.
|
|
3896
|
+
// If workflow-config exists and declares serviceHooks, every hook ref must resolve
|
|
3897
|
+
// to a flow or state-machine in this services-config artifact.
|
|
3898
|
+
// Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
|
|
3899
|
+
services: (artifact2) => {
|
|
3900
|
+
const workflowArtifactPath = join5(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
3901
|
+
if (!existsSync5(workflowArtifactPath)) {
|
|
3902
|
+
return { success: true };
|
|
3903
|
+
}
|
|
3904
|
+
let workflowArtifact;
|
|
3905
|
+
try {
|
|
3906
|
+
workflowArtifact = JSON.parse(readFileSync5(workflowArtifactPath, "utf-8"));
|
|
3907
|
+
} catch {
|
|
3908
|
+
return { success: true };
|
|
3909
|
+
}
|
|
3910
|
+
const declaredHooks = workflowArtifact.serviceHooks ?? [];
|
|
3911
|
+
if (declaredHooks.length === 0) {
|
|
3912
|
+
return { success: true };
|
|
3913
|
+
}
|
|
3914
|
+
const flows = artifact2["flows"] ?? [];
|
|
3915
|
+
const workflows = artifact2["workflows"] ?? [];
|
|
3916
|
+
const flowNames = /* @__PURE__ */ new Set([...flows.map((f) => f.name), ...workflows.map((w) => w.name)]);
|
|
3917
|
+
const unresolved = declaredHooks.filter((h) => !flowNames.has(h.ref));
|
|
3918
|
+
if (unresolved.length > 0) {
|
|
3919
|
+
return {
|
|
3920
|
+
success: false,
|
|
3921
|
+
error: {
|
|
3922
|
+
message: `Workflow declared ${declaredHooks.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."
|
|
3923
|
+
}
|
|
3924
|
+
};
|
|
3925
|
+
}
|
|
3683
3926
|
return { success: true };
|
|
3684
3927
|
}
|
|
3685
3928
|
};
|
|
@@ -3697,10 +3940,10 @@ function handleValidateArtifact(input) {
|
|
|
3697
3940
|
}
|
|
3698
3941
|
}
|
|
3699
3942
|
try {
|
|
3700
|
-
const artifactsDir =
|
|
3943
|
+
const artifactsDir = join5(cwd, ".stackwright", "artifacts");
|
|
3701
3944
|
mkdirSync4(artifactsDir, { recursive: true });
|
|
3702
3945
|
const artifactFile = PHASE_ARTIFACT[phase];
|
|
3703
|
-
const artifactPath =
|
|
3946
|
+
const artifactPath = join5(artifactsDir, artifactFile);
|
|
3704
3947
|
const serialized = JSON.stringify(artifact, null, 2) + "\n";
|
|
3705
3948
|
const artifactBytes = Buffer.from(serialized, "utf-8");
|
|
3706
3949
|
safeWriteSync(artifactPath, serialized);
|
|
@@ -3817,9 +4060,9 @@ function registerPipelineTools(server2) {
|
|
|
3817
4060
|
isError: true
|
|
3818
4061
|
};
|
|
3819
4062
|
}
|
|
3820
|
-
const questionsDir =
|
|
4063
|
+
const questionsDir = join5(process.cwd(), ".stackwright", "questions");
|
|
3821
4064
|
mkdirSync4(questionsDir, { recursive: true });
|
|
3822
|
-
const outPath =
|
|
4065
|
+
const outPath = join5(questionsDir, `${phase}.json`);
|
|
3823
4066
|
writeFileSync4(
|
|
3824
4067
|
outPath,
|
|
3825
4068
|
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
@@ -3871,8 +4114,8 @@ function registerPipelineTools(server2) {
|
|
|
3871
4114
|
|
|
3872
4115
|
// src/tools/safe-write.ts
|
|
3873
4116
|
import { z as z14 } from "zod";
|
|
3874
|
-
import { writeFileSync as writeFileSync5, readFileSync as
|
|
3875
|
-
import { normalize, isAbsolute, dirname, join as
|
|
4117
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
|
|
4118
|
+
import { normalize, isAbsolute, dirname, join as join6 } from "path";
|
|
3876
4119
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
3877
4120
|
"stackwright-pro-designer-otter": [
|
|
3878
4121
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Design language artifact" }
|
|
@@ -3887,8 +4130,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3887
4130
|
],
|
|
3888
4131
|
"stackwright-pro-auth-otter": [
|
|
3889
4132
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
|
|
3890
|
-
{
|
|
3891
|
-
|
|
4133
|
+
{
|
|
4134
|
+
prefix: "stackwright.auth.",
|
|
4135
|
+
suffix: ".yml",
|
|
4136
|
+
description: "Auth config YAML (stackwright.auth.yml \u2014 compiled to _auth.json)"
|
|
4137
|
+
},
|
|
3892
4138
|
{
|
|
3893
4139
|
prefix: ".env",
|
|
3894
4140
|
suffix: "",
|
|
@@ -3907,7 +4153,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3907
4153
|
],
|
|
3908
4154
|
"stackwright-pro-data-otter": [
|
|
3909
4155
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Data config artifact" },
|
|
3910
|
-
{
|
|
4156
|
+
{
|
|
4157
|
+
prefix: "stackwright.collections.",
|
|
4158
|
+
suffix: ".yml",
|
|
4159
|
+
description: "Collections config YAML (stackwright.collections.yml \u2014 compiled to _collections.json)"
|
|
4160
|
+
}
|
|
3911
4161
|
],
|
|
3912
4162
|
"stackwright-pro-page-otter": [
|
|
3913
4163
|
{ prefix: "pages/", suffix: "/content.yml", description: "Page content YAML" },
|
|
@@ -3919,7 +4169,7 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3919
4169
|
{ prefix: "pages/", suffix: "/content.yaml", description: "Dashboard content YAML" },
|
|
3920
4170
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Dashboard manifest" }
|
|
3921
4171
|
],
|
|
3922
|
-
"stackwright-pro-
|
|
4172
|
+
"stackwright-pro-form-wizard-otter": [
|
|
3923
4173
|
{ prefix: "workflows/", suffix: ".yml", description: "Workflow definition" },
|
|
3924
4174
|
{ prefix: "workflows/", suffix: ".yaml", description: "Workflow definition" },
|
|
3925
4175
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Workflow config" }
|
|
@@ -3933,7 +4183,12 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3933
4183
|
}
|
|
3934
4184
|
],
|
|
3935
4185
|
"stackwright-pro-api-otter": [
|
|
3936
|
-
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" }
|
|
4186
|
+
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" },
|
|
4187
|
+
{
|
|
4188
|
+
prefix: "stackwright.integrations.",
|
|
4189
|
+
suffix: ".yml",
|
|
4190
|
+
description: "Integrations config YAML (stackwright.integrations.yml \u2014 compiled to _integrations.json)"
|
|
4191
|
+
}
|
|
3937
4192
|
],
|
|
3938
4193
|
"stackwright-pro-geo-otter": [
|
|
3939
4194
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Geo manifest artifact" },
|
|
@@ -4012,22 +4267,22 @@ function extractContentTypes(yamlContent) {
|
|
|
4012
4267
|
return types.length > 0 ? types : ["unknown"];
|
|
4013
4268
|
}
|
|
4014
4269
|
function readPageRegistry(cwd) {
|
|
4015
|
-
const regPath =
|
|
4270
|
+
const regPath = join6(cwd, PAGE_REGISTRY_FILE);
|
|
4016
4271
|
if (!existsSync6(regPath)) return {};
|
|
4017
4272
|
try {
|
|
4018
|
-
const stat =
|
|
4273
|
+
const stat = lstatSync7(regPath);
|
|
4019
4274
|
if (stat.isSymbolicLink()) return {};
|
|
4020
|
-
return JSON.parse(
|
|
4275
|
+
return JSON.parse(readFileSync6(regPath, "utf-8"));
|
|
4021
4276
|
} catch {
|
|
4022
4277
|
return {};
|
|
4023
4278
|
}
|
|
4024
4279
|
}
|
|
4025
4280
|
function writePageRegistry(cwd, registry) {
|
|
4026
|
-
const dir =
|
|
4281
|
+
const dir = join6(cwd, ".stackwright");
|
|
4027
4282
|
mkdirSync5(dir, { recursive: true });
|
|
4028
|
-
const regPath =
|
|
4283
|
+
const regPath = join6(cwd, PAGE_REGISTRY_FILE);
|
|
4029
4284
|
if (existsSync6(regPath)) {
|
|
4030
|
-
const stat =
|
|
4285
|
+
const stat = lstatSync7(regPath);
|
|
4031
4286
|
if (stat.isSymbolicLink()) {
|
|
4032
4287
|
throw new Error("Refusing to write page registry through symlink");
|
|
4033
4288
|
}
|
|
@@ -4047,8 +4302,8 @@ function checkPageOwnership(cwd, slug, callerOtter) {
|
|
|
4047
4302
|
};
|
|
4048
4303
|
}
|
|
4049
4304
|
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;
|
|
4050
|
-
function stripNextjsBracketSegments(
|
|
4051
|
-
return
|
|
4305
|
+
function stripNextjsBracketSegments(path4) {
|
|
4306
|
+
return path4.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
|
|
4052
4307
|
}
|
|
4053
4308
|
function checkPathAllowed(callerOtter, filePath) {
|
|
4054
4309
|
const normalized = normalize(filePath);
|
|
@@ -4198,10 +4453,10 @@ function handleSafeWrite(input) {
|
|
|
4198
4453
|
return { text: JSON.stringify(result), isError: true };
|
|
4199
4454
|
}
|
|
4200
4455
|
const normalized = normalize(filePath);
|
|
4201
|
-
const fullPath =
|
|
4456
|
+
const fullPath = join6(cwd, normalized);
|
|
4202
4457
|
if (existsSync6(fullPath)) {
|
|
4203
4458
|
try {
|
|
4204
|
-
const stat =
|
|
4459
|
+
const stat = lstatSync7(fullPath);
|
|
4205
4460
|
if (stat.isSymbolicLink()) {
|
|
4206
4461
|
const result = {
|
|
4207
4462
|
success: false,
|
|
@@ -4304,8 +4559,8 @@ function registerSafeWriteTools(server2) {
|
|
|
4304
4559
|
|
|
4305
4560
|
// src/tools/auth.ts
|
|
4306
4561
|
import { z as z15 } from "zod";
|
|
4307
|
-
import { readFileSync as
|
|
4308
|
-
import { join as
|
|
4562
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
4563
|
+
import { join as join7 } from "path";
|
|
4309
4564
|
function buildHierarchy(roles) {
|
|
4310
4565
|
const h = {};
|
|
4311
4566
|
for (let i = 0; i < roles.length - 1; i++) {
|
|
@@ -4319,17 +4574,22 @@ function hierarchyToYaml(hierarchy, indent) {
|
|
|
4319
4574
|
return entries.map(([role, subs]) => `${indent}${role}: [${subs.join(", ")}]`).join("\n");
|
|
4320
4575
|
}
|
|
4321
4576
|
function rolesToYaml(roles, indent) {
|
|
4322
|
-
return roles.map((r) => `${indent}- ${r}`).join("\n");
|
|
4577
|
+
return roles.map((r) => `${indent}- name: ${r}`).join("\n");
|
|
4578
|
+
}
|
|
4579
|
+
function normalizeRoutes(routes, defaultRole) {
|
|
4580
|
+
return routes.map(
|
|
4581
|
+
(r) => typeof r === "string" ? { pattern: r, requiredRole: defaultRole } : { pattern: r.pattern, requiredRole: r.requiredRole ?? defaultRole }
|
|
4582
|
+
);
|
|
4323
4583
|
}
|
|
4324
|
-
function routesToYaml(routes,
|
|
4325
|
-
return routes.map((r) => `${indent}- pattern: ${r}
|
|
4326
|
-
${indent} requiredRole: ${
|
|
4584
|
+
function routesToYaml(routes, indent) {
|
|
4585
|
+
return routes.map((r) => `${indent}- pattern: ${r.pattern}
|
|
4586
|
+
${indent} requiredRole: ${r.requiredRole}`).join("\n");
|
|
4327
4587
|
}
|
|
4328
4588
|
function detectNextMajorVersion(cwd) {
|
|
4329
4589
|
try {
|
|
4330
|
-
const pkgPath =
|
|
4590
|
+
const pkgPath = join7(cwd, "package.json");
|
|
4331
4591
|
if (!existsSync7(pkgPath)) return null;
|
|
4332
|
-
const pkg = JSON.parse(
|
|
4592
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
4333
4593
|
const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
4334
4594
|
if (!nextVersion) return null;
|
|
4335
4595
|
const match = nextVersion.match(/(\d+)/);
|
|
@@ -4338,25 +4598,6 @@ function detectNextMajorVersion(cwd) {
|
|
|
4338
4598
|
return null;
|
|
4339
4599
|
}
|
|
4340
4600
|
}
|
|
4341
|
-
function upsertAuthBlock(existing, authYaml) {
|
|
4342
|
-
const lines = existing.split("\n");
|
|
4343
|
-
let authStart = -1;
|
|
4344
|
-
let authEnd = lines.length;
|
|
4345
|
-
for (let i = 0; i < lines.length; i++) {
|
|
4346
|
-
if (/^auth:/.test(lines[i])) {
|
|
4347
|
-
authStart = i;
|
|
4348
|
-
} else if (authStart >= 0 && i > authStart && /^\S/.test(lines[i]) && lines[i].trim() !== "") {
|
|
4349
|
-
authEnd = i;
|
|
4350
|
-
break;
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
|
-
if (authStart < 0) {
|
|
4354
|
-
return existing.trimEnd() + "\n" + authYaml + "\n";
|
|
4355
|
-
}
|
|
4356
|
-
const before = lines.slice(0, authStart);
|
|
4357
|
-
const after = lines.slice(authEnd);
|
|
4358
|
-
return [...before, ...authYaml.trimEnd().split("\n"), ...after.length ? after : []].join("\n");
|
|
4359
|
-
}
|
|
4360
4601
|
function generateMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4361
4602
|
const rbacBlock = ` rbac: {
|
|
4362
4603
|
roles: ${JSON.stringify(roles)},
|
|
@@ -4367,9 +4608,10 @@ function generateMiddlewareContent(method, params, roles, defaultRole, hierarchy
|
|
|
4367
4608
|
enabled: ${auditEnabled},
|
|
4368
4609
|
retentionDays: ${auditRetentionDays},
|
|
4369
4610
|
},`;
|
|
4370
|
-
const
|
|
4611
|
+
const patterns = protectedRoutes.map((r) => r.pattern);
|
|
4612
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(patterns)},`;
|
|
4371
4613
|
const configBlock = `export const config = {
|
|
4372
|
-
matcher: ${JSON.stringify(
|
|
4614
|
+
matcher: ${JSON.stringify(patterns)},
|
|
4373
4615
|
};`;
|
|
4374
4616
|
if (method === "cac") {
|
|
4375
4617
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
@@ -4452,9 +4694,10 @@ function generateProxyContent(method, params, roles, defaultRole, hierarchy, aud
|
|
|
4452
4694
|
enabled: ${auditEnabled},
|
|
4453
4695
|
retentionDays: ${auditRetentionDays},
|
|
4454
4696
|
},`;
|
|
4455
|
-
const
|
|
4697
|
+
const patterns = protectedRoutes.map((r) => r.pattern);
|
|
4698
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(patterns)},`;
|
|
4456
4699
|
const configBlock = `export const config = {
|
|
4457
|
-
matcher: ${JSON.stringify(
|
|
4700
|
+
matcher: ${JSON.stringify(patterns)},
|
|
4458
4701
|
};`;
|
|
4459
4702
|
if (method === "cac") {
|
|
4460
4703
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
@@ -4554,36 +4797,33 @@ OAUTH2_CLIENT_SECRET=your-client-secret
|
|
|
4554
4797
|
`;
|
|
4555
4798
|
}
|
|
4556
4799
|
function generateYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
|
|
4557
|
-
const rbacSection = `
|
|
4558
|
-
|
|
4559
|
-
${rolesToYaml(roles, "
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
${hierarchyToYaml(hierarchy, "
|
|
4563
|
-
const auditSection = `
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
const
|
|
4567
|
-
${
|
|
4568
|
-
const routeLines = protectedRoutes.map((r) => ` - pattern: ${r}
|
|
4569
|
-
requiredRole: ${defaultRole}`).join("\n");
|
|
4570
|
-
const providerLine = params.provider ? ` provider: ${params.provider}
|
|
4800
|
+
const rbacSection = `rbac:
|
|
4801
|
+
roles:
|
|
4802
|
+
${rolesToYaml(roles, " ")}
|
|
4803
|
+
defaultRole: ${defaultRole}
|
|
4804
|
+
hierarchy:
|
|
4805
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
4806
|
+
const auditSection = `audit:
|
|
4807
|
+
enabled: ${auditEnabled}
|
|
4808
|
+
retentionDays: ${auditRetentionDays}`;
|
|
4809
|
+
const routeLines = routesToYaml(protectedRoutes, " ");
|
|
4810
|
+
const providerLine = params.provider ? `provider: ${params.provider}
|
|
4571
4811
|
` : "";
|
|
4572
4812
|
if (method === "cac") {
|
|
4573
4813
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4574
4814
|
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4575
4815
|
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4576
4816
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4577
|
-
return
|
|
4578
|
-
|
|
4579
|
-
${providerLine}
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4817
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4818
|
+
method: cac
|
|
4819
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4820
|
+
cac:
|
|
4821
|
+
caBundle: \${CAC_CA_BUNDLE}
|
|
4822
|
+
edipiLookup: ${edipiLookup}
|
|
4823
|
+
ocspEndpoint: \${CAC_OCSP_ENDPOINT}
|
|
4824
|
+
certHeader: ${certHeader}
|
|
4585
4825
|
${rbacSection}
|
|
4586
|
-
|
|
4826
|
+
protectedRoutes:
|
|
4587
4827
|
${routeLines}
|
|
4588
4828
|
${auditSection}
|
|
4589
4829
|
`;
|
|
@@ -4591,33 +4831,33 @@ ${auditSection}
|
|
|
4591
4831
|
if (method === "oidc") {
|
|
4592
4832
|
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4593
4833
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4594
|
-
return
|
|
4595
|
-
|
|
4596
|
-
${providerLine}
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4834
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4835
|
+
method: oidc
|
|
4836
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4837
|
+
oidc:
|
|
4838
|
+
discoveryUrl: \${OIDC_DISCOVERY_URL}
|
|
4839
|
+
clientId: \${OIDC_CLIENT_ID}
|
|
4840
|
+
clientSecret: \${OIDC_CLIENT_SECRET}
|
|
4841
|
+
scopes: ${scopes2}
|
|
4842
|
+
roleClaim: ${roleClaim}
|
|
4603
4843
|
${rbacSection}
|
|
4604
|
-
|
|
4844
|
+
protectedRoutes:
|
|
4605
4845
|
${routeLines}
|
|
4606
4846
|
${auditSection}
|
|
4607
4847
|
`;
|
|
4608
4848
|
}
|
|
4609
4849
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4610
|
-
return
|
|
4611
|
-
|
|
4612
|
-
${providerLine}
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4850
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4851
|
+
method: oauth2
|
|
4852
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4853
|
+
oauth2:
|
|
4854
|
+
authorizationUrl: \${OAUTH2_AUTH_URL}
|
|
4855
|
+
tokenUrl: \${OAUTH2_TOKEN_URL}
|
|
4856
|
+
clientId: \${OAUTH2_CLIENT_ID}
|
|
4857
|
+
clientSecret: \${OAUTH2_CLIENT_SECRET}
|
|
4858
|
+
scopes: ${scopes}
|
|
4619
4859
|
${rbacSection}
|
|
4620
|
-
|
|
4860
|
+
protectedRoutes:
|
|
4621
4861
|
${routeLines}
|
|
4622
4862
|
${auditSection}
|
|
4623
4863
|
`;
|
|
@@ -4632,9 +4872,10 @@ function generateDevOnlyMiddlewareContent(method, params, roles, defaultRole, hi
|
|
|
4632
4872
|
enabled: ${auditEnabled},
|
|
4633
4873
|
retentionDays: ${auditRetentionDays},
|
|
4634
4874
|
},`;
|
|
4635
|
-
const
|
|
4875
|
+
const patterns = protectedRoutes.map((r) => r.pattern);
|
|
4876
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(patterns)},`;
|
|
4636
4877
|
const configBlock = `export const config = {
|
|
4637
|
-
matcher: ${JSON.stringify(
|
|
4878
|
+
matcher: ${JSON.stringify(patterns)},
|
|
4638
4879
|
};`;
|
|
4639
4880
|
const devHeader = [
|
|
4640
4881
|
"// middleware.ts \u2014 generated by @stackwright-pro/auth-nextjs (dev-only mock)",
|
|
@@ -4721,9 +4962,10 @@ function generateDevOnlyProxyContent(method, params, roles, defaultRole, hierarc
|
|
|
4721
4962
|
enabled: ${auditEnabled},
|
|
4722
4963
|
retentionDays: ${auditRetentionDays},
|
|
4723
4964
|
},`;
|
|
4724
|
-
const
|
|
4965
|
+
const patterns = protectedRoutes.map((r) => r.pattern);
|
|
4966
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(patterns)},`;
|
|
4725
4967
|
const configBlock = `export const config = {
|
|
4726
|
-
matcher: ${JSON.stringify(
|
|
4968
|
+
matcher: ${JSON.stringify(patterns)},
|
|
4727
4969
|
};`;
|
|
4728
4970
|
const devHeader = [
|
|
4729
4971
|
"// proxy.ts -- generated by @stackwright-pro/auth (Next.js >=16, dev-only mock)",
|
|
@@ -4801,35 +5043,35 @@ ${configBlock}
|
|
|
4801
5043
|
`;
|
|
4802
5044
|
}
|
|
4803
5045
|
function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
|
|
4804
|
-
const rbacSection = `
|
|
4805
|
-
|
|
4806
|
-
${rolesToYaml(roles, "
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
${hierarchyToYaml(hierarchy, "
|
|
4810
|
-
const auditSection = `
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
const routeLines = protectedRoutes.map((r) => `
|
|
4814
|
-
|
|
4815
|
-
const providerLine = params.provider ? `
|
|
5046
|
+
const rbacSection = `rbac:
|
|
5047
|
+
roles:
|
|
5048
|
+
${rolesToYaml(roles, " ")}
|
|
5049
|
+
defaultRole: ${defaultRole}
|
|
5050
|
+
hierarchy:
|
|
5051
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
5052
|
+
const auditSection = `audit:
|
|
5053
|
+
enabled: ${auditEnabled}
|
|
5054
|
+
retentionDays: ${auditRetentionDays}`;
|
|
5055
|
+
const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
|
|
5056
|
+
requiredRole: ${r.requiredRole}`).join("\n");
|
|
5057
|
+
const providerLine = params.provider ? `provider: ${params.provider}
|
|
4816
5058
|
` : "";
|
|
4817
5059
|
if (method === "cac") {
|
|
4818
5060
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4819
5061
|
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4820
5062
|
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4821
5063
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4822
|
-
return
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
${providerLine}
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
5064
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5065
|
+
method: cac
|
|
5066
|
+
devOnly: true
|
|
5067
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5068
|
+
cac:
|
|
5069
|
+
caBundle: ${caBundle}
|
|
5070
|
+
edipiLookup: ${edipiLookup}
|
|
5071
|
+
ocspEndpoint: ${ocspEndpoint}
|
|
5072
|
+
certHeader: ${certHeader}
|
|
4831
5073
|
${rbacSection}
|
|
4832
|
-
|
|
5074
|
+
protectedRoutes:
|
|
4833
5075
|
${routeLines}
|
|
4834
5076
|
${auditSection}
|
|
4835
5077
|
`;
|
|
@@ -4837,35 +5079,35 @@ ${auditSection}
|
|
|
4837
5079
|
if (method === "oidc") {
|
|
4838
5080
|
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4839
5081
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4840
|
-
return
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
${providerLine}
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
5082
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5083
|
+
method: oidc
|
|
5084
|
+
devOnly: true
|
|
5085
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5086
|
+
oidc:
|
|
5087
|
+
discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
|
|
5088
|
+
clientId: dev-mock-client
|
|
5089
|
+
clientSecret: dev-mock-secret
|
|
5090
|
+
scopes: ${scopes2}
|
|
5091
|
+
roleClaim: ${roleClaim}
|
|
4850
5092
|
${rbacSection}
|
|
4851
|
-
|
|
5093
|
+
protectedRoutes:
|
|
4852
5094
|
${routeLines}
|
|
4853
5095
|
${auditSection}
|
|
4854
5096
|
`;
|
|
4855
5097
|
}
|
|
4856
5098
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4857
|
-
return
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
${providerLine}
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
5099
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5100
|
+
method: oauth2
|
|
5101
|
+
devOnly: true
|
|
5102
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5103
|
+
oauth2:
|
|
5104
|
+
authorizationUrl: https://dev-mock-oauth2/authorize
|
|
5105
|
+
tokenUrl: https://dev-mock-oauth2/token
|
|
5106
|
+
clientId: dev-mock-client
|
|
5107
|
+
clientSecret: dev-mock-secret
|
|
5108
|
+
scopes: ${scopes}
|
|
4867
5109
|
${rbacSection}
|
|
4868
|
-
|
|
5110
|
+
protectedRoutes:
|
|
4869
5111
|
${routeLines}
|
|
4870
5112
|
${auditSection}
|
|
4871
5113
|
`;
|
|
@@ -4944,6 +5186,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4944
5186
|
} = params;
|
|
4945
5187
|
const roles = rbacRoles;
|
|
4946
5188
|
const defaultRole = params.rbacDefaultRole ?? roles[roles.length - 1];
|
|
5189
|
+
const normalizedRoutes = normalizeRoutes(protectedRoutes, defaultRole);
|
|
4947
5190
|
const hierarchy = buildHierarchy(roles);
|
|
4948
5191
|
const detectedVersion = params.nextMajorVersion ?? detectNextMajorVersion(cwd);
|
|
4949
5192
|
const useProxy = (detectedVersion ?? 0) >= 16;
|
|
@@ -4980,7 +5223,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4980
5223
|
hierarchy,
|
|
4981
5224
|
auditEnabled,
|
|
4982
5225
|
auditRetentionDays,
|
|
4983
|
-
|
|
5226
|
+
normalizedRoutes
|
|
4984
5227
|
) : generateDevOnlyMiddlewareContent(
|
|
4985
5228
|
effectiveMethod,
|
|
4986
5229
|
params,
|
|
@@ -4989,7 +5232,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4989
5232
|
hierarchy,
|
|
4990
5233
|
auditEnabled,
|
|
4991
5234
|
auditRetentionDays,
|
|
4992
|
-
|
|
5235
|
+
normalizedRoutes
|
|
4993
5236
|
) : useProxy ? generateProxyContent(
|
|
4994
5237
|
effectiveMethod,
|
|
4995
5238
|
params,
|
|
@@ -4998,7 +5241,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4998
5241
|
hierarchy,
|
|
4999
5242
|
auditEnabled,
|
|
5000
5243
|
auditRetentionDays,
|
|
5001
|
-
|
|
5244
|
+
normalizedRoutes
|
|
5002
5245
|
) : generateMiddlewareContent(
|
|
5003
5246
|
effectiveMethod,
|
|
5004
5247
|
params,
|
|
@@ -5007,9 +5250,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5007
5250
|
hierarchy,
|
|
5008
5251
|
auditEnabled,
|
|
5009
5252
|
auditRetentionDays,
|
|
5010
|
-
|
|
5253
|
+
normalizedRoutes
|
|
5011
5254
|
);
|
|
5012
|
-
writeFileSync6(
|
|
5255
|
+
writeFileSync6(join7(cwd, conventionFile), authFileContent, "utf8");
|
|
5013
5256
|
filesWritten.push(conventionFile);
|
|
5014
5257
|
} catch (err) {
|
|
5015
5258
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5029,9 +5272,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5029
5272
|
if (!devOnly) {
|
|
5030
5273
|
try {
|
|
5031
5274
|
const envBlock = generateEnvBlock(effectiveMethod, params);
|
|
5032
|
-
const envPath =
|
|
5275
|
+
const envPath = join7(cwd, ".env.example");
|
|
5033
5276
|
if (existsSync7(envPath)) {
|
|
5034
|
-
const existing =
|
|
5277
|
+
const existing = readFileSync7(envPath, "utf8");
|
|
5035
5278
|
writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
5036
5279
|
} else {
|
|
5037
5280
|
writeFileSync6(envPath, envBlock, "utf8");
|
|
@@ -5052,10 +5295,10 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5052
5295
|
}
|
|
5053
5296
|
if (devOnly) {
|
|
5054
5297
|
try {
|
|
5055
|
-
const mockAuthDir =
|
|
5298
|
+
const mockAuthDir = join7(cwd, "lib");
|
|
5056
5299
|
mkdirSync6(mockAuthDir, { recursive: true });
|
|
5057
5300
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
5058
|
-
writeFileSync6(
|
|
5301
|
+
writeFileSync6(join7(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
5059
5302
|
filesWritten.push("lib/mock-auth.ts");
|
|
5060
5303
|
} catch (err) {
|
|
5061
5304
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5073,9 +5316,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5073
5316
|
};
|
|
5074
5317
|
}
|
|
5075
5318
|
try {
|
|
5076
|
-
const pkgPath =
|
|
5319
|
+
const pkgPath = join7(cwd, "package.json");
|
|
5077
5320
|
if (existsSync7(pkgPath)) {
|
|
5078
|
-
const existingPkg =
|
|
5321
|
+
const existingPkg = readFileSync7(pkgPath, "utf8");
|
|
5079
5322
|
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
5080
5323
|
writeFileSync6(pkgPath, updatedPkg, "utf8");
|
|
5081
5324
|
filesWritten.push("package.json");
|
|
@@ -5105,7 +5348,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5105
5348
|
hierarchy,
|
|
5106
5349
|
auditEnabled,
|
|
5107
5350
|
auditRetentionDays,
|
|
5108
|
-
|
|
5351
|
+
normalizedRoutes,
|
|
5109
5352
|
useProxy
|
|
5110
5353
|
) : generateYamlBlock(
|
|
5111
5354
|
effectiveMethod,
|
|
@@ -5115,24 +5358,21 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5115
5358
|
hierarchy,
|
|
5116
5359
|
auditEnabled,
|
|
5117
5360
|
auditRetentionDays,
|
|
5118
|
-
|
|
5361
|
+
normalizedRoutes,
|
|
5119
5362
|
useProxy
|
|
5120
5363
|
);
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
writeFileSync6(ymlPath, authYaml, "utf8");
|
|
5124
|
-
} else {
|
|
5125
|
-
const existing = readFileSync6(ymlPath, "utf8");
|
|
5126
|
-
writeFileSync6(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
|
|
5127
|
-
}
|
|
5128
|
-
filesWritten.push("stackwright.yml");
|
|
5364
|
+
writeFileSync6(join7(cwd, "stackwright.auth.yml"), authYaml, "utf8");
|
|
5365
|
+
filesWritten.push("stackwright.auth.yml");
|
|
5129
5366
|
} catch (err) {
|
|
5130
5367
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5131
5368
|
return {
|
|
5132
5369
|
content: [
|
|
5133
5370
|
{
|
|
5134
5371
|
type: "text",
|
|
5135
|
-
text: JSON.stringify({
|
|
5372
|
+
text: JSON.stringify({
|
|
5373
|
+
success: false,
|
|
5374
|
+
error: `Failed writing stackwright.auth.yml: ${msg}`
|
|
5375
|
+
})
|
|
5136
5376
|
}
|
|
5137
5377
|
],
|
|
5138
5378
|
isError: true
|
|
@@ -5177,7 +5417,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5177
5417
|
function registerAuthTools(server2) {
|
|
5178
5418
|
server2.tool(
|
|
5179
5419
|
"stackwright_pro_configure_auth",
|
|
5180
|
-
"Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` from a secure template,
|
|
5420
|
+
"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.",
|
|
5181
5421
|
{
|
|
5182
5422
|
method: z15.enum(["cac", "oidc", "oauth2", "none"]),
|
|
5183
5423
|
provider: z15.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
|
|
@@ -5204,8 +5444,15 @@ function registerAuthTools(server2) {
|
|
|
5204
5444
|
// Audit
|
|
5205
5445
|
auditEnabled: boolCoerce(z15.boolean().optional()),
|
|
5206
5446
|
auditRetentionDays: numCoerce(z15.number().int().positive().optional()),
|
|
5207
|
-
// Routes
|
|
5208
|
-
protectedRoutes: jsonCoerce(
|
|
5447
|
+
// Routes — accepts bare strings (backward-compat) or per-route objects (swp-owu0)
|
|
5448
|
+
protectedRoutes: jsonCoerce(
|
|
5449
|
+
z15.array(
|
|
5450
|
+
z15.union([
|
|
5451
|
+
z15.string(),
|
|
5452
|
+
z15.object({ pattern: z15.string(), requiredRole: z15.string().optional() })
|
|
5453
|
+
])
|
|
5454
|
+
).optional()
|
|
5455
|
+
),
|
|
5209
5456
|
// Injection for tests
|
|
5210
5457
|
_cwd: z15.string().optional(),
|
|
5211
5458
|
devOnly: boolCoerce(z15.boolean().optional()),
|
|
@@ -5221,28 +5468,28 @@ function registerAuthTools(server2) {
|
|
|
5221
5468
|
|
|
5222
5469
|
// src/integrity.ts
|
|
5223
5470
|
import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
5224
|
-
import { readFileSync as
|
|
5225
|
-
import { join as
|
|
5471
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync3, lstatSync as lstatSync8 } from "fs";
|
|
5472
|
+
import { join as join8, basename } from "path";
|
|
5226
5473
|
var _checksums = /* @__PURE__ */ new Map([
|
|
5227
5474
|
[
|
|
5228
5475
|
"stackwright-pro-api-otter.json",
|
|
5229
|
-
"
|
|
5476
|
+
"822b35d7a330ed8ac0b42a63b0f70a41885aa9b5ea23cc5b8b998b549da4c05c"
|
|
5230
5477
|
],
|
|
5231
5478
|
[
|
|
5232
5479
|
"stackwright-pro-auth-otter.json",
|
|
5233
|
-
"
|
|
5480
|
+
"d6cd5732667018d99456be77d5955e454da7a0999a0330b5f03a483aa1b9b33f"
|
|
5234
5481
|
],
|
|
5235
5482
|
[
|
|
5236
5483
|
"stackwright-pro-dashboard-otter.json",
|
|
5237
|
-
"
|
|
5484
|
+
"e3b82555fcffbd77285bd304828814e9f9f7257130797c9de3785de97527668c"
|
|
5238
5485
|
],
|
|
5239
5486
|
[
|
|
5240
5487
|
"stackwright-pro-data-otter.json",
|
|
5241
|
-
"
|
|
5488
|
+
"bb66eaab31c6872536dca4d3ff145724a46356946dd0665cc4f0346714d3bacb"
|
|
5242
5489
|
],
|
|
5243
5490
|
[
|
|
5244
5491
|
"stackwright-pro-designer-otter.json",
|
|
5245
|
-
"
|
|
5492
|
+
"b54121c6a2ab5b1ad68c1262c58b2e04e6315feacd6bb8de8e78eb36f2fa6be6"
|
|
5246
5493
|
],
|
|
5247
5494
|
[
|
|
5248
5495
|
"stackwright-pro-domain-expert-otter.json",
|
|
@@ -5250,35 +5497,35 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
5250
5497
|
],
|
|
5251
5498
|
[
|
|
5252
5499
|
"stackwright-pro-foreman-otter.json",
|
|
5253
|
-
"
|
|
5500
|
+
"abb1cc5e40a8c5ff98057273f43a9e90e2791a15951090cd4d98407c0c3618e3"
|
|
5501
|
+
],
|
|
5502
|
+
[
|
|
5503
|
+
"stackwright-pro-form-wizard-otter.json",
|
|
5504
|
+
"975ad68e8fb6fe5a10373be278946fa4d9d7ec2238a0b2bd9e4a412e5b1fdd6d"
|
|
5254
5505
|
],
|
|
5255
5506
|
[
|
|
5256
5507
|
"stackwright-pro-geo-otter.json",
|
|
5257
|
-
"
|
|
5508
|
+
"ac440da786d8c5ab1feddbf861449c3be3a433a04370578e1b2d35bf6ae7a601"
|
|
5258
5509
|
],
|
|
5259
5510
|
[
|
|
5260
5511
|
"stackwright-pro-page-otter.json",
|
|
5261
|
-
"
|
|
5512
|
+
"b70bd6e5d2a40230f7c24ff4a6113585a1614e5b6a215ece2bd47ff053c30f58"
|
|
5262
5513
|
],
|
|
5263
5514
|
[
|
|
5264
5515
|
"stackwright-pro-polish-otter.json",
|
|
5265
|
-
"
|
|
5516
|
+
"48ef3cf7f29ac6b5e8ab2004d4d41e2fb899e781689178d3905993241c84095a"
|
|
5266
5517
|
],
|
|
5267
5518
|
[
|
|
5268
5519
|
"stackwright-pro-scaffold-otter.json",
|
|
5269
|
-
"
|
|
5520
|
+
"e8662b63bbc9ce38851d94f9656bbebefdb7843d038862adb9fde2c23f9e77c2"
|
|
5270
5521
|
],
|
|
5271
5522
|
[
|
|
5272
5523
|
"stackwright-pro-theme-otter.json",
|
|
5273
|
-
"
|
|
5274
|
-
],
|
|
5275
|
-
[
|
|
5276
|
-
"stackwright-pro-workflow-otter.json",
|
|
5277
|
-
"5f6209fadc1355580e2455a16386f06bd7d5814f047b2dd5b33800abf6a48ff8"
|
|
5524
|
+
"fb62e56642f7f94c50ce173f3e1ce978b3f20ab1567e87403b901d6dd991a7db"
|
|
5278
5525
|
],
|
|
5279
5526
|
[
|
|
5280
5527
|
"stackwright-services-otter.json",
|
|
5281
|
-
"
|
|
5528
|
+
"0a5dac670482871c718099767b30bf23d8ec57e942bd40a00ce295926d82c6bd"
|
|
5282
5529
|
]
|
|
5283
5530
|
]);
|
|
5284
5531
|
Object.freeze(_checksums);
|
|
@@ -5307,7 +5554,7 @@ function verifyOtterFile(filePath) {
|
|
|
5307
5554
|
}
|
|
5308
5555
|
let stat;
|
|
5309
5556
|
try {
|
|
5310
|
-
stat =
|
|
5557
|
+
stat = lstatSync8(filePath);
|
|
5311
5558
|
} catch (err) {
|
|
5312
5559
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5313
5560
|
return { verified: false, filename, error: `Cannot stat file: ${msg}` };
|
|
@@ -5325,7 +5572,7 @@ function verifyOtterFile(filePath) {
|
|
|
5325
5572
|
}
|
|
5326
5573
|
let raw;
|
|
5327
5574
|
try {
|
|
5328
|
-
raw =
|
|
5575
|
+
raw = readFileSync8(filePath);
|
|
5329
5576
|
} catch (err) {
|
|
5330
5577
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5331
5578
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -5375,7 +5622,7 @@ function verifyAllOtters(otterDir) {
|
|
|
5375
5622
|
const unknown = [];
|
|
5376
5623
|
let entries;
|
|
5377
5624
|
try {
|
|
5378
|
-
entries =
|
|
5625
|
+
entries = readdirSync3(otterDir);
|
|
5379
5626
|
} catch (err) {
|
|
5380
5627
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5381
5628
|
return {
|
|
@@ -5386,9 +5633,9 @@ function verifyAllOtters(otterDir) {
|
|
|
5386
5633
|
}
|
|
5387
5634
|
const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
|
|
5388
5635
|
for (const filename of otterFiles) {
|
|
5389
|
-
const filePath =
|
|
5636
|
+
const filePath = join8(otterDir, filename);
|
|
5390
5637
|
try {
|
|
5391
|
-
if (
|
|
5638
|
+
if (lstatSync8(filePath).isSymbolicLink()) {
|
|
5392
5639
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
5393
5640
|
continue;
|
|
5394
5641
|
}
|
|
@@ -5414,9 +5661,9 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
|
|
|
5414
5661
|
function resolveOtterDir() {
|
|
5415
5662
|
const cwd = process.cwd();
|
|
5416
5663
|
for (const relative of DEFAULT_SEARCH_PATHS) {
|
|
5417
|
-
const candidate =
|
|
5664
|
+
const candidate = join8(cwd, relative);
|
|
5418
5665
|
try {
|
|
5419
|
-
|
|
5666
|
+
lstatSync8(candidate);
|
|
5420
5667
|
return candidate;
|
|
5421
5668
|
} catch {
|
|
5422
5669
|
}
|
|
@@ -5495,13 +5742,13 @@ function registerIntegrityTools(server2) {
|
|
|
5495
5742
|
|
|
5496
5743
|
// src/tools/domain.ts
|
|
5497
5744
|
import { z as z16 } from "zod";
|
|
5498
|
-
import { readFileSync as
|
|
5499
|
-
import { join as
|
|
5745
|
+
import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
|
|
5746
|
+
import { join as join9 } from "path";
|
|
5500
5747
|
function handleListCollections(input) {
|
|
5501
5748
|
const cwd = input._cwd ?? process.cwd();
|
|
5502
5749
|
const sources = [
|
|
5503
5750
|
{
|
|
5504
|
-
path:
|
|
5751
|
+
path: join9(cwd, ".stackwright", "artifacts", "data-config.json"),
|
|
5505
5752
|
source: "data-config.json",
|
|
5506
5753
|
parse: (raw) => {
|
|
5507
5754
|
const parsed = JSON.parse(raw);
|
|
@@ -5512,15 +5759,15 @@ function handleListCollections(input) {
|
|
|
5512
5759
|
}
|
|
5513
5760
|
},
|
|
5514
5761
|
{
|
|
5515
|
-
path:
|
|
5762
|
+
path: join9(cwd, "stackwright.yml"),
|
|
5516
5763
|
source: "stackwright.yml",
|
|
5517
5764
|
parse: extractCollectionsFromYaml
|
|
5518
5765
|
}
|
|
5519
5766
|
];
|
|
5520
|
-
for (const { path:
|
|
5521
|
-
if (!existsSync8(
|
|
5767
|
+
for (const { path: path4, source, parse } of sources) {
|
|
5768
|
+
if (!existsSync8(path4)) continue;
|
|
5522
5769
|
try {
|
|
5523
|
-
const collections = parse(
|
|
5770
|
+
const collections = parse(readFileSync9(path4, "utf8"));
|
|
5524
5771
|
return {
|
|
5525
5772
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
5526
5773
|
isError: false
|
|
@@ -5671,7 +5918,7 @@ function handleValidateWorkflow(input) {
|
|
|
5671
5918
|
if (input.workflow && Object.keys(input.workflow).length > 0) {
|
|
5672
5919
|
raw = input.workflow;
|
|
5673
5920
|
} else {
|
|
5674
|
-
const artifactPath =
|
|
5921
|
+
const artifactPath = join9(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
5675
5922
|
if (!existsSync8(artifactPath)) {
|
|
5676
5923
|
return fail([
|
|
5677
5924
|
{
|
|
@@ -5681,7 +5928,7 @@ function handleValidateWorkflow(input) {
|
|
|
5681
5928
|
]);
|
|
5682
5929
|
}
|
|
5683
5930
|
try {
|
|
5684
|
-
raw = JSON.parse(
|
|
5931
|
+
raw = JSON.parse(readFileSync9(artifactPath, "utf8"));
|
|
5685
5932
|
} catch (err) {
|
|
5686
5933
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
5687
5934
|
}
|
|
@@ -5793,12 +6040,12 @@ function handleValidateWorkflow(input) {
|
|
|
5793
6040
|
return { text: JSON.stringify({ valid: errors.length === 0, errors, warnings }), isError: false };
|
|
5794
6041
|
}
|
|
5795
6042
|
function validateTransitionTargets(step, stepId, stepIds, errors) {
|
|
5796
|
-
const check = (target,
|
|
6043
|
+
const check = (target, path4) => {
|
|
5797
6044
|
if (typeof target === "string" && !stepIds.has(target)) {
|
|
5798
6045
|
errors.push({
|
|
5799
6046
|
code: "ORPHANED_TRANSITION",
|
|
5800
6047
|
message: `Step "${stepId}" transitions to "${target}" which does not exist`,
|
|
5801
|
-
path:
|
|
6048
|
+
path: path4
|
|
5802
6049
|
});
|
|
5803
6050
|
}
|
|
5804
6051
|
};
|
|
@@ -5837,11 +6084,11 @@ function validateTransitionTargets(step, stepId, stepIds, errors) {
|
|
|
5837
6084
|
}
|
|
5838
6085
|
function collectServiceWarnings(step, stepId, warnings) {
|
|
5839
6086
|
const isService = (val) => typeof val === "string" && val.startsWith("service:");
|
|
5840
|
-
const warn = (
|
|
6087
|
+
const warn = (path4) => {
|
|
5841
6088
|
warnings.push({
|
|
5842
6089
|
code: "WARN_SERVICE_REFERENCE",
|
|
5843
|
-
message: `service: reference at "${
|
|
5844
|
-
path:
|
|
6090
|
+
message: `service: reference at "${path4}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
|
|
6091
|
+
path: path4
|
|
5845
6092
|
});
|
|
5846
6093
|
};
|
|
5847
6094
|
const onSubmit = step.on_submit;
|
|
@@ -5919,7 +6166,7 @@ function buildTypeSchemaSummary() {
|
|
|
5919
6166
|
"TransitionConditionSchema",
|
|
5920
6167
|
"PersistenceSchema"
|
|
5921
6168
|
],
|
|
5922
|
-
otter: "stackwright-pro-
|
|
6169
|
+
otter: "stackwright-pro-form-wizard-otter",
|
|
5923
6170
|
artifactKey: "workflowConfig"
|
|
5924
6171
|
},
|
|
5925
6172
|
auth: {
|
|
@@ -5991,15 +6238,15 @@ function registerTypeSchemasTool(server2) {
|
|
|
5991
6238
|
// src/tools/scaffold-cleanup.ts
|
|
5992
6239
|
import {
|
|
5993
6240
|
existsSync as existsSync9,
|
|
5994
|
-
lstatSync as
|
|
6241
|
+
lstatSync as lstatSync9,
|
|
5995
6242
|
unlinkSync as unlinkSync2,
|
|
5996
|
-
readFileSync as
|
|
6243
|
+
readFileSync as readFileSync10,
|
|
5997
6244
|
writeFileSync as writeFileSync7,
|
|
5998
|
-
readdirSync as
|
|
6245
|
+
readdirSync as readdirSync4,
|
|
5999
6246
|
rmdirSync,
|
|
6000
6247
|
mkdirSync as mkdirSync7
|
|
6001
6248
|
} from "fs";
|
|
6002
|
-
import { join as
|
|
6249
|
+
import { join as join10, dirname as dirname2 } from "path";
|
|
6003
6250
|
var SCAFFOLD_FILES_TO_DELETE = [
|
|
6004
6251
|
"content/posts/getting-started.yaml",
|
|
6005
6252
|
"content/posts/hello-world.yaml"
|
|
@@ -6024,12 +6271,12 @@ var FALLBACK_COLORS = {
|
|
|
6024
6271
|
mutedForeground: "#737373"
|
|
6025
6272
|
};
|
|
6026
6273
|
function readThemeColors(cwd) {
|
|
6027
|
-
const tokenPath =
|
|
6274
|
+
const tokenPath = join10(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
6028
6275
|
if (!existsSync9(tokenPath)) return { ...FALLBACK_COLORS };
|
|
6029
|
-
const stat =
|
|
6276
|
+
const stat = lstatSync9(tokenPath);
|
|
6030
6277
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
6031
6278
|
try {
|
|
6032
|
-
const raw = JSON.parse(
|
|
6279
|
+
const raw = JSON.parse(readFileSync10(tokenPath, "utf-8"));
|
|
6033
6280
|
const css = raw?.cssVariables ?? {};
|
|
6034
6281
|
const toHsl = (hslValue) => {
|
|
6035
6282
|
if (!hslValue || typeof hslValue !== "string") return null;
|
|
@@ -6090,14 +6337,14 @@ function generateNotFoundPage(colors) {
|
|
|
6090
6337
|
}
|
|
6091
6338
|
function isSafePath(fullPath) {
|
|
6092
6339
|
if (!existsSync9(fullPath)) return true;
|
|
6093
|
-
const stat =
|
|
6340
|
+
const stat = lstatSync9(fullPath);
|
|
6094
6341
|
return !stat.isSymbolicLink();
|
|
6095
6342
|
}
|
|
6096
6343
|
function isDirEmpty(dirPath) {
|
|
6097
6344
|
if (!existsSync9(dirPath)) return false;
|
|
6098
|
-
const stat =
|
|
6345
|
+
const stat = lstatSync9(dirPath);
|
|
6099
6346
|
if (!stat.isDirectory()) return false;
|
|
6100
|
-
return
|
|
6347
|
+
return readdirSync4(dirPath).length === 0;
|
|
6101
6348
|
}
|
|
6102
6349
|
function handleCleanupScaffold(_cwd) {
|
|
6103
6350
|
const cwd = _cwd ?? process.cwd();
|
|
@@ -6110,7 +6357,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6110
6357
|
cleanupWarnings: []
|
|
6111
6358
|
};
|
|
6112
6359
|
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
6113
|
-
const fullPath =
|
|
6360
|
+
const fullPath = join10(cwd, relPath);
|
|
6114
6361
|
if (!existsSync9(fullPath)) {
|
|
6115
6362
|
result.skipped.push(relPath);
|
|
6116
6363
|
continue;
|
|
@@ -6128,13 +6375,13 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6128
6375
|
}
|
|
6129
6376
|
{
|
|
6130
6377
|
const relPath = "pages/getting-started/content.yml";
|
|
6131
|
-
const fullPath =
|
|
6378
|
+
const fullPath = join10(cwd, relPath);
|
|
6132
6379
|
if (!existsSync9(fullPath)) {
|
|
6133
6380
|
result.skipped.push(relPath);
|
|
6134
6381
|
} else if (!isSafePath(fullPath)) {
|
|
6135
6382
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
6136
6383
|
} else {
|
|
6137
|
-
const content =
|
|
6384
|
+
const content = readFileSync10(fullPath, "utf-8");
|
|
6138
6385
|
const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
|
|
6139
6386
|
(marker) => content.includes(marker)
|
|
6140
6387
|
);
|
|
@@ -6154,8 +6401,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6154
6401
|
}
|
|
6155
6402
|
{
|
|
6156
6403
|
const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
|
|
6157
|
-
const fullPath =
|
|
6158
|
-
const postsDir =
|
|
6404
|
+
const fullPath = join10(cwd, relPath);
|
|
6405
|
+
const postsDir = join10(cwd, "content/posts");
|
|
6159
6406
|
if (!existsSync9(fullPath)) {
|
|
6160
6407
|
result.skipped.push(relPath);
|
|
6161
6408
|
} else if (!isSafePath(fullPath)) {
|
|
@@ -6163,7 +6410,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6163
6410
|
} else if (!existsSync9(postsDir)) {
|
|
6164
6411
|
result.skipped.push(relPath);
|
|
6165
6412
|
} else {
|
|
6166
|
-
const userPostFiles =
|
|
6413
|
+
const userPostFiles = readdirSync4(postsDir).filter(
|
|
6167
6414
|
(f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
6168
6415
|
);
|
|
6169
6416
|
if (userPostFiles.length === 0) {
|
|
@@ -6181,7 +6428,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6181
6428
|
}
|
|
6182
6429
|
}
|
|
6183
6430
|
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
6184
|
-
const fullDir =
|
|
6431
|
+
const fullDir = join10(cwd, relDir);
|
|
6185
6432
|
if (!existsSync9(fullDir)) continue;
|
|
6186
6433
|
if (!isSafePath(fullDir)) {
|
|
6187
6434
|
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
@@ -6197,7 +6444,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6197
6444
|
}
|
|
6198
6445
|
}
|
|
6199
6446
|
{
|
|
6200
|
-
const fullPath =
|
|
6447
|
+
const fullPath = join10(cwd, NOT_FOUND_PATH);
|
|
6201
6448
|
if (!existsSync9(fullPath)) {
|
|
6202
6449
|
result.skipped.push(NOT_FOUND_PATH);
|
|
6203
6450
|
} else if (!isSafePath(fullPath)) {
|
|
@@ -6475,11 +6722,256 @@ function registerContrastTools(server2) {
|
|
|
6475
6722
|
);
|
|
6476
6723
|
}
|
|
6477
6724
|
|
|
6725
|
+
// src/tools/compile.ts
|
|
6726
|
+
import { z as z19 } from "zod";
|
|
6727
|
+
import fs2 from "fs";
|
|
6728
|
+
import path3 from "path";
|
|
6729
|
+
import { compileCollections } from "@stackwright-pro/pulse/server";
|
|
6730
|
+
import { compileAuth } from "@stackwright-pro/auth-nextjs/server";
|
|
6731
|
+
import { compileIntegrations } from "@stackwright-pro/openapi/server";
|
|
6732
|
+
function buildContext(projectRoot) {
|
|
6733
|
+
return {
|
|
6734
|
+
projectRoot,
|
|
6735
|
+
contentOutDir: path3.join(projectRoot, "public", "stackwright-content"),
|
|
6736
|
+
imagesDir: path3.join(projectRoot, "public", "images"),
|
|
6737
|
+
siteConfig: loadSiteConfig(projectRoot)
|
|
6738
|
+
};
|
|
6739
|
+
}
|
|
6740
|
+
function loadSiteConfig(projectRoot) {
|
|
6741
|
+
try {
|
|
6742
|
+
const sitePath = path3.join(projectRoot, "public", "stackwright-content", "_site.json");
|
|
6743
|
+
return JSON.parse(fs2.readFileSync(sitePath, "utf8"));
|
|
6744
|
+
} catch {
|
|
6745
|
+
return {};
|
|
6746
|
+
}
|
|
6747
|
+
}
|
|
6748
|
+
function formatDuration(startMs) {
|
|
6749
|
+
const ms = Date.now() - startMs;
|
|
6750
|
+
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
|
|
6751
|
+
}
|
|
6752
|
+
async function tryOssCompile(fn, ctx, extra) {
|
|
6753
|
+
try {
|
|
6754
|
+
const bsPath = path3.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
|
|
6755
|
+
if (!fs2.existsSync(bsPath)) {
|
|
6756
|
+
return {
|
|
6757
|
+
ok: false,
|
|
6758
|
+
error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
|
|
6759
|
+
};
|
|
6760
|
+
}
|
|
6761
|
+
const bs = await import(bsPath);
|
|
6762
|
+
if (typeof bs[fn] !== "function") {
|
|
6763
|
+
return { ok: false, error: `${fn} not exported from @stackwright/build-scripts` };
|
|
6764
|
+
}
|
|
6765
|
+
await bs[fn](ctx, ...extra ? [extra] : []);
|
|
6766
|
+
return { ok: true };
|
|
6767
|
+
} catch (err) {
|
|
6768
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
function registerCompileTools(server2) {
|
|
6772
|
+
const projectRootArg = z19.string().optional().describe("Absolute path to project root (defaults to cwd)");
|
|
6773
|
+
server2.tool(
|
|
6774
|
+
"stackwright_pro_compile_collections",
|
|
6775
|
+
"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.",
|
|
6776
|
+
{ projectRoot: projectRootArg },
|
|
6777
|
+
async ({ projectRoot }) => {
|
|
6778
|
+
const start = Date.now();
|
|
6779
|
+
const root = projectRoot ?? process.cwd();
|
|
6780
|
+
try {
|
|
6781
|
+
await compileCollections(buildContext(root));
|
|
6782
|
+
return {
|
|
6783
|
+
content: [
|
|
6784
|
+
{
|
|
6785
|
+
type: "text",
|
|
6786
|
+
text: ` _collections.json written (${formatDuration(start)})`
|
|
6787
|
+
}
|
|
6788
|
+
]
|
|
6789
|
+
};
|
|
6790
|
+
} catch (err) {
|
|
6791
|
+
return {
|
|
6792
|
+
content: [
|
|
6793
|
+
{
|
|
6794
|
+
type: "text",
|
|
6795
|
+
text: ` compile_collections failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6796
|
+
}
|
|
6797
|
+
]
|
|
6798
|
+
};
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
);
|
|
6802
|
+
server2.tool(
|
|
6803
|
+
"stackwright_pro_compile_auth",
|
|
6804
|
+
"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.",
|
|
6805
|
+
{ projectRoot: projectRootArg },
|
|
6806
|
+
async ({ projectRoot }) => {
|
|
6807
|
+
const start = Date.now();
|
|
6808
|
+
const root = projectRoot ?? process.cwd();
|
|
6809
|
+
try {
|
|
6810
|
+
await compileAuth(buildContext(root));
|
|
6811
|
+
return {
|
|
6812
|
+
content: [
|
|
6813
|
+
{
|
|
6814
|
+
type: "text",
|
|
6815
|
+
text: ` _auth.json written (${formatDuration(start)})`
|
|
6816
|
+
}
|
|
6817
|
+
]
|
|
6818
|
+
};
|
|
6819
|
+
} catch (err) {
|
|
6820
|
+
return {
|
|
6821
|
+
content: [
|
|
6822
|
+
{
|
|
6823
|
+
type: "text",
|
|
6824
|
+
text: ` compile_auth failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6825
|
+
}
|
|
6826
|
+
]
|
|
6827
|
+
};
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
);
|
|
6831
|
+
server2.tool(
|
|
6832
|
+
"stackwright_pro_compile_integrations",
|
|
6833
|
+
"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).",
|
|
6834
|
+
{ projectRoot: projectRootArg },
|
|
6835
|
+
async ({ projectRoot }) => {
|
|
6836
|
+
const start = Date.now();
|
|
6837
|
+
const root = projectRoot ?? process.cwd();
|
|
6838
|
+
try {
|
|
6839
|
+
await compileIntegrations(buildContext(root));
|
|
6840
|
+
return {
|
|
6841
|
+
content: [
|
|
6842
|
+
{
|
|
6843
|
+
type: "text",
|
|
6844
|
+
text: ` _integrations.json written (${formatDuration(start)})`
|
|
6845
|
+
}
|
|
6846
|
+
]
|
|
6847
|
+
};
|
|
6848
|
+
} catch (err) {
|
|
6849
|
+
return {
|
|
6850
|
+
content: [
|
|
6851
|
+
{
|
|
6852
|
+
type: "text",
|
|
6853
|
+
text: ` compile_integrations failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6854
|
+
}
|
|
6855
|
+
]
|
|
6856
|
+
};
|
|
6857
|
+
}
|
|
6858
|
+
}
|
|
6859
|
+
);
|
|
6860
|
+
server2.tool(
|
|
6861
|
+
"stackwright_pro_compile_all",
|
|
6862
|
+
"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.",
|
|
6863
|
+
{ projectRoot: projectRootArg },
|
|
6864
|
+
async ({ projectRoot }) => {
|
|
6865
|
+
const start = Date.now();
|
|
6866
|
+
const root = projectRoot ?? process.cwd();
|
|
6867
|
+
const ctx = buildContext(root);
|
|
6868
|
+
const results = [];
|
|
6869
|
+
const errors = [];
|
|
6870
|
+
for (const [label, fn] of [
|
|
6871
|
+
["collections", () => compileCollections(ctx)],
|
|
6872
|
+
["auth", () => compileAuth(ctx)],
|
|
6873
|
+
["integrations", () => compileIntegrations(ctx)]
|
|
6874
|
+
]) {
|
|
6875
|
+
try {
|
|
6876
|
+
await fn();
|
|
6877
|
+
results.push(` _${label}.json`);
|
|
6878
|
+
} catch (err) {
|
|
6879
|
+
errors.push(` ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
const summary = errors.length > 0 ? ` compile_all finished with errors (${formatDuration(start)}):
|
|
6883
|
+
${results.join("\n")}
|
|
6884
|
+
Errors:
|
|
6885
|
+
${errors.join("\n")}` : ` compile_all complete (${formatDuration(start)}):
|
|
6886
|
+
${results.join("\n")}`;
|
|
6887
|
+
return { content: [{ type: "text", text: summary }] };
|
|
6888
|
+
}
|
|
6889
|
+
);
|
|
6890
|
+
server2.tool(
|
|
6891
|
+
"stackwright_pro_compile_theme",
|
|
6892
|
+
"Compile stackwright.yml theme config \u2192 _theme.json. Delegates to compileTheme from @stackwright/build-scripts installed in the project.",
|
|
6893
|
+
{ projectRoot: projectRootArg },
|
|
6894
|
+
async ({ projectRoot }) => {
|
|
6895
|
+
const start = Date.now();
|
|
6896
|
+
const root = projectRoot ?? process.cwd();
|
|
6897
|
+
const { ok, error } = await tryOssCompile("compileTheme", buildContext(root));
|
|
6898
|
+
return {
|
|
6899
|
+
content: [
|
|
6900
|
+
{
|
|
6901
|
+
type: "text",
|
|
6902
|
+
text: ok ? ` _theme.json written (${formatDuration(start)})` : ` compile_theme failed: ${error}`
|
|
6903
|
+
}
|
|
6904
|
+
]
|
|
6905
|
+
};
|
|
6906
|
+
}
|
|
6907
|
+
);
|
|
6908
|
+
server2.tool(
|
|
6909
|
+
"stackwright_pro_compile_site",
|
|
6910
|
+
"Compile stackwright.yml \u2192 _site.json. Delegates to compileSite from @stackwright/build-scripts installed in the project.",
|
|
6911
|
+
{ projectRoot: projectRootArg },
|
|
6912
|
+
async ({ projectRoot }) => {
|
|
6913
|
+
const start = Date.now();
|
|
6914
|
+
const root = projectRoot ?? process.cwd();
|
|
6915
|
+
const { ok, error } = await tryOssCompile("compileSite", buildContext(root));
|
|
6916
|
+
return {
|
|
6917
|
+
content: [
|
|
6918
|
+
{
|
|
6919
|
+
type: "text",
|
|
6920
|
+
text: ok ? ` _site.json written (${formatDuration(start)})` : ` compile_site failed: ${error}`
|
|
6921
|
+
}
|
|
6922
|
+
]
|
|
6923
|
+
};
|
|
6924
|
+
}
|
|
6925
|
+
);
|
|
6926
|
+
server2.tool(
|
|
6927
|
+
"stackwright_pro_compile_pages",
|
|
6928
|
+
"Compile all pages content.yml files \u2192 page JSON files. Delegates to compilePages from @stackwright/build-scripts installed in the project.",
|
|
6929
|
+
{ projectRoot: projectRootArg },
|
|
6930
|
+
async ({ projectRoot }) => {
|
|
6931
|
+
const start = Date.now();
|
|
6932
|
+
const root = projectRoot ?? process.cwd();
|
|
6933
|
+
const { ok, error } = await tryOssCompile("compilePages", buildContext(root));
|
|
6934
|
+
return {
|
|
6935
|
+
content: [
|
|
6936
|
+
{
|
|
6937
|
+
type: "text",
|
|
6938
|
+
text: ok ? ` pages compiled (${formatDuration(start)})` : ` compile_pages failed: ${error}`
|
|
6939
|
+
}
|
|
6940
|
+
]
|
|
6941
|
+
};
|
|
6942
|
+
}
|
|
6943
|
+
);
|
|
6944
|
+
server2.tool(
|
|
6945
|
+
"stackwright_pro_compile_page",
|
|
6946
|
+
"Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
|
|
6947
|
+
{
|
|
6948
|
+
slug: z19.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
|
|
6949
|
+
projectRoot: projectRootArg
|
|
6950
|
+
},
|
|
6951
|
+
async ({ slug, projectRoot }) => {
|
|
6952
|
+
const start = Date.now();
|
|
6953
|
+
const root = projectRoot ?? process.cwd();
|
|
6954
|
+
const { ok, error } = await tryOssCompile("compilePage", buildContext(root), { slug });
|
|
6955
|
+
return {
|
|
6956
|
+
content: [
|
|
6957
|
+
{
|
|
6958
|
+
type: "text",
|
|
6959
|
+
text: ok ? ` page "${slug}" compiled (${formatDuration(start)})` : ` compile_page failed: ${error}`
|
|
6960
|
+
}
|
|
6961
|
+
]
|
|
6962
|
+
};
|
|
6963
|
+
}
|
|
6964
|
+
);
|
|
6965
|
+
}
|
|
6966
|
+
|
|
6478
6967
|
// package.json
|
|
6479
6968
|
var package_default = {
|
|
6480
6969
|
dependencies: {
|
|
6481
6970
|
"@modelcontextprotocol/sdk": "^1.10.0",
|
|
6971
|
+
"@stackwright-pro/auth-nextjs": "workspace:*",
|
|
6482
6972
|
"@stackwright-pro/cli-data-explorer": "workspace:*",
|
|
6973
|
+
"@stackwright-pro/openapi": "workspace:*",
|
|
6974
|
+
"@stackwright-pro/pulse": "workspace:*",
|
|
6483
6975
|
"@stackwright-pro/types": "workspace:*",
|
|
6484
6976
|
"@types/js-yaml": "^4.0.9",
|
|
6485
6977
|
"js-yaml": "^4.2.0",
|
|
@@ -6500,7 +6992,7 @@ var package_default = {
|
|
|
6500
6992
|
"test:coverage": "vitest run --coverage"
|
|
6501
6993
|
},
|
|
6502
6994
|
name: "@stackwright-pro/mcp",
|
|
6503
|
-
version: "0.2.0-alpha.
|
|
6995
|
+
version: "0.2.0-alpha.95",
|
|
6504
6996
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
6505
6997
|
license: "SEE LICENSE IN LICENSE",
|
|
6506
6998
|
main: "./dist/server.js",
|
|
@@ -6558,7 +7050,22 @@ registerValidateYamlFragmentTool(server);
|
|
|
6558
7050
|
registerGetSchemaTool(server);
|
|
6559
7051
|
registerScaffoldCleanupTools(server);
|
|
6560
7052
|
registerContrastTools(server);
|
|
7053
|
+
registerCompileTools(server);
|
|
6561
7054
|
async function main() {
|
|
7055
|
+
const pipelineGraph = loadPipelineGraph();
|
|
7056
|
+
const phasePosition = new Map(PHASE_ORDER.map((p, i) => [p, i]));
|
|
7057
|
+
for (const [phase, deps] of Object.entries(pipelineGraph.dependencies)) {
|
|
7058
|
+
const phaseIdx = phasePosition.get(phase);
|
|
7059
|
+
if (phaseIdx === void 0) continue;
|
|
7060
|
+
for (const dep of deps) {
|
|
7061
|
+
const depIdx = phasePosition.get(dep);
|
|
7062
|
+
if (depIdx !== void 0 && depIdx >= phaseIdx) {
|
|
7063
|
+
throw new Error(
|
|
7064
|
+
`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.`
|
|
7065
|
+
);
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
6562
7069
|
const transport = new StdioServerTransport();
|
|
6563
7070
|
try {
|
|
6564
7071
|
const servicesRegisterPkg = "@stackwright-services/mcp/register";
|