@stackwright-pro/mcp 0.2.0-alpha.92 → 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 +13 -13
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +13 -13
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +750 -311
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +698 -259
- package/dist/server.mjs.map +1 -1
- package/package.json +6 -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,223 @@ 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
|
+
};
|
|
2706
|
+
function manifestNameToPhase(name) {
|
|
2707
|
+
return MANIFEST_NAME_TO_PHASE[name] ?? null;
|
|
2708
|
+
}
|
|
2709
|
+
var OTTER_SEARCH_PATHS = [
|
|
2710
|
+
"node_modules/@stackwright-pro/otters/src/",
|
|
2711
|
+
// production: installed package
|
|
2712
|
+
"packages/otters/src/",
|
|
2713
|
+
// monorepo: run from repo root
|
|
2714
|
+
"../otters/src/"
|
|
2715
|
+
// monorepo: run from packages/mcp/
|
|
2716
|
+
];
|
|
2717
|
+
function resolveOtterDirFromCwd() {
|
|
2718
|
+
const cwd = process.cwd();
|
|
2719
|
+
for (const relative of OTTER_SEARCH_PATHS) {
|
|
2720
|
+
const candidate = (0, import_path5.join)(cwd, relative);
|
|
2721
|
+
try {
|
|
2722
|
+
(0, import_fs5.lstatSync)(candidate);
|
|
2723
|
+
return candidate;
|
|
2724
|
+
} catch {
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
throw new Error(
|
|
2728
|
+
`Cannot locate otter directory. Searched: ${OTTER_SEARCH_PATHS.join(", ")} (relative to ${cwd}). Make sure @stackwright-pro/otters is installed.`
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2731
|
+
function loadOtterManifestsFromDir(otterDir) {
|
|
2732
|
+
const entries = (0, import_fs5.readdirSync)(otterDir);
|
|
2733
|
+
const manifests = [];
|
|
2734
|
+
for (const filename of entries) {
|
|
2735
|
+
if (!filename.endsWith("-otter.json")) continue;
|
|
2736
|
+
const filePath = (0, import_path5.join)(otterDir, filename);
|
|
2737
|
+
if ((0, import_fs5.lstatSync)(filePath).isSymbolicLink()) continue;
|
|
2738
|
+
try {
|
|
2739
|
+
const raw = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
2740
|
+
const manifest = JSON.parse(raw);
|
|
2741
|
+
manifests.push(manifest);
|
|
2742
|
+
} catch (err) {
|
|
2743
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2744
|
+
throw new Error(`Failed to load otter manifest "${filename}": ${msg}`, { cause: err });
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
return manifests;
|
|
2748
|
+
}
|
|
2749
|
+
function topologicalSort(dependencies) {
|
|
2750
|
+
const phases = Object.keys(dependencies);
|
|
2751
|
+
const inDegree = {};
|
|
2752
|
+
const adjList = {};
|
|
2753
|
+
for (const phase of phases) {
|
|
2754
|
+
inDegree[phase] ??= 0;
|
|
2755
|
+
adjList[phase] ??= [];
|
|
2756
|
+
}
|
|
2757
|
+
for (const phase of phases) {
|
|
2758
|
+
for (const dep of dependencies[phase] ?? []) {
|
|
2759
|
+
inDegree[phase] = (inDegree[phase] ?? 0) + 1;
|
|
2760
|
+
adjList[dep] ??= [];
|
|
2761
|
+
adjList[dep].push(phase);
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
const queue = phases.filter((p) => (inDegree[p] ?? 0) === 0);
|
|
2765
|
+
const result = [];
|
|
2766
|
+
while (queue.length > 0) {
|
|
2767
|
+
const node = queue.shift();
|
|
2768
|
+
result.push(node);
|
|
2769
|
+
for (const dependent of adjList[node] ?? []) {
|
|
2770
|
+
inDegree[dependent] -= 1;
|
|
2771
|
+
if (inDegree[dependent] === 0) {
|
|
2772
|
+
queue.push(dependent);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
if (result.length !== phases.length) {
|
|
2777
|
+
const cycleNodes = phases.filter((p) => (inDegree[p] ?? 0) > 0);
|
|
2778
|
+
throw new Error(
|
|
2779
|
+
`Pipeline dependency cycle detected involving: [${cycleNodes.join(", ")}]. Check otter pipeline declarations for circular dependencies.`
|
|
2780
|
+
);
|
|
2781
|
+
}
|
|
2782
|
+
return result;
|
|
2783
|
+
}
|
|
2784
|
+
function buildPipelineGraph(manifests) {
|
|
2785
|
+
const sinkProducers = {};
|
|
2786
|
+
const artifactProducers = {};
|
|
2787
|
+
for (const m of manifests) {
|
|
2788
|
+
const phase = manifestNameToPhase(m.name);
|
|
2789
|
+
if (!phase) continue;
|
|
2790
|
+
const out = m.pipeline?.outputs;
|
|
2791
|
+
if (!out) continue;
|
|
2792
|
+
for (const sink of out.sinks ?? []) {
|
|
2793
|
+
if (sinkProducers[sink]) {
|
|
2794
|
+
throw new Error(
|
|
2795
|
+
`Sink "${sink}" is produced by both "${sinkProducers[sink]}" and "${phase}". Each sink must have exactly one producer.`
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
sinkProducers[sink] = phase;
|
|
2799
|
+
}
|
|
2800
|
+
if (out.artifact) {
|
|
2801
|
+
if (artifactProducers[out.artifact]) {
|
|
2802
|
+
throw new Error(
|
|
2803
|
+
`Artifact "${out.artifact}" is produced by both "${artifactProducers[out.artifact]}" and "${phase}". Each artifact must have exactly one producer.`
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
artifactProducers[out.artifact] = phase;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
const dependencies = {};
|
|
2810
|
+
for (const m of manifests) {
|
|
2811
|
+
const phase = manifestNameToPhase(m.name);
|
|
2812
|
+
if (!phase) continue;
|
|
2813
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2814
|
+
const inp = m.pipeline?.inputs;
|
|
2815
|
+
if (inp) {
|
|
2816
|
+
for (const sink of inp.sinks ?? []) {
|
|
2817
|
+
const producer = sinkProducers[sink];
|
|
2818
|
+
if (!producer) {
|
|
2819
|
+
throw new Error(
|
|
2820
|
+
`Phase "${phase}" declares sink input "${sink}" but no otter produces it. Add an otter with outputs.sinks including "${sink}".`
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
if (producer !== phase) deps.add(producer);
|
|
2824
|
+
}
|
|
2825
|
+
for (const artifact of inp.artifacts ?? []) {
|
|
2826
|
+
const producer = artifactProducers[artifact];
|
|
2827
|
+
if (!producer) {
|
|
2828
|
+
throw new Error(
|
|
2829
|
+
`Phase "${phase}" declares artifact input "${artifact}" but no otter produces it. Add an otter with outputs.artifact = "${artifact}".`
|
|
2830
|
+
);
|
|
2831
|
+
}
|
|
2832
|
+
if (producer !== phase) deps.add(producer);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
dependencies[phase] = Array.from(deps);
|
|
2836
|
+
}
|
|
2837
|
+
const order = topologicalSort(dependencies);
|
|
2838
|
+
return { dependencies, order, sinkProducers, artifactProducers };
|
|
2839
|
+
}
|
|
2840
|
+
var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
|
|
2841
|
+
function loadDistributedOtterManifests(cwd) {
|
|
2842
|
+
const results = [];
|
|
2843
|
+
for (const namespace of DISTRIBUTED_NAMESPACES) {
|
|
2844
|
+
const nsDir = (0, import_path5.join)(cwd, "node_modules", namespace);
|
|
2845
|
+
let pkgDirNames;
|
|
2846
|
+
try {
|
|
2847
|
+
pkgDirNames = (0, import_fs5.readdirSync)(nsDir);
|
|
2848
|
+
} catch {
|
|
2849
|
+
continue;
|
|
2850
|
+
}
|
|
2851
|
+
for (const pkgDirName of pkgDirNames) {
|
|
2852
|
+
const pkgDir = (0, import_path5.join)(nsDir, pkgDirName);
|
|
2853
|
+
const packageName = `${namespace}/${pkgDirName}`;
|
|
2854
|
+
let otterRelPaths;
|
|
2855
|
+
try {
|
|
2856
|
+
const raw = (0, import_fs5.readFileSync)((0, import_path5.join)(pkgDir, "package.json"), "utf-8");
|
|
2857
|
+
const pkgJson = JSON.parse(raw);
|
|
2858
|
+
const declared = pkgJson.stackwright?.otters;
|
|
2859
|
+
if (!Array.isArray(declared) || declared.length === 0) continue;
|
|
2860
|
+
otterRelPaths = declared;
|
|
2861
|
+
} catch {
|
|
2862
|
+
continue;
|
|
2863
|
+
}
|
|
2864
|
+
for (const relPath of otterRelPaths) {
|
|
2865
|
+
const otterFilePath = (0, import_path5.join)(pkgDir, relPath);
|
|
2866
|
+
try {
|
|
2867
|
+
const raw = (0, import_fs5.readFileSync)(otterFilePath, "utf-8");
|
|
2868
|
+
const manifest = JSON.parse(raw);
|
|
2869
|
+
results.push({ manifest, packageName });
|
|
2870
|
+
} catch (err) {
|
|
2871
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2872
|
+
console.warn(
|
|
2873
|
+
`[pipeline-graph] Failed to load distributed otter at "${otterFilePath}" from "${packageName}": ${msg}`
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
return results;
|
|
2880
|
+
}
|
|
2881
|
+
function _mergeManifestPools(central, distributed) {
|
|
2882
|
+
const centralNames = new Set(central.map((m) => m.name));
|
|
2883
|
+
const merged = [...central];
|
|
2884
|
+
for (const { manifest, packageName } of distributed) {
|
|
2885
|
+
if (centralNames.has(manifest.name)) {
|
|
2886
|
+
console.warn(
|
|
2887
|
+
`[pipeline-graph] Otter "${manifest.name}" declared by both @stackwright-pro/otters and ${packageName} \u2014 using @stackwright-pro/otters version.`
|
|
2888
|
+
);
|
|
2889
|
+
} else {
|
|
2890
|
+
merged.push(manifest);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
return merged;
|
|
2894
|
+
}
|
|
2895
|
+
var _cachedGraph = null;
|
|
2896
|
+
function loadPipelineGraph() {
|
|
2897
|
+
if (_cachedGraph) return _cachedGraph;
|
|
2898
|
+
const otterDir = resolveOtterDirFromCwd();
|
|
2899
|
+
const central = loadOtterManifestsFromDir(otterDir);
|
|
2900
|
+
const distributed = loadDistributedOtterManifests(process.cwd());
|
|
2901
|
+
const manifests = _mergeManifestPools(central, distributed);
|
|
2902
|
+
_cachedGraph = buildPipelineGraph(manifests);
|
|
2903
|
+
return _cachedGraph;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2689
2906
|
// src/tools/pipeline.ts
|
|
2690
2907
|
var PHASE_ORDER = [
|
|
2691
2908
|
"designer",
|
|
@@ -2694,45 +2911,15 @@ var PHASE_ORDER = [
|
|
|
2694
2911
|
// generates app/ directory from config
|
|
2695
2912
|
"api",
|
|
2696
2913
|
"data",
|
|
2914
|
+
"auth",
|
|
2915
|
+
// moved earlier — only depends on design-language.json (designer)
|
|
2697
2916
|
"geo",
|
|
2698
2917
|
"workflow",
|
|
2699
2918
|
"services",
|
|
2700
2919
|
"pages",
|
|
2701
2920
|
"dashboard",
|
|
2702
|
-
"auth",
|
|
2703
2921
|
"polish"
|
|
2704
2922
|
];
|
|
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
2923
|
var PHASE_ARTIFACT = {
|
|
2737
2924
|
designer: "design-language.json",
|
|
2738
2925
|
theme: "theme-tokens.json",
|
|
@@ -2789,11 +2976,11 @@ function createDefaultState() {
|
|
|
2789
2976
|
};
|
|
2790
2977
|
}
|
|
2791
2978
|
function statePath(cwd) {
|
|
2792
|
-
return (0,
|
|
2979
|
+
return (0, import_path6.join)(cwd, ".stackwright", "pipeline-state.json");
|
|
2793
2980
|
}
|
|
2794
2981
|
function readState(cwd) {
|
|
2795
2982
|
const p = statePath(cwd);
|
|
2796
|
-
if (!(0,
|
|
2983
|
+
if (!(0, import_fs6.existsSync)(p)) return createDefaultState();
|
|
2797
2984
|
const raw = JSON.parse(safeReadSync(p));
|
|
2798
2985
|
if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
|
|
2799
2986
|
return createDefaultState();
|
|
@@ -2801,26 +2988,26 @@ function readState(cwd) {
|
|
|
2801
2988
|
return raw;
|
|
2802
2989
|
}
|
|
2803
2990
|
function safeWriteSync(filePath, content) {
|
|
2804
|
-
if ((0,
|
|
2805
|
-
const stat = (0,
|
|
2991
|
+
if ((0, import_fs6.existsSync)(filePath)) {
|
|
2992
|
+
const stat = (0, import_fs6.lstatSync)(filePath);
|
|
2806
2993
|
if (stat.isSymbolicLink()) {
|
|
2807
2994
|
throw new Error(`Refusing to write to symlink: ${filePath}`);
|
|
2808
2995
|
}
|
|
2809
2996
|
}
|
|
2810
|
-
(0,
|
|
2997
|
+
(0, import_fs6.writeFileSync)(filePath, content);
|
|
2811
2998
|
}
|
|
2812
2999
|
function safeReadSync(filePath) {
|
|
2813
|
-
if ((0,
|
|
2814
|
-
const stat = (0,
|
|
3000
|
+
if ((0, import_fs6.existsSync)(filePath)) {
|
|
3001
|
+
const stat = (0, import_fs6.lstatSync)(filePath);
|
|
2815
3002
|
if (stat.isSymbolicLink()) {
|
|
2816
3003
|
throw new Error(`Refusing to read symlink: ${filePath}`);
|
|
2817
3004
|
}
|
|
2818
3005
|
}
|
|
2819
|
-
return (0,
|
|
3006
|
+
return (0, import_fs6.readFileSync)(filePath, "utf-8");
|
|
2820
3007
|
}
|
|
2821
3008
|
function writeState(cwd, state) {
|
|
2822
|
-
const dir = (0,
|
|
2823
|
-
(0,
|
|
3009
|
+
const dir = (0, import_path6.join)(cwd, ".stackwright");
|
|
3010
|
+
(0, import_fs6.mkdirSync)(dir, { recursive: true });
|
|
2824
3011
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2825
3012
|
safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
2826
3013
|
}
|
|
@@ -2841,8 +3028,8 @@ function handleGetPipelineState(_cwd) {
|
|
|
2841
3028
|
const cwd = _cwd ?? process.cwd();
|
|
2842
3029
|
try {
|
|
2843
3030
|
const state = readState(cwd);
|
|
2844
|
-
const keyPath = (0,
|
|
2845
|
-
if (!(0,
|
|
3031
|
+
const keyPath = (0, import_path6.join)(cwd, ".stackwright", "pipeline-keys.json");
|
|
3032
|
+
if (!(0, import_fs6.existsSync)(keyPath)) {
|
|
2846
3033
|
try {
|
|
2847
3034
|
const { fingerprint } = initPipelineKeys(cwd);
|
|
2848
3035
|
state["signingKeyFingerprint"] = fingerprint;
|
|
@@ -2945,8 +3132,8 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
2945
3132
|
isError: true
|
|
2946
3133
|
};
|
|
2947
3134
|
}
|
|
2948
|
-
const answerFile = (0,
|
|
2949
|
-
if (!(0,
|
|
3135
|
+
const answerFile = (0, import_path6.join)(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3136
|
+
if (!(0, import_fs6.existsSync)(answerFile)) {
|
|
2950
3137
|
return {
|
|
2951
3138
|
text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
|
|
2952
3139
|
isError: false
|
|
@@ -2970,12 +3157,12 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
2970
3157
|
}
|
|
2971
3158
|
}
|
|
2972
3159
|
try {
|
|
2973
|
-
const answersDir = (0,
|
|
3160
|
+
const answersDir = (0, import_path6.join)(cwd, ".stackwright", "answers");
|
|
2974
3161
|
const answeredPhases = [];
|
|
2975
3162
|
const missingPhases = [];
|
|
2976
3163
|
for (const phase2 of PHASE_ORDER) {
|
|
2977
|
-
const answerFile = (0,
|
|
2978
|
-
if ((0,
|
|
3164
|
+
const answerFile = (0, import_path6.join)(answersDir, `${phase2}.json`);
|
|
3165
|
+
if ((0, import_fs6.existsSync)(answerFile)) {
|
|
2979
3166
|
try {
|
|
2980
3167
|
const raw = safeReadSync(answerFile);
|
|
2981
3168
|
const parsed = JSON.parse(raw);
|
|
@@ -3008,6 +3195,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3008
3195
|
const cwd = _cwd ?? process.cwd();
|
|
3009
3196
|
try {
|
|
3010
3197
|
const state = readState(cwd);
|
|
3198
|
+
const graph = loadPipelineGraph();
|
|
3011
3199
|
const completed = [];
|
|
3012
3200
|
const ready = [];
|
|
3013
3201
|
const blocked = [];
|
|
@@ -3017,7 +3205,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3017
3205
|
completed.push(phase);
|
|
3018
3206
|
continue;
|
|
3019
3207
|
}
|
|
3020
|
-
const deps =
|
|
3208
|
+
const deps = graph.dependencies[phase] ?? [];
|
|
3021
3209
|
const unmetDeps = deps.filter((dep) => !state.phases[dep]?.executed);
|
|
3022
3210
|
if (unmetDeps.length === 0) {
|
|
3023
3211
|
ready.push(phase);
|
|
@@ -3043,7 +3231,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3043
3231
|
function handleListArtifacts(_cwd) {
|
|
3044
3232
|
const cwd = _cwd ?? process.cwd();
|
|
3045
3233
|
try {
|
|
3046
|
-
const artifactsDir = (0,
|
|
3234
|
+
const artifactsDir = (0, import_path6.join)(cwd, ".stackwright", "artifacts");
|
|
3047
3235
|
let manifest = null;
|
|
3048
3236
|
try {
|
|
3049
3237
|
manifest = loadSignatureManifest(cwd);
|
|
@@ -3053,8 +3241,8 @@ function handleListArtifacts(_cwd) {
|
|
|
3053
3241
|
let completedCount = 0;
|
|
3054
3242
|
for (const phase of PHASE_ORDER) {
|
|
3055
3243
|
const expectedFile = PHASE_ARTIFACT[phase];
|
|
3056
|
-
const fullPath = (0,
|
|
3057
|
-
const exists = (0,
|
|
3244
|
+
const fullPath = (0, import_path6.join)(artifactsDir, expectedFile);
|
|
3245
|
+
const exists = (0, import_fs6.existsSync)(fullPath);
|
|
3058
3246
|
if (exists) completedCount++;
|
|
3059
3247
|
let signed = false;
|
|
3060
3248
|
let signatureValid = null;
|
|
@@ -3107,9 +3295,9 @@ function handleWritePhaseQuestions(input) {
|
|
|
3107
3295
|
}
|
|
3108
3296
|
} catch {
|
|
3109
3297
|
}
|
|
3110
|
-
const questionsDir = (0,
|
|
3111
|
-
(0,
|
|
3112
|
-
const filePath = (0,
|
|
3298
|
+
const questionsDir = (0, import_path6.join)(cwd, ".stackwright", "questions");
|
|
3299
|
+
(0, import_fs6.mkdirSync)(questionsDir, { recursive: true });
|
|
3300
|
+
const filePath = (0, import_path6.join)(questionsDir, `${phase}.json`);
|
|
3113
3301
|
const payload = {
|
|
3114
3302
|
version: "1.0",
|
|
3115
3303
|
phase,
|
|
@@ -3149,14 +3337,14 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3149
3337
|
};
|
|
3150
3338
|
}
|
|
3151
3339
|
try {
|
|
3152
|
-
const answersPath = (0,
|
|
3340
|
+
const answersPath = (0, import_path6.join)(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3153
3341
|
let answers = {};
|
|
3154
|
-
if ((0,
|
|
3342
|
+
if ((0, import_fs6.existsSync)(answersPath)) {
|
|
3155
3343
|
answers = JSON.parse(safeReadSync(answersPath));
|
|
3156
3344
|
}
|
|
3157
3345
|
let buildContextText = "";
|
|
3158
|
-
const buildContextPath = (0,
|
|
3159
|
-
if ((0,
|
|
3346
|
+
const buildContextPath = (0, import_path6.join)(cwd, ".stackwright", "build-context.json");
|
|
3347
|
+
if ((0, import_fs6.existsSync)(buildContextPath)) {
|
|
3160
3348
|
try {
|
|
3161
3349
|
const bcRaw = JSON.parse(safeReadSync(buildContextPath));
|
|
3162
3350
|
if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
|
|
@@ -3165,13 +3353,14 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3165
3353
|
} catch {
|
|
3166
3354
|
}
|
|
3167
3355
|
}
|
|
3168
|
-
const
|
|
3356
|
+
const graph = loadPipelineGraph();
|
|
3357
|
+
const deps = graph.dependencies[phase] ?? [];
|
|
3169
3358
|
const artifactSections = [];
|
|
3170
3359
|
const missingDependencies = [];
|
|
3171
3360
|
for (const dep of deps) {
|
|
3172
3361
|
const artifactFile = PHASE_ARTIFACT[dep];
|
|
3173
|
-
const artifactPath = (0,
|
|
3174
|
-
if ((0,
|
|
3362
|
+
const artifactPath = (0, import_path6.join)(cwd, ".stackwright", "artifacts", artifactFile);
|
|
3363
|
+
if ((0, import_fs6.existsSync)(artifactPath)) {
|
|
3175
3364
|
const rawContent = safeReadSync(artifactPath);
|
|
3176
3365
|
const rawBytes = Buffer.from(rawContent, "utf-8");
|
|
3177
3366
|
const content = JSON.parse(rawContent);
|
|
@@ -3236,8 +3425,8 @@ ${JSON.stringify(content, null, 2)}`
|
|
|
3236
3425
|
parts.push("BUILD_CONTEXT:", buildContextText, "");
|
|
3237
3426
|
}
|
|
3238
3427
|
if (phase === "auth") {
|
|
3239
|
-
const initContextPath = (0,
|
|
3240
|
-
if ((0,
|
|
3428
|
+
const initContextPath = (0, import_path6.join)(cwd, ".stackwright", "init-context.json");
|
|
3429
|
+
if ((0, import_fs6.existsSync)(initContextPath)) {
|
|
3241
3430
|
try {
|
|
3242
3431
|
const initContext = JSON.parse(safeReadSync(initContextPath));
|
|
3243
3432
|
if (initContext.devOnly === true || initContext.nonInteractive === true) {
|
|
@@ -3704,13 +3893,13 @@ function handleValidateArtifact(input) {
|
|
|
3704
3893
|
// to a flow or state-machine in this services-config artifact.
|
|
3705
3894
|
// Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
|
|
3706
3895
|
services: (artifact2) => {
|
|
3707
|
-
const workflowArtifactPath = (0,
|
|
3708
|
-
if (!(0,
|
|
3896
|
+
const workflowArtifactPath = (0, import_path6.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
3897
|
+
if (!(0, import_fs6.existsSync)(workflowArtifactPath)) {
|
|
3709
3898
|
return { success: true };
|
|
3710
3899
|
}
|
|
3711
3900
|
let workflowArtifact;
|
|
3712
3901
|
try {
|
|
3713
|
-
workflowArtifact = JSON.parse((0,
|
|
3902
|
+
workflowArtifact = JSON.parse((0, import_fs6.readFileSync)(workflowArtifactPath, "utf-8"));
|
|
3714
3903
|
} catch {
|
|
3715
3904
|
return { success: true };
|
|
3716
3905
|
}
|
|
@@ -3747,10 +3936,10 @@ function handleValidateArtifact(input) {
|
|
|
3747
3936
|
}
|
|
3748
3937
|
}
|
|
3749
3938
|
try {
|
|
3750
|
-
const artifactsDir = (0,
|
|
3751
|
-
(0,
|
|
3939
|
+
const artifactsDir = (0, import_path6.join)(cwd, ".stackwright", "artifacts");
|
|
3940
|
+
(0, import_fs6.mkdirSync)(artifactsDir, { recursive: true });
|
|
3752
3941
|
const artifactFile = PHASE_ARTIFACT[phase];
|
|
3753
|
-
const artifactPath = (0,
|
|
3942
|
+
const artifactPath = (0, import_path6.join)(artifactsDir, artifactFile);
|
|
3754
3943
|
const serialized = JSON.stringify(artifact, null, 2) + "\n";
|
|
3755
3944
|
const artifactBytes = Buffer.from(serialized, "utf-8");
|
|
3756
3945
|
safeWriteSync(artifactPath, serialized);
|
|
@@ -3867,10 +4056,10 @@ function registerPipelineTools(server2) {
|
|
|
3867
4056
|
isError: true
|
|
3868
4057
|
};
|
|
3869
4058
|
}
|
|
3870
|
-
const questionsDir = (0,
|
|
3871
|
-
(0,
|
|
3872
|
-
const outPath = (0,
|
|
3873
|
-
(0,
|
|
4059
|
+
const questionsDir = (0, import_path6.join)(process.cwd(), ".stackwright", "questions");
|
|
4060
|
+
(0, import_fs6.mkdirSync)(questionsDir, { recursive: true });
|
|
4061
|
+
const outPath = (0, import_path6.join)(questionsDir, `${phase}.json`);
|
|
4062
|
+
(0, import_fs6.writeFileSync)(
|
|
3874
4063
|
outPath,
|
|
3875
4064
|
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
3876
4065
|
);
|
|
@@ -3921,8 +4110,8 @@ function registerPipelineTools(server2) {
|
|
|
3921
4110
|
|
|
3922
4111
|
// src/tools/safe-write.ts
|
|
3923
4112
|
var import_zod14 = require("zod");
|
|
3924
|
-
var
|
|
3925
|
-
var
|
|
4113
|
+
var import_fs7 = require("fs");
|
|
4114
|
+
var import_path7 = require("path");
|
|
3926
4115
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
3927
4116
|
"stackwright-pro-designer-otter": [
|
|
3928
4117
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Design language artifact" }
|
|
@@ -3937,8 +4126,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3937
4126
|
],
|
|
3938
4127
|
"stackwright-pro-auth-otter": [
|
|
3939
4128
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
|
|
3940
|
-
{
|
|
3941
|
-
|
|
4129
|
+
{
|
|
4130
|
+
prefix: "stackwright.auth.",
|
|
4131
|
+
suffix: ".yml",
|
|
4132
|
+
description: "Auth config YAML (stackwright.auth.yml \u2014 compiled to _auth.json)"
|
|
4133
|
+
},
|
|
3942
4134
|
{
|
|
3943
4135
|
prefix: ".env",
|
|
3944
4136
|
suffix: "",
|
|
@@ -3957,7 +4149,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3957
4149
|
],
|
|
3958
4150
|
"stackwright-pro-data-otter": [
|
|
3959
4151
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Data config artifact" },
|
|
3960
|
-
{
|
|
4152
|
+
{
|
|
4153
|
+
prefix: "stackwright.collections.",
|
|
4154
|
+
suffix: ".yml",
|
|
4155
|
+
description: "Collections config YAML (stackwright.collections.yml \u2014 compiled to _collections.json)"
|
|
4156
|
+
}
|
|
3961
4157
|
],
|
|
3962
4158
|
"stackwright-pro-page-otter": [
|
|
3963
4159
|
{ prefix: "pages/", suffix: "/content.yml", description: "Page content YAML" },
|
|
@@ -3983,7 +4179,12 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3983
4179
|
}
|
|
3984
4180
|
],
|
|
3985
4181
|
"stackwright-pro-api-otter": [
|
|
3986
|
-
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" }
|
|
4182
|
+
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" },
|
|
4183
|
+
{
|
|
4184
|
+
prefix: "stackwright.integrations.",
|
|
4185
|
+
suffix: ".yml",
|
|
4186
|
+
description: "Integrations config YAML (stackwright.integrations.yml \u2014 compiled to _integrations.json)"
|
|
4187
|
+
}
|
|
3987
4188
|
],
|
|
3988
4189
|
"stackwright-pro-geo-otter": [
|
|
3989
4190
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Geo manifest artifact" },
|
|
@@ -4046,7 +4247,7 @@ function getMaxBytesForPath(filePath) {
|
|
|
4046
4247
|
}
|
|
4047
4248
|
var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
|
|
4048
4249
|
function extractPageSlug(filePath) {
|
|
4049
|
-
const match = (0,
|
|
4250
|
+
const match = (0, import_path7.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
|
|
4050
4251
|
return match?.[1] ?? null;
|
|
4051
4252
|
}
|
|
4052
4253
|
function extractContentTypes(yamlContent) {
|
|
@@ -4062,27 +4263,27 @@ function extractContentTypes(yamlContent) {
|
|
|
4062
4263
|
return types.length > 0 ? types : ["unknown"];
|
|
4063
4264
|
}
|
|
4064
4265
|
function readPageRegistry(cwd) {
|
|
4065
|
-
const regPath = (0,
|
|
4066
|
-
if (!(0,
|
|
4266
|
+
const regPath = (0, import_path7.join)(cwd, PAGE_REGISTRY_FILE);
|
|
4267
|
+
if (!(0, import_fs7.existsSync)(regPath)) return {};
|
|
4067
4268
|
try {
|
|
4068
|
-
const stat = (0,
|
|
4269
|
+
const stat = (0, import_fs7.lstatSync)(regPath);
|
|
4069
4270
|
if (stat.isSymbolicLink()) return {};
|
|
4070
|
-
return JSON.parse((0,
|
|
4271
|
+
return JSON.parse((0, import_fs7.readFileSync)(regPath, "utf-8"));
|
|
4071
4272
|
} catch {
|
|
4072
4273
|
return {};
|
|
4073
4274
|
}
|
|
4074
4275
|
}
|
|
4075
4276
|
function writePageRegistry(cwd, registry) {
|
|
4076
|
-
const dir = (0,
|
|
4077
|
-
(0,
|
|
4078
|
-
const regPath = (0,
|
|
4079
|
-
if ((0,
|
|
4080
|
-
const stat = (0,
|
|
4277
|
+
const dir = (0, import_path7.join)(cwd, ".stackwright");
|
|
4278
|
+
(0, import_fs7.mkdirSync)(dir, { recursive: true });
|
|
4279
|
+
const regPath = (0, import_path7.join)(cwd, PAGE_REGISTRY_FILE);
|
|
4280
|
+
if ((0, import_fs7.existsSync)(regPath)) {
|
|
4281
|
+
const stat = (0, import_fs7.lstatSync)(regPath);
|
|
4081
4282
|
if (stat.isSymbolicLink()) {
|
|
4082
4283
|
throw new Error("Refusing to write page registry through symlink");
|
|
4083
4284
|
}
|
|
4084
4285
|
}
|
|
4085
|
-
(0,
|
|
4286
|
+
(0, import_fs7.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
|
|
4086
4287
|
}
|
|
4087
4288
|
function checkPageOwnership(cwd, slug, callerOtter) {
|
|
4088
4289
|
const registry = readPageRegistry(cwd);
|
|
@@ -4097,15 +4298,15 @@ function checkPageOwnership(cwd, slug, callerOtter) {
|
|
|
4097
4298
|
};
|
|
4098
4299
|
}
|
|
4099
4300
|
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
|
|
4301
|
+
function stripNextjsBracketSegments(path4) {
|
|
4302
|
+
return path4.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
|
|
4102
4303
|
}
|
|
4103
4304
|
function checkPathAllowed(callerOtter, filePath) {
|
|
4104
|
-
const normalized = (0,
|
|
4305
|
+
const normalized = (0, import_path7.normalize)(filePath);
|
|
4105
4306
|
if (stripNextjsBracketSegments(normalized).includes("..")) {
|
|
4106
4307
|
return { allowed: false, error: 'Path traversal detected: ".." segments are not allowed' };
|
|
4107
4308
|
}
|
|
4108
|
-
if ((0,
|
|
4309
|
+
if ((0, import_path7.isAbsolute)(normalized)) {
|
|
4109
4310
|
return {
|
|
4110
4311
|
allowed: false,
|
|
4111
4312
|
error: "Absolute paths are not allowed \u2014 use paths relative to project root"
|
|
@@ -4247,11 +4448,11 @@ function handleSafeWrite(input) {
|
|
|
4247
4448
|
};
|
|
4248
4449
|
return { text: JSON.stringify(result), isError: true };
|
|
4249
4450
|
}
|
|
4250
|
-
const normalized = (0,
|
|
4251
|
-
const fullPath = (0,
|
|
4252
|
-
if ((0,
|
|
4451
|
+
const normalized = (0, import_path7.normalize)(filePath);
|
|
4452
|
+
const fullPath = (0, import_path7.join)(cwd, normalized);
|
|
4453
|
+
if ((0, import_fs7.existsSync)(fullPath)) {
|
|
4253
4454
|
try {
|
|
4254
|
-
const stat = (0,
|
|
4455
|
+
const stat = (0, import_fs7.lstatSync)(fullPath);
|
|
4255
4456
|
if (stat.isSymbolicLink()) {
|
|
4256
4457
|
const result = {
|
|
4257
4458
|
success: false,
|
|
@@ -4292,9 +4493,9 @@ function handleSafeWrite(input) {
|
|
|
4292
4493
|
}
|
|
4293
4494
|
try {
|
|
4294
4495
|
if (createDirectories) {
|
|
4295
|
-
(0,
|
|
4496
|
+
(0, import_fs7.mkdirSync)((0, import_path7.dirname)(fullPath), { recursive: true });
|
|
4296
4497
|
}
|
|
4297
|
-
(0,
|
|
4498
|
+
(0, import_fs7.writeFileSync)(fullPath, content, { encoding: "utf-8" });
|
|
4298
4499
|
if (pageSlug) {
|
|
4299
4500
|
try {
|
|
4300
4501
|
const registry = readPageRegistry(cwd);
|
|
@@ -4354,8 +4555,8 @@ function registerSafeWriteTools(server2) {
|
|
|
4354
4555
|
|
|
4355
4556
|
// src/tools/auth.ts
|
|
4356
4557
|
var import_zod15 = require("zod");
|
|
4357
|
-
var
|
|
4358
|
-
var
|
|
4558
|
+
var import_fs8 = require("fs");
|
|
4559
|
+
var import_path8 = require("path");
|
|
4359
4560
|
function buildHierarchy(roles) {
|
|
4360
4561
|
const h = {};
|
|
4361
4562
|
for (let i = 0; i < roles.length - 1; i++) {
|
|
@@ -4369,7 +4570,7 @@ function hierarchyToYaml(hierarchy, indent) {
|
|
|
4369
4570
|
return entries.map(([role, subs]) => `${indent}${role}: [${subs.join(", ")}]`).join("\n");
|
|
4370
4571
|
}
|
|
4371
4572
|
function rolesToYaml(roles, indent) {
|
|
4372
|
-
return roles.map((r) => `${indent}- ${r}`).join("\n");
|
|
4573
|
+
return roles.map((r) => `${indent}- name: ${r}`).join("\n");
|
|
4373
4574
|
}
|
|
4374
4575
|
function normalizeRoutes(routes, defaultRole) {
|
|
4375
4576
|
return routes.map(
|
|
@@ -4382,9 +4583,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
|
|
|
4382
4583
|
}
|
|
4383
4584
|
function detectNextMajorVersion(cwd) {
|
|
4384
4585
|
try {
|
|
4385
|
-
const pkgPath = (0,
|
|
4386
|
-
if (!(0,
|
|
4387
|
-
const pkg = JSON.parse((0,
|
|
4586
|
+
const pkgPath = (0, import_path8.join)(cwd, "package.json");
|
|
4587
|
+
if (!(0, import_fs8.existsSync)(pkgPath)) return null;
|
|
4588
|
+
const pkg = JSON.parse((0, import_fs8.readFileSync)(pkgPath, "utf8"));
|
|
4388
4589
|
const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
4389
4590
|
if (!nextVersion) return null;
|
|
4390
4591
|
const match = nextVersion.match(/(\d+)/);
|
|
@@ -4393,25 +4594,6 @@ function detectNextMajorVersion(cwd) {
|
|
|
4393
4594
|
return null;
|
|
4394
4595
|
}
|
|
4395
4596
|
}
|
|
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
4597
|
function generateMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4416
4598
|
const rbacBlock = ` rbac: {
|
|
4417
4599
|
roles: ${JSON.stringify(roles)},
|
|
@@ -4611,33 +4793,33 @@ OAUTH2_CLIENT_SECRET=your-client-secret
|
|
|
4611
4793
|
`;
|
|
4612
4794
|
}
|
|
4613
4795
|
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 ? `
|
|
4796
|
+
const rbacSection = `rbac:
|
|
4797
|
+
roles:
|
|
4798
|
+
${rolesToYaml(roles, " ")}
|
|
4799
|
+
defaultRole: ${defaultRole}
|
|
4800
|
+
hierarchy:
|
|
4801
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
4802
|
+
const auditSection = `audit:
|
|
4803
|
+
enabled: ${auditEnabled}
|
|
4804
|
+
retentionDays: ${auditRetentionDays}`;
|
|
4805
|
+
const routeLines = routesToYaml(protectedRoutes, " ");
|
|
4806
|
+
const providerLine = params.provider ? `provider: ${params.provider}
|
|
4625
4807
|
` : "";
|
|
4626
4808
|
if (method === "cac") {
|
|
4627
4809
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4628
4810
|
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4629
4811
|
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4630
4812
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4631
|
-
return
|
|
4632
|
-
|
|
4633
|
-
${providerLine}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4813
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4814
|
+
method: cac
|
|
4815
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4816
|
+
cac:
|
|
4817
|
+
caBundle: \${CAC_CA_BUNDLE}
|
|
4818
|
+
edipiLookup: ${edipiLookup}
|
|
4819
|
+
ocspEndpoint: \${CAC_OCSP_ENDPOINT}
|
|
4820
|
+
certHeader: ${certHeader}
|
|
4639
4821
|
${rbacSection}
|
|
4640
|
-
|
|
4822
|
+
protectedRoutes:
|
|
4641
4823
|
${routeLines}
|
|
4642
4824
|
${auditSection}
|
|
4643
4825
|
`;
|
|
@@ -4645,33 +4827,33 @@ ${auditSection}
|
|
|
4645
4827
|
if (method === "oidc") {
|
|
4646
4828
|
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4647
4829
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4648
|
-
return
|
|
4649
|
-
|
|
4650
|
-
${providerLine}
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4830
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4831
|
+
method: oidc
|
|
4832
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4833
|
+
oidc:
|
|
4834
|
+
discoveryUrl: \${OIDC_DISCOVERY_URL}
|
|
4835
|
+
clientId: \${OIDC_CLIENT_ID}
|
|
4836
|
+
clientSecret: \${OIDC_CLIENT_SECRET}
|
|
4837
|
+
scopes: ${scopes2}
|
|
4838
|
+
roleClaim: ${roleClaim}
|
|
4657
4839
|
${rbacSection}
|
|
4658
|
-
|
|
4840
|
+
protectedRoutes:
|
|
4659
4841
|
${routeLines}
|
|
4660
4842
|
${auditSection}
|
|
4661
4843
|
`;
|
|
4662
4844
|
}
|
|
4663
4845
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4664
|
-
return
|
|
4665
|
-
|
|
4666
|
-
${providerLine}
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4846
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4847
|
+
method: oauth2
|
|
4848
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4849
|
+
oauth2:
|
|
4850
|
+
authorizationUrl: \${OAUTH2_AUTH_URL}
|
|
4851
|
+
tokenUrl: \${OAUTH2_TOKEN_URL}
|
|
4852
|
+
clientId: \${OAUTH2_CLIENT_ID}
|
|
4853
|
+
clientSecret: \${OAUTH2_CLIENT_SECRET}
|
|
4854
|
+
scopes: ${scopes}
|
|
4673
4855
|
${rbacSection}
|
|
4674
|
-
|
|
4856
|
+
protectedRoutes:
|
|
4675
4857
|
${routeLines}
|
|
4676
4858
|
${auditSection}
|
|
4677
4859
|
`;
|
|
@@ -4857,35 +5039,35 @@ ${configBlock}
|
|
|
4857
5039
|
`;
|
|
4858
5040
|
}
|
|
4859
5041
|
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 ? `
|
|
5042
|
+
const rbacSection = `rbac:
|
|
5043
|
+
roles:
|
|
5044
|
+
${rolesToYaml(roles, " ")}
|
|
5045
|
+
defaultRole: ${defaultRole}
|
|
5046
|
+
hierarchy:
|
|
5047
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
5048
|
+
const auditSection = `audit:
|
|
5049
|
+
enabled: ${auditEnabled}
|
|
5050
|
+
retentionDays: ${auditRetentionDays}`;
|
|
5051
|
+
const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
|
|
5052
|
+
requiredRole: ${r.requiredRole}`).join("\n");
|
|
5053
|
+
const providerLine = params.provider ? `provider: ${params.provider}
|
|
4872
5054
|
` : "";
|
|
4873
5055
|
if (method === "cac") {
|
|
4874
5056
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4875
5057
|
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4876
5058
|
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4877
5059
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4878
|
-
return
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
${providerLine}
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
5060
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5061
|
+
method: cac
|
|
5062
|
+
devOnly: true
|
|
5063
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5064
|
+
cac:
|
|
5065
|
+
caBundle: ${caBundle}
|
|
5066
|
+
edipiLookup: ${edipiLookup}
|
|
5067
|
+
ocspEndpoint: ${ocspEndpoint}
|
|
5068
|
+
certHeader: ${certHeader}
|
|
4887
5069
|
${rbacSection}
|
|
4888
|
-
|
|
5070
|
+
protectedRoutes:
|
|
4889
5071
|
${routeLines}
|
|
4890
5072
|
${auditSection}
|
|
4891
5073
|
`;
|
|
@@ -4893,35 +5075,35 @@ ${auditSection}
|
|
|
4893
5075
|
if (method === "oidc") {
|
|
4894
5076
|
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4895
5077
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4896
|
-
return
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
${providerLine}
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
5078
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5079
|
+
method: oidc
|
|
5080
|
+
devOnly: true
|
|
5081
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5082
|
+
oidc:
|
|
5083
|
+
discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
|
|
5084
|
+
clientId: dev-mock-client
|
|
5085
|
+
clientSecret: dev-mock-secret
|
|
5086
|
+
scopes: ${scopes2}
|
|
5087
|
+
roleClaim: ${roleClaim}
|
|
4906
5088
|
${rbacSection}
|
|
4907
|
-
|
|
5089
|
+
protectedRoutes:
|
|
4908
5090
|
${routeLines}
|
|
4909
5091
|
${auditSection}
|
|
4910
5092
|
`;
|
|
4911
5093
|
}
|
|
4912
5094
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4913
|
-
return
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
${providerLine}
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
5095
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5096
|
+
method: oauth2
|
|
5097
|
+
devOnly: true
|
|
5098
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5099
|
+
oauth2:
|
|
5100
|
+
authorizationUrl: https://dev-mock-oauth2/authorize
|
|
5101
|
+
tokenUrl: https://dev-mock-oauth2/token
|
|
5102
|
+
clientId: dev-mock-client
|
|
5103
|
+
clientSecret: dev-mock-secret
|
|
5104
|
+
scopes: ${scopes}
|
|
4923
5105
|
${rbacSection}
|
|
4924
|
-
|
|
5106
|
+
protectedRoutes:
|
|
4925
5107
|
${routeLines}
|
|
4926
5108
|
${auditSection}
|
|
4927
5109
|
`;
|
|
@@ -5066,7 +5248,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5066
5248
|
auditRetentionDays,
|
|
5067
5249
|
normalizedRoutes
|
|
5068
5250
|
);
|
|
5069
|
-
(0,
|
|
5251
|
+
(0, import_fs8.writeFileSync)((0, import_path8.join)(cwd, conventionFile), authFileContent, "utf8");
|
|
5070
5252
|
filesWritten.push(conventionFile);
|
|
5071
5253
|
} catch (err) {
|
|
5072
5254
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5086,12 +5268,12 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5086
5268
|
if (!devOnly) {
|
|
5087
5269
|
try {
|
|
5088
5270
|
const envBlock = generateEnvBlock(effectiveMethod, params);
|
|
5089
|
-
const envPath = (0,
|
|
5090
|
-
if ((0,
|
|
5091
|
-
const existing = (0,
|
|
5092
|
-
(0,
|
|
5271
|
+
const envPath = (0, import_path8.join)(cwd, ".env.example");
|
|
5272
|
+
if ((0, import_fs8.existsSync)(envPath)) {
|
|
5273
|
+
const existing = (0, import_fs8.readFileSync)(envPath, "utf8");
|
|
5274
|
+
(0, import_fs8.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
5093
5275
|
} else {
|
|
5094
|
-
(0,
|
|
5276
|
+
(0, import_fs8.writeFileSync)(envPath, envBlock, "utf8");
|
|
5095
5277
|
}
|
|
5096
5278
|
filesWritten.push(".env.example");
|
|
5097
5279
|
} catch (err) {
|
|
@@ -5109,10 +5291,10 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5109
5291
|
}
|
|
5110
5292
|
if (devOnly) {
|
|
5111
5293
|
try {
|
|
5112
|
-
const mockAuthDir = (0,
|
|
5113
|
-
(0,
|
|
5294
|
+
const mockAuthDir = (0, import_path8.join)(cwd, "lib");
|
|
5295
|
+
(0, import_fs8.mkdirSync)(mockAuthDir, { recursive: true });
|
|
5114
5296
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
5115
|
-
(0,
|
|
5297
|
+
(0, import_fs8.writeFileSync)((0, import_path8.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
5116
5298
|
filesWritten.push("lib/mock-auth.ts");
|
|
5117
5299
|
} catch (err) {
|
|
5118
5300
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5130,11 +5312,11 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5130
5312
|
};
|
|
5131
5313
|
}
|
|
5132
5314
|
try {
|
|
5133
|
-
const pkgPath = (0,
|
|
5134
|
-
if ((0,
|
|
5135
|
-
const existingPkg = (0,
|
|
5315
|
+
const pkgPath = (0, import_path8.join)(cwd, "package.json");
|
|
5316
|
+
if ((0, import_fs8.existsSync)(pkgPath)) {
|
|
5317
|
+
const existingPkg = (0, import_fs8.readFileSync)(pkgPath, "utf8");
|
|
5136
5318
|
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
5137
|
-
(0,
|
|
5319
|
+
(0, import_fs8.writeFileSync)(pkgPath, updatedPkg, "utf8");
|
|
5138
5320
|
filesWritten.push("package.json");
|
|
5139
5321
|
}
|
|
5140
5322
|
} catch (err) {
|
|
@@ -5175,21 +5357,18 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5175
5357
|
normalizedRoutes,
|
|
5176
5358
|
useProxy
|
|
5177
5359
|
);
|
|
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");
|
|
5360
|
+
(0, import_fs8.writeFileSync)((0, import_path8.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
|
|
5361
|
+
filesWritten.push("stackwright.auth.yml");
|
|
5186
5362
|
} catch (err) {
|
|
5187
5363
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5188
5364
|
return {
|
|
5189
5365
|
content: [
|
|
5190
5366
|
{
|
|
5191
5367
|
type: "text",
|
|
5192
|
-
text: JSON.stringify({
|
|
5368
|
+
text: JSON.stringify({
|
|
5369
|
+
success: false,
|
|
5370
|
+
error: `Failed writing stackwright.auth.yml: ${msg}`
|
|
5371
|
+
})
|
|
5193
5372
|
}
|
|
5194
5373
|
],
|
|
5195
5374
|
isError: true
|
|
@@ -5234,7 +5413,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5234
5413
|
function registerAuthTools(server2) {
|
|
5235
5414
|
server2.tool(
|
|
5236
5415
|
"stackwright_pro_configure_auth",
|
|
5237
|
-
"Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` from a secure template,
|
|
5416
|
+
"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
5417
|
{
|
|
5239
5418
|
method: import_zod15.z.enum(["cac", "oidc", "oauth2", "none"]),
|
|
5240
5419
|
provider: import_zod15.z.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
|
|
@@ -5285,28 +5464,28 @@ function registerAuthTools(server2) {
|
|
|
5285
5464
|
|
|
5286
5465
|
// src/integrity.ts
|
|
5287
5466
|
var import_crypto4 = require("crypto");
|
|
5288
|
-
var
|
|
5289
|
-
var
|
|
5467
|
+
var import_fs9 = require("fs");
|
|
5468
|
+
var import_path9 = require("path");
|
|
5290
5469
|
var _checksums = /* @__PURE__ */ new Map([
|
|
5291
5470
|
[
|
|
5292
5471
|
"stackwright-pro-api-otter.json",
|
|
5293
|
-
"
|
|
5472
|
+
"822b35d7a330ed8ac0b42a63b0f70a41885aa9b5ea23cc5b8b998b549da4c05c"
|
|
5294
5473
|
],
|
|
5295
5474
|
[
|
|
5296
5475
|
"stackwright-pro-auth-otter.json",
|
|
5297
|
-
"
|
|
5476
|
+
"d6cd5732667018d99456be77d5955e454da7a0999a0330b5f03a483aa1b9b33f"
|
|
5298
5477
|
],
|
|
5299
5478
|
[
|
|
5300
5479
|
"stackwright-pro-dashboard-otter.json",
|
|
5301
|
-
"
|
|
5480
|
+
"e3b82555fcffbd77285bd304828814e9f9f7257130797c9de3785de97527668c"
|
|
5302
5481
|
],
|
|
5303
5482
|
[
|
|
5304
5483
|
"stackwright-pro-data-otter.json",
|
|
5305
|
-
"
|
|
5484
|
+
"bb66eaab31c6872536dca4d3ff145724a46356946dd0665cc4f0346714d3bacb"
|
|
5306
5485
|
],
|
|
5307
5486
|
[
|
|
5308
5487
|
"stackwright-pro-designer-otter.json",
|
|
5309
|
-
"
|
|
5488
|
+
"b54121c6a2ab5b1ad68c1262c58b2e04e6315feacd6bb8de8e78eb36f2fa6be6"
|
|
5310
5489
|
],
|
|
5311
5490
|
[
|
|
5312
5491
|
"stackwright-pro-domain-expert-otter.json",
|
|
@@ -5314,35 +5493,35 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
5314
5493
|
],
|
|
5315
5494
|
[
|
|
5316
5495
|
"stackwright-pro-foreman-otter.json",
|
|
5317
|
-
"
|
|
5496
|
+
"abb1cc5e40a8c5ff98057273f43a9e90e2791a15951090cd4d98407c0c3618e3"
|
|
5318
5497
|
],
|
|
5319
5498
|
[
|
|
5320
5499
|
"stackwright-pro-form-wizard-otter.json",
|
|
5321
|
-
"
|
|
5500
|
+
"975ad68e8fb6fe5a10373be278946fa4d9d7ec2238a0b2bd9e4a412e5b1fdd6d"
|
|
5322
5501
|
],
|
|
5323
5502
|
[
|
|
5324
5503
|
"stackwright-pro-geo-otter.json",
|
|
5325
|
-
"
|
|
5504
|
+
"ac440da786d8c5ab1feddbf861449c3be3a433a04370578e1b2d35bf6ae7a601"
|
|
5326
5505
|
],
|
|
5327
5506
|
[
|
|
5328
5507
|
"stackwright-pro-page-otter.json",
|
|
5329
|
-
"
|
|
5508
|
+
"b70bd6e5d2a40230f7c24ff4a6113585a1614e5b6a215ece2bd47ff053c30f58"
|
|
5330
5509
|
],
|
|
5331
5510
|
[
|
|
5332
5511
|
"stackwright-pro-polish-otter.json",
|
|
5333
|
-
"
|
|
5512
|
+
"48ef3cf7f29ac6b5e8ab2004d4d41e2fb899e781689178d3905993241c84095a"
|
|
5334
5513
|
],
|
|
5335
5514
|
[
|
|
5336
5515
|
"stackwright-pro-scaffold-otter.json",
|
|
5337
|
-
"
|
|
5516
|
+
"e8662b63bbc9ce38851d94f9656bbebefdb7843d038862adb9fde2c23f9e77c2"
|
|
5338
5517
|
],
|
|
5339
5518
|
[
|
|
5340
5519
|
"stackwright-pro-theme-otter.json",
|
|
5341
|
-
"
|
|
5520
|
+
"fb62e56642f7f94c50ce173f3e1ce978b3f20ab1567e87403b901d6dd991a7db"
|
|
5342
5521
|
],
|
|
5343
5522
|
[
|
|
5344
5523
|
"stackwright-services-otter.json",
|
|
5345
|
-
"
|
|
5524
|
+
"0a5dac670482871c718099767b30bf23d8ec57e942bd40a00ce295926d82c6bd"
|
|
5346
5525
|
]
|
|
5347
5526
|
]);
|
|
5348
5527
|
Object.freeze(_checksums);
|
|
@@ -5364,14 +5543,14 @@ function safeEqual(a, b) {
|
|
|
5364
5543
|
return (0, import_crypto4.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
|
|
5365
5544
|
}
|
|
5366
5545
|
function verifyOtterFile(filePath) {
|
|
5367
|
-
const filename = (0,
|
|
5546
|
+
const filename = (0, import_path9.basename)(filePath);
|
|
5368
5547
|
const expected = CANONICAL_CHECKSUMS.get(filename);
|
|
5369
5548
|
if (expected === void 0) {
|
|
5370
5549
|
return { verified: false, filename, error: `Unknown otter file: not in canonical set` };
|
|
5371
5550
|
}
|
|
5372
5551
|
let stat;
|
|
5373
5552
|
try {
|
|
5374
|
-
stat = (0,
|
|
5553
|
+
stat = (0, import_fs9.lstatSync)(filePath);
|
|
5375
5554
|
} catch (err) {
|
|
5376
5555
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5377
5556
|
return { verified: false, filename, error: `Cannot stat file: ${msg}` };
|
|
@@ -5389,7 +5568,7 @@ function verifyOtterFile(filePath) {
|
|
|
5389
5568
|
}
|
|
5390
5569
|
let raw;
|
|
5391
5570
|
try {
|
|
5392
|
-
raw = (0,
|
|
5571
|
+
raw = (0, import_fs9.readFileSync)(filePath);
|
|
5393
5572
|
} catch (err) {
|
|
5394
5573
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5395
5574
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -5439,7 +5618,7 @@ function verifyAllOtters(otterDir) {
|
|
|
5439
5618
|
const unknown = [];
|
|
5440
5619
|
let entries;
|
|
5441
5620
|
try {
|
|
5442
|
-
entries = (0,
|
|
5621
|
+
entries = (0, import_fs9.readdirSync)(otterDir);
|
|
5443
5622
|
} catch (err) {
|
|
5444
5623
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5445
5624
|
return {
|
|
@@ -5450,9 +5629,9 @@ function verifyAllOtters(otterDir) {
|
|
|
5450
5629
|
}
|
|
5451
5630
|
const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
|
|
5452
5631
|
for (const filename of otterFiles) {
|
|
5453
|
-
const filePath = (0,
|
|
5632
|
+
const filePath = (0, import_path9.join)(otterDir, filename);
|
|
5454
5633
|
try {
|
|
5455
|
-
if ((0,
|
|
5634
|
+
if ((0, import_fs9.lstatSync)(filePath).isSymbolicLink()) {
|
|
5456
5635
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
5457
5636
|
continue;
|
|
5458
5637
|
}
|
|
@@ -5478,9 +5657,9 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
|
|
|
5478
5657
|
function resolveOtterDir() {
|
|
5479
5658
|
const cwd = process.cwd();
|
|
5480
5659
|
for (const relative of DEFAULT_SEARCH_PATHS) {
|
|
5481
|
-
const candidate = (0,
|
|
5660
|
+
const candidate = (0, import_path9.join)(cwd, relative);
|
|
5482
5661
|
try {
|
|
5483
|
-
(0,
|
|
5662
|
+
(0, import_fs9.lstatSync)(candidate);
|
|
5484
5663
|
return candidate;
|
|
5485
5664
|
} catch {
|
|
5486
5665
|
}
|
|
@@ -5559,13 +5738,13 @@ function registerIntegrityTools(server2) {
|
|
|
5559
5738
|
|
|
5560
5739
|
// src/tools/domain.ts
|
|
5561
5740
|
var import_zod16 = require("zod");
|
|
5562
|
-
var
|
|
5563
|
-
var
|
|
5741
|
+
var import_fs10 = require("fs");
|
|
5742
|
+
var import_path10 = require("path");
|
|
5564
5743
|
function handleListCollections(input) {
|
|
5565
5744
|
const cwd = input._cwd ?? process.cwd();
|
|
5566
5745
|
const sources = [
|
|
5567
5746
|
{
|
|
5568
|
-
path: (0,
|
|
5747
|
+
path: (0, import_path10.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
|
|
5569
5748
|
source: "data-config.json",
|
|
5570
5749
|
parse: (raw) => {
|
|
5571
5750
|
const parsed = JSON.parse(raw);
|
|
@@ -5576,15 +5755,15 @@ function handleListCollections(input) {
|
|
|
5576
5755
|
}
|
|
5577
5756
|
},
|
|
5578
5757
|
{
|
|
5579
|
-
path: (0,
|
|
5758
|
+
path: (0, import_path10.join)(cwd, "stackwright.yml"),
|
|
5580
5759
|
source: "stackwright.yml",
|
|
5581
5760
|
parse: extractCollectionsFromYaml
|
|
5582
5761
|
}
|
|
5583
5762
|
];
|
|
5584
|
-
for (const { path:
|
|
5585
|
-
if (!(0,
|
|
5763
|
+
for (const { path: path4, source, parse } of sources) {
|
|
5764
|
+
if (!(0, import_fs10.existsSync)(path4)) continue;
|
|
5586
5765
|
try {
|
|
5587
|
-
const collections = parse((0,
|
|
5766
|
+
const collections = parse((0, import_fs10.readFileSync)(path4, "utf8"));
|
|
5588
5767
|
return {
|
|
5589
5768
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
5590
5769
|
isError: false
|
|
@@ -5735,8 +5914,8 @@ function handleValidateWorkflow(input) {
|
|
|
5735
5914
|
if (input.workflow && Object.keys(input.workflow).length > 0) {
|
|
5736
5915
|
raw = input.workflow;
|
|
5737
5916
|
} else {
|
|
5738
|
-
const artifactPath = (0,
|
|
5739
|
-
if (!(0,
|
|
5917
|
+
const artifactPath = (0, import_path10.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
5918
|
+
if (!(0, import_fs10.existsSync)(artifactPath)) {
|
|
5740
5919
|
return fail([
|
|
5741
5920
|
{
|
|
5742
5921
|
code: "NO_WORKFLOW",
|
|
@@ -5745,7 +5924,7 @@ function handleValidateWorkflow(input) {
|
|
|
5745
5924
|
]);
|
|
5746
5925
|
}
|
|
5747
5926
|
try {
|
|
5748
|
-
raw = JSON.parse((0,
|
|
5927
|
+
raw = JSON.parse((0, import_fs10.readFileSync)(artifactPath, "utf8"));
|
|
5749
5928
|
} catch (err) {
|
|
5750
5929
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
5751
5930
|
}
|
|
@@ -5857,12 +6036,12 @@ function handleValidateWorkflow(input) {
|
|
|
5857
6036
|
return { text: JSON.stringify({ valid: errors.length === 0, errors, warnings }), isError: false };
|
|
5858
6037
|
}
|
|
5859
6038
|
function validateTransitionTargets(step, stepId, stepIds, errors) {
|
|
5860
|
-
const check = (target,
|
|
6039
|
+
const check = (target, path4) => {
|
|
5861
6040
|
if (typeof target === "string" && !stepIds.has(target)) {
|
|
5862
6041
|
errors.push({
|
|
5863
6042
|
code: "ORPHANED_TRANSITION",
|
|
5864
6043
|
message: `Step "${stepId}" transitions to "${target}" which does not exist`,
|
|
5865
|
-
path:
|
|
6044
|
+
path: path4
|
|
5866
6045
|
});
|
|
5867
6046
|
}
|
|
5868
6047
|
};
|
|
@@ -5901,11 +6080,11 @@ function validateTransitionTargets(step, stepId, stepIds, errors) {
|
|
|
5901
6080
|
}
|
|
5902
6081
|
function collectServiceWarnings(step, stepId, warnings) {
|
|
5903
6082
|
const isService = (val) => typeof val === "string" && val.startsWith("service:");
|
|
5904
|
-
const warn = (
|
|
6083
|
+
const warn = (path4) => {
|
|
5905
6084
|
warnings.push({
|
|
5906
6085
|
code: "WARN_SERVICE_REFERENCE",
|
|
5907
|
-
message: `service: reference at "${
|
|
5908
|
-
path:
|
|
6086
|
+
message: `service: reference at "${path4}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
|
|
6087
|
+
path: path4
|
|
5909
6088
|
});
|
|
5910
6089
|
};
|
|
5911
6090
|
const onSubmit = step.on_submit;
|
|
@@ -6053,8 +6232,8 @@ function registerTypeSchemasTool(server2) {
|
|
|
6053
6232
|
}
|
|
6054
6233
|
|
|
6055
6234
|
// src/tools/scaffold-cleanup.ts
|
|
6056
|
-
var
|
|
6057
|
-
var
|
|
6235
|
+
var import_fs11 = require("fs");
|
|
6236
|
+
var import_path11 = require("path");
|
|
6058
6237
|
var SCAFFOLD_FILES_TO_DELETE = [
|
|
6059
6238
|
"content/posts/getting-started.yaml",
|
|
6060
6239
|
"content/posts/hello-world.yaml"
|
|
@@ -6079,12 +6258,12 @@ var FALLBACK_COLORS = {
|
|
|
6079
6258
|
mutedForeground: "#737373"
|
|
6080
6259
|
};
|
|
6081
6260
|
function readThemeColors(cwd) {
|
|
6082
|
-
const tokenPath = (0,
|
|
6083
|
-
if (!(0,
|
|
6084
|
-
const stat = (0,
|
|
6261
|
+
const tokenPath = (0, import_path11.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
6262
|
+
if (!(0, import_fs11.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
|
|
6263
|
+
const stat = (0, import_fs11.lstatSync)(tokenPath);
|
|
6085
6264
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
6086
6265
|
try {
|
|
6087
|
-
const raw = JSON.parse((0,
|
|
6266
|
+
const raw = JSON.parse((0, import_fs11.readFileSync)(tokenPath, "utf-8"));
|
|
6088
6267
|
const css = raw?.cssVariables ?? {};
|
|
6089
6268
|
const toHsl = (hslValue) => {
|
|
6090
6269
|
if (!hslValue || typeof hslValue !== "string") return null;
|
|
@@ -6144,15 +6323,15 @@ function generateNotFoundPage(colors) {
|
|
|
6144
6323
|
`;
|
|
6145
6324
|
}
|
|
6146
6325
|
function isSafePath(fullPath) {
|
|
6147
|
-
if (!(0,
|
|
6148
|
-
const stat = (0,
|
|
6326
|
+
if (!(0, import_fs11.existsSync)(fullPath)) return true;
|
|
6327
|
+
const stat = (0, import_fs11.lstatSync)(fullPath);
|
|
6149
6328
|
return !stat.isSymbolicLink();
|
|
6150
6329
|
}
|
|
6151
6330
|
function isDirEmpty(dirPath) {
|
|
6152
|
-
if (!(0,
|
|
6153
|
-
const stat = (0,
|
|
6331
|
+
if (!(0, import_fs11.existsSync)(dirPath)) return false;
|
|
6332
|
+
const stat = (0, import_fs11.lstatSync)(dirPath);
|
|
6154
6333
|
if (!stat.isDirectory()) return false;
|
|
6155
|
-
return (0,
|
|
6334
|
+
return (0, import_fs11.readdirSync)(dirPath).length === 0;
|
|
6156
6335
|
}
|
|
6157
6336
|
function handleCleanupScaffold(_cwd) {
|
|
6158
6337
|
const cwd = _cwd ?? process.cwd();
|
|
@@ -6165,8 +6344,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6165
6344
|
cleanupWarnings: []
|
|
6166
6345
|
};
|
|
6167
6346
|
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
6168
|
-
const fullPath = (0,
|
|
6169
|
-
if (!(0,
|
|
6347
|
+
const fullPath = (0, import_path11.join)(cwd, relPath);
|
|
6348
|
+
if (!(0, import_fs11.existsSync)(fullPath)) {
|
|
6170
6349
|
result.skipped.push(relPath);
|
|
6171
6350
|
continue;
|
|
6172
6351
|
}
|
|
@@ -6175,7 +6354,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6175
6354
|
continue;
|
|
6176
6355
|
}
|
|
6177
6356
|
try {
|
|
6178
|
-
(0,
|
|
6357
|
+
(0, import_fs11.unlinkSync)(fullPath);
|
|
6179
6358
|
result.deleted.push(relPath);
|
|
6180
6359
|
} catch (err) {
|
|
6181
6360
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -6183,19 +6362,19 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6183
6362
|
}
|
|
6184
6363
|
{
|
|
6185
6364
|
const relPath = "pages/getting-started/content.yml";
|
|
6186
|
-
const fullPath = (0,
|
|
6187
|
-
if (!(0,
|
|
6365
|
+
const fullPath = (0, import_path11.join)(cwd, relPath);
|
|
6366
|
+
if (!(0, import_fs11.existsSync)(fullPath)) {
|
|
6188
6367
|
result.skipped.push(relPath);
|
|
6189
6368
|
} else if (!isSafePath(fullPath)) {
|
|
6190
6369
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
6191
6370
|
} else {
|
|
6192
|
-
const content = (0,
|
|
6371
|
+
const content = (0, import_fs11.readFileSync)(fullPath, "utf-8");
|
|
6193
6372
|
const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
|
|
6194
6373
|
(marker) => content.includes(marker)
|
|
6195
6374
|
);
|
|
6196
6375
|
if (isScaffoldDefault) {
|
|
6197
6376
|
try {
|
|
6198
|
-
(0,
|
|
6377
|
+
(0, import_fs11.unlinkSync)(fullPath);
|
|
6199
6378
|
result.deleted.push(relPath);
|
|
6200
6379
|
} catch (err) {
|
|
6201
6380
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -6209,21 +6388,21 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6209
6388
|
}
|
|
6210
6389
|
{
|
|
6211
6390
|
const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
|
|
6212
|
-
const fullPath = (0,
|
|
6213
|
-
const postsDir = (0,
|
|
6214
|
-
if (!(0,
|
|
6391
|
+
const fullPath = (0, import_path11.join)(cwd, relPath);
|
|
6392
|
+
const postsDir = (0, import_path11.join)(cwd, "content/posts");
|
|
6393
|
+
if (!(0, import_fs11.existsSync)(fullPath)) {
|
|
6215
6394
|
result.skipped.push(relPath);
|
|
6216
6395
|
} else if (!isSafePath(fullPath)) {
|
|
6217
6396
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
6218
|
-
} else if (!(0,
|
|
6397
|
+
} else if (!(0, import_fs11.existsSync)(postsDir)) {
|
|
6219
6398
|
result.skipped.push(relPath);
|
|
6220
6399
|
} else {
|
|
6221
|
-
const userPostFiles = (0,
|
|
6400
|
+
const userPostFiles = (0, import_fs11.readdirSync)(postsDir).filter(
|
|
6222
6401
|
(f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
6223
6402
|
);
|
|
6224
6403
|
if (userPostFiles.length === 0) {
|
|
6225
6404
|
try {
|
|
6226
|
-
(0,
|
|
6405
|
+
(0, import_fs11.unlinkSync)(fullPath);
|
|
6227
6406
|
result.deleted.push(relPath);
|
|
6228
6407
|
} catch (err) {
|
|
6229
6408
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -6236,15 +6415,15 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6236
6415
|
}
|
|
6237
6416
|
}
|
|
6238
6417
|
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
6239
|
-
const fullDir = (0,
|
|
6240
|
-
if (!(0,
|
|
6418
|
+
const fullDir = (0, import_path11.join)(cwd, relDir);
|
|
6419
|
+
if (!(0, import_fs11.existsSync)(fullDir)) continue;
|
|
6241
6420
|
if (!isSafePath(fullDir)) {
|
|
6242
6421
|
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
6243
6422
|
continue;
|
|
6244
6423
|
}
|
|
6245
6424
|
if (isDirEmpty(fullDir)) {
|
|
6246
6425
|
try {
|
|
6247
|
-
(0,
|
|
6426
|
+
(0, import_fs11.rmdirSync)(fullDir);
|
|
6248
6427
|
result.prunedDirs.push(relDir);
|
|
6249
6428
|
} catch (err) {
|
|
6250
6429
|
result.errors.push(`Failed to prune directory ${relDir}: ${String(err)}`);
|
|
@@ -6252,8 +6431,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6252
6431
|
}
|
|
6253
6432
|
}
|
|
6254
6433
|
{
|
|
6255
|
-
const fullPath = (0,
|
|
6256
|
-
if (!(0,
|
|
6434
|
+
const fullPath = (0, import_path11.join)(cwd, NOT_FOUND_PATH);
|
|
6435
|
+
if (!(0, import_fs11.existsSync)(fullPath)) {
|
|
6257
6436
|
result.skipped.push(NOT_FOUND_PATH);
|
|
6258
6437
|
} else if (!isSafePath(fullPath)) {
|
|
6259
6438
|
result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
|
|
@@ -6261,9 +6440,9 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6261
6440
|
try {
|
|
6262
6441
|
const colors = readThemeColors(cwd);
|
|
6263
6442
|
const content = generateNotFoundPage(colors);
|
|
6264
|
-
const dir = (0,
|
|
6265
|
-
(0,
|
|
6266
|
-
(0,
|
|
6443
|
+
const dir = (0, import_path11.dirname)(fullPath);
|
|
6444
|
+
(0, import_fs11.mkdirSync)(dir, { recursive: true });
|
|
6445
|
+
(0, import_fs11.writeFileSync)(fullPath, content, "utf-8");
|
|
6267
6446
|
result.rewritten.push(NOT_FOUND_PATH);
|
|
6268
6447
|
} catch (err) {
|
|
6269
6448
|
result.errors.push(`Failed to rewrite ${NOT_FOUND_PATH}: ${String(err)}`);
|
|
@@ -6530,11 +6709,256 @@ function registerContrastTools(server2) {
|
|
|
6530
6709
|
);
|
|
6531
6710
|
}
|
|
6532
6711
|
|
|
6712
|
+
// src/tools/compile.ts
|
|
6713
|
+
var import_zod19 = require("zod");
|
|
6714
|
+
var import_fs12 = __toESM(require("fs"));
|
|
6715
|
+
var import_path12 = __toESM(require("path"));
|
|
6716
|
+
var import_server = require("@stackwright-pro/pulse/server");
|
|
6717
|
+
var import_server2 = require("@stackwright-pro/auth-nextjs/server");
|
|
6718
|
+
var import_server3 = require("@stackwright-pro/openapi/server");
|
|
6719
|
+
function buildContext(projectRoot) {
|
|
6720
|
+
return {
|
|
6721
|
+
projectRoot,
|
|
6722
|
+
contentOutDir: import_path12.default.join(projectRoot, "public", "stackwright-content"),
|
|
6723
|
+
imagesDir: import_path12.default.join(projectRoot, "public", "images"),
|
|
6724
|
+
siteConfig: loadSiteConfig(projectRoot)
|
|
6725
|
+
};
|
|
6726
|
+
}
|
|
6727
|
+
function loadSiteConfig(projectRoot) {
|
|
6728
|
+
try {
|
|
6729
|
+
const sitePath = import_path12.default.join(projectRoot, "public", "stackwright-content", "_site.json");
|
|
6730
|
+
return JSON.parse(import_fs12.default.readFileSync(sitePath, "utf8"));
|
|
6731
|
+
} catch {
|
|
6732
|
+
return {};
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
function formatDuration(startMs) {
|
|
6736
|
+
const ms = Date.now() - startMs;
|
|
6737
|
+
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
|
|
6738
|
+
}
|
|
6739
|
+
async function tryOssCompile(fn, ctx, extra) {
|
|
6740
|
+
try {
|
|
6741
|
+
const bsPath = import_path12.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
|
|
6742
|
+
if (!import_fs12.default.existsSync(bsPath)) {
|
|
6743
|
+
return {
|
|
6744
|
+
ok: false,
|
|
6745
|
+
error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
|
|
6746
|
+
};
|
|
6747
|
+
}
|
|
6748
|
+
const bs = await import(bsPath);
|
|
6749
|
+
if (typeof bs[fn] !== "function") {
|
|
6750
|
+
return { ok: false, error: `${fn} not exported from @stackwright/build-scripts` };
|
|
6751
|
+
}
|
|
6752
|
+
await bs[fn](ctx, ...extra ? [extra] : []);
|
|
6753
|
+
return { ok: true };
|
|
6754
|
+
} catch (err) {
|
|
6755
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
function registerCompileTools(server2) {
|
|
6759
|
+
const projectRootArg = import_zod19.z.string().optional().describe("Absolute path to project root (defaults to cwd)");
|
|
6760
|
+
server2.tool(
|
|
6761
|
+
"stackwright_pro_compile_collections",
|
|
6762
|
+
"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.",
|
|
6763
|
+
{ projectRoot: projectRootArg },
|
|
6764
|
+
async ({ projectRoot }) => {
|
|
6765
|
+
const start = Date.now();
|
|
6766
|
+
const root = projectRoot ?? process.cwd();
|
|
6767
|
+
try {
|
|
6768
|
+
await (0, import_server.compileCollections)(buildContext(root));
|
|
6769
|
+
return {
|
|
6770
|
+
content: [
|
|
6771
|
+
{
|
|
6772
|
+
type: "text",
|
|
6773
|
+
text: ` _collections.json written (${formatDuration(start)})`
|
|
6774
|
+
}
|
|
6775
|
+
]
|
|
6776
|
+
};
|
|
6777
|
+
} catch (err) {
|
|
6778
|
+
return {
|
|
6779
|
+
content: [
|
|
6780
|
+
{
|
|
6781
|
+
type: "text",
|
|
6782
|
+
text: ` compile_collections failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6783
|
+
}
|
|
6784
|
+
]
|
|
6785
|
+
};
|
|
6786
|
+
}
|
|
6787
|
+
}
|
|
6788
|
+
);
|
|
6789
|
+
server2.tool(
|
|
6790
|
+
"stackwright_pro_compile_auth",
|
|
6791
|
+
"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.",
|
|
6792
|
+
{ projectRoot: projectRootArg },
|
|
6793
|
+
async ({ projectRoot }) => {
|
|
6794
|
+
const start = Date.now();
|
|
6795
|
+
const root = projectRoot ?? process.cwd();
|
|
6796
|
+
try {
|
|
6797
|
+
await (0, import_server2.compileAuth)(buildContext(root));
|
|
6798
|
+
return {
|
|
6799
|
+
content: [
|
|
6800
|
+
{
|
|
6801
|
+
type: "text",
|
|
6802
|
+
text: ` _auth.json written (${formatDuration(start)})`
|
|
6803
|
+
}
|
|
6804
|
+
]
|
|
6805
|
+
};
|
|
6806
|
+
} catch (err) {
|
|
6807
|
+
return {
|
|
6808
|
+
content: [
|
|
6809
|
+
{
|
|
6810
|
+
type: "text",
|
|
6811
|
+
text: ` compile_auth failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6812
|
+
}
|
|
6813
|
+
]
|
|
6814
|
+
};
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6817
|
+
);
|
|
6818
|
+
server2.tool(
|
|
6819
|
+
"stackwright_pro_compile_integrations",
|
|
6820
|
+
"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).",
|
|
6821
|
+
{ projectRoot: projectRootArg },
|
|
6822
|
+
async ({ projectRoot }) => {
|
|
6823
|
+
const start = Date.now();
|
|
6824
|
+
const root = projectRoot ?? process.cwd();
|
|
6825
|
+
try {
|
|
6826
|
+
await (0, import_server3.compileIntegrations)(buildContext(root));
|
|
6827
|
+
return {
|
|
6828
|
+
content: [
|
|
6829
|
+
{
|
|
6830
|
+
type: "text",
|
|
6831
|
+
text: ` _integrations.json written (${formatDuration(start)})`
|
|
6832
|
+
}
|
|
6833
|
+
]
|
|
6834
|
+
};
|
|
6835
|
+
} catch (err) {
|
|
6836
|
+
return {
|
|
6837
|
+
content: [
|
|
6838
|
+
{
|
|
6839
|
+
type: "text",
|
|
6840
|
+
text: ` compile_integrations failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6841
|
+
}
|
|
6842
|
+
]
|
|
6843
|
+
};
|
|
6844
|
+
}
|
|
6845
|
+
}
|
|
6846
|
+
);
|
|
6847
|
+
server2.tool(
|
|
6848
|
+
"stackwright_pro_compile_all",
|
|
6849
|
+
"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.",
|
|
6850
|
+
{ projectRoot: projectRootArg },
|
|
6851
|
+
async ({ projectRoot }) => {
|
|
6852
|
+
const start = Date.now();
|
|
6853
|
+
const root = projectRoot ?? process.cwd();
|
|
6854
|
+
const ctx = buildContext(root);
|
|
6855
|
+
const results = [];
|
|
6856
|
+
const errors = [];
|
|
6857
|
+
for (const [label, fn] of [
|
|
6858
|
+
["collections", () => (0, import_server.compileCollections)(ctx)],
|
|
6859
|
+
["auth", () => (0, import_server2.compileAuth)(ctx)],
|
|
6860
|
+
["integrations", () => (0, import_server3.compileIntegrations)(ctx)]
|
|
6861
|
+
]) {
|
|
6862
|
+
try {
|
|
6863
|
+
await fn();
|
|
6864
|
+
results.push(` _${label}.json`);
|
|
6865
|
+
} catch (err) {
|
|
6866
|
+
errors.push(` ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6867
|
+
}
|
|
6868
|
+
}
|
|
6869
|
+
const summary = errors.length > 0 ? ` compile_all finished with errors (${formatDuration(start)}):
|
|
6870
|
+
${results.join("\n")}
|
|
6871
|
+
Errors:
|
|
6872
|
+
${errors.join("\n")}` : ` compile_all complete (${formatDuration(start)}):
|
|
6873
|
+
${results.join("\n")}`;
|
|
6874
|
+
return { content: [{ type: "text", text: summary }] };
|
|
6875
|
+
}
|
|
6876
|
+
);
|
|
6877
|
+
server2.tool(
|
|
6878
|
+
"stackwright_pro_compile_theme",
|
|
6879
|
+
"Compile stackwright.yml theme config \u2192 _theme.json. Delegates to compileTheme from @stackwright/build-scripts installed in the project.",
|
|
6880
|
+
{ projectRoot: projectRootArg },
|
|
6881
|
+
async ({ projectRoot }) => {
|
|
6882
|
+
const start = Date.now();
|
|
6883
|
+
const root = projectRoot ?? process.cwd();
|
|
6884
|
+
const { ok, error } = await tryOssCompile("compileTheme", buildContext(root));
|
|
6885
|
+
return {
|
|
6886
|
+
content: [
|
|
6887
|
+
{
|
|
6888
|
+
type: "text",
|
|
6889
|
+
text: ok ? ` _theme.json written (${formatDuration(start)})` : ` compile_theme failed: ${error}`
|
|
6890
|
+
}
|
|
6891
|
+
]
|
|
6892
|
+
};
|
|
6893
|
+
}
|
|
6894
|
+
);
|
|
6895
|
+
server2.tool(
|
|
6896
|
+
"stackwright_pro_compile_site",
|
|
6897
|
+
"Compile stackwright.yml \u2192 _site.json. Delegates to compileSite from @stackwright/build-scripts installed in the project.",
|
|
6898
|
+
{ projectRoot: projectRootArg },
|
|
6899
|
+
async ({ projectRoot }) => {
|
|
6900
|
+
const start = Date.now();
|
|
6901
|
+
const root = projectRoot ?? process.cwd();
|
|
6902
|
+
const { ok, error } = await tryOssCompile("compileSite", buildContext(root));
|
|
6903
|
+
return {
|
|
6904
|
+
content: [
|
|
6905
|
+
{
|
|
6906
|
+
type: "text",
|
|
6907
|
+
text: ok ? ` _site.json written (${formatDuration(start)})` : ` compile_site failed: ${error}`
|
|
6908
|
+
}
|
|
6909
|
+
]
|
|
6910
|
+
};
|
|
6911
|
+
}
|
|
6912
|
+
);
|
|
6913
|
+
server2.tool(
|
|
6914
|
+
"stackwright_pro_compile_pages",
|
|
6915
|
+
"Compile all pages content.yml files \u2192 page JSON files. Delegates to compilePages from @stackwright/build-scripts installed in the project.",
|
|
6916
|
+
{ projectRoot: projectRootArg },
|
|
6917
|
+
async ({ projectRoot }) => {
|
|
6918
|
+
const start = Date.now();
|
|
6919
|
+
const root = projectRoot ?? process.cwd();
|
|
6920
|
+
const { ok, error } = await tryOssCompile("compilePages", buildContext(root));
|
|
6921
|
+
return {
|
|
6922
|
+
content: [
|
|
6923
|
+
{
|
|
6924
|
+
type: "text",
|
|
6925
|
+
text: ok ? ` pages compiled (${formatDuration(start)})` : ` compile_pages failed: ${error}`
|
|
6926
|
+
}
|
|
6927
|
+
]
|
|
6928
|
+
};
|
|
6929
|
+
}
|
|
6930
|
+
);
|
|
6931
|
+
server2.tool(
|
|
6932
|
+
"stackwright_pro_compile_page",
|
|
6933
|
+
"Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
|
|
6934
|
+
{
|
|
6935
|
+
slug: import_zod19.z.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
|
|
6936
|
+
projectRoot: projectRootArg
|
|
6937
|
+
},
|
|
6938
|
+
async ({ slug, projectRoot }) => {
|
|
6939
|
+
const start = Date.now();
|
|
6940
|
+
const root = projectRoot ?? process.cwd();
|
|
6941
|
+
const { ok, error } = await tryOssCompile("compilePage", buildContext(root), { slug });
|
|
6942
|
+
return {
|
|
6943
|
+
content: [
|
|
6944
|
+
{
|
|
6945
|
+
type: "text",
|
|
6946
|
+
text: ok ? ` page "${slug}" compiled (${formatDuration(start)})` : ` compile_page failed: ${error}`
|
|
6947
|
+
}
|
|
6948
|
+
]
|
|
6949
|
+
};
|
|
6950
|
+
}
|
|
6951
|
+
);
|
|
6952
|
+
}
|
|
6953
|
+
|
|
6533
6954
|
// package.json
|
|
6534
6955
|
var package_default = {
|
|
6535
6956
|
dependencies: {
|
|
6536
6957
|
"@modelcontextprotocol/sdk": "^1.10.0",
|
|
6958
|
+
"@stackwright-pro/auth-nextjs": "workspace:*",
|
|
6537
6959
|
"@stackwright-pro/cli-data-explorer": "workspace:*",
|
|
6960
|
+
"@stackwright-pro/openapi": "workspace:*",
|
|
6961
|
+
"@stackwright-pro/pulse": "workspace:*",
|
|
6538
6962
|
"@stackwright-pro/types": "workspace:*",
|
|
6539
6963
|
"@types/js-yaml": "^4.0.9",
|
|
6540
6964
|
"js-yaml": "^4.2.0",
|
|
@@ -6555,7 +6979,7 @@ var package_default = {
|
|
|
6555
6979
|
"test:coverage": "vitest run --coverage"
|
|
6556
6980
|
},
|
|
6557
6981
|
name: "@stackwright-pro/mcp",
|
|
6558
|
-
version: "0.2.0-alpha.
|
|
6982
|
+
version: "0.2.0-alpha.95",
|
|
6559
6983
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
6560
6984
|
license: "SEE LICENSE IN LICENSE",
|
|
6561
6985
|
main: "./dist/server.js",
|
|
@@ -6613,7 +7037,22 @@ registerValidateYamlFragmentTool(server);
|
|
|
6613
7037
|
registerGetSchemaTool(server);
|
|
6614
7038
|
registerScaffoldCleanupTools(server);
|
|
6615
7039
|
registerContrastTools(server);
|
|
7040
|
+
registerCompileTools(server);
|
|
6616
7041
|
async function main() {
|
|
7042
|
+
const pipelineGraph = loadPipelineGraph();
|
|
7043
|
+
const phasePosition = new Map(PHASE_ORDER.map((p, i) => [p, i]));
|
|
7044
|
+
for (const [phase, deps] of Object.entries(pipelineGraph.dependencies)) {
|
|
7045
|
+
const phaseIdx = phasePosition.get(phase);
|
|
7046
|
+
if (phaseIdx === void 0) continue;
|
|
7047
|
+
for (const dep of deps) {
|
|
7048
|
+
const depIdx = phasePosition.get(dep);
|
|
7049
|
+
if (depIdx !== void 0 && depIdx >= phaseIdx) {
|
|
7050
|
+
throw new Error(
|
|
7051
|
+
`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.`
|
|
7052
|
+
);
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
6617
7056
|
const transport = new import_stdio.StdioServerTransport();
|
|
6618
7057
|
try {
|
|
6619
7058
|
const servicesRegisterPkg = "@stackwright-services/mcp/register";
|