1# jevstrudel, built from this repo: the Astro site (Strudel's REPL with the 2# song browser), the static site deployed from it, and the stdio MCP proxy 3# Claude Code starts from .mcp.json. 4{ 5 stdenv, 6 nodejs, 7 pnpm_10, 8 fetchPnpmDeps, 9 pnpmConfigHook, 10 importNpmLock, 11 runCommand, 12 writeShellApplication, 13 src, 14}: 15let 16 pname = "jevstrudel"; 17 version = "0-unstable-${src.lastModifiedDate or "0"}"; 18 19 pnpmDeps = fetchPnpmDeps { 20 inherit pname version src; 21 pnpm = pnpm_10; 22 fetcherVersion = 4; 23 hash = "sha256-xdDD3pJNjPjNAHLnGncq8ofay+HMaKGoAuPVEvFzG3g="; 24 }; 25 nativeBuildInputs = [ 26 nodejs 27 pnpm_10 28 pnpmConfigHook 29 ]; 30 31 site = stdenv.mkDerivation { 32 inherit 33 pname 34 version 35 src 36 pnpmDeps 37 nativeBuildInputs 38 ; 39 40 buildPhase = '' 41 runHook preBuild 42 pnpm run build 43 runHook postBuild 44 ''; 45 46 installPhase = '' 47 runHook preInstall 48 share=$out/share/jevstrudel 49 mkdir -p $share/website 50 cp -r website/dist $share/website/dist 51 # Upstream's CNAME names strudel.cc; a host that honours it (GitHub 52 # Pages) would try to claim Strudel's domain for this build. 53 rm $share/website/dist/CNAME 54 runHook postInstall 55 ''; 56 }; 57 58 # jevstrudel's tests (`nix flake check`): Jev's core, the Worker (the 59 # relay's rules and log, the votes, the D1 migrations), the agreement 60 # report, the deploy's storage step, the telemetry reader, Jev's decision 61 # history, and every song evaluated and played to its end against a stand-in Jev 62 # (website/src/jev/allSongs.test.mjs). Upstream's own suites are 63 # Strudel's to run. 64 tests = stdenv.mkDerivation { 65 pname = "${pname}-tests"; 66 inherit 67 version 68 src 69 pnpmDeps 70 nativeBuildInputs 71 ; 72 dontBuild = true; 73 doCheck = true; 74 checkPhase = '' 75 runHook preCheck 76 pnpm exec vitest run website/src/jev worker tools/agreement tools/deploy tools/events tools/jev-history 77 runHook postCheck 78 ''; 79 installPhase = "touch $out"; 80 }; 81 # The Worker as the deploy uploads it and the type check reads it: 82 # worker/ with its own node_modules, holding exactly its dependencies' 83 # closure (the WebAuthn library), taken from the installed workspace by 84 # worker-closure.mjs. So a copy of it anywhere bundles and type-checks 85 # without the rest of the repo. 86 worker = stdenv.mkDerivation { 87 pname = "${pname}-worker"; 88 inherit 89 version 90 src 91 pnpmDeps 92 nativeBuildInputs 93 ; 94 dontBuild = true; 95 installPhase = '' 96 runHook preInstall 97 node nix/worker-closure.mjs worker $out 98 runHook postInstall 99 ''; 100 dontFixup = true; 101 }; 102 103 # tools/mcp/ with its node_modules, beside the two files it reads from 104 # worker/ (the dev port, and the dev tools for when the Worker is down), at 105 # the same relative paths as in the repo. 106 mcpNodeModules = importNpmLock.buildNodeModules { 107 npmRoot = "${src}/tools/mcp"; 108 inherit nodejs; 109 }; 110 mcpTree = runCommand "jevstrudel-mcp-tree" { } '' 111 mkdir -p $out/tools/mcp $out/worker/src 112 cp ${src}/tools/mcp/*.mjs $out/tools/mcp/ 113 ln -s ${mcpNodeModules}/node_modules $out/tools/mcp/node_modules 114 cp ${src}/worker/wrangler.json $out/worker/ 115 cp ${src}/worker/src/tools.json ${src}/worker/src/shared-tools.json $out/worker/src/ 116 ''; 117in 118{ 119 inherit site tests worker; 120 121 # The static site alone, ready for any static host: the REPL and song 122 # browser at /, the sample pack under /jev-samples/ and /_astro/. No MCP: 123 # the production Worker has no hub, so the page's probe finds none. 124 static = runCommand "jevstrudel-static" { } "ln -s ${site}/share/jevstrudel/website/dist $out"; 125 126 # The stdio MCP server in .mcp.json: a proxy with no state, forwarding 127 # each message to the MCP hub in the jevstrudel Worker under `wrangler dev` 128 # or, switched with its use_environment tool, to the public site's hosted 129 # MCP as an OAuth client (tools/mcp/), so neither side has to restart for 130 # the other. It needs only Node and the MCP SDK (tools/mcp/package-lock.json, 131 # each package fetched by its integrity hash, apart from the site's pnpm 132 # closure), not the built site. 133 mcp = writeShellApplication { 134 name = "strudel-mcp"; 135 runtimeInputs = [ nodejs ]; 136 text = '' 137 exec node ${mcpTree}/tools/mcp/strudel-mcp.mjs 138 ''; 139 }; 140 141 # The proxy's own tests (`nix flake check`): the environment switch and 142 # forwarding, against fakes (tools/mcp/proxy.test.mjs). 143 mcpTests = runCommand "jevstrudel-mcp-tests" { nativeBuildInputs = [ nodejs ]; } '' 144 cd ${mcpTree} 145 node --test tools/mcp/*.test.mjs 146 touch $out 147 ''; 148}