jevstrudel.git / nix / strudel.nix

jevstrudel, built from this repo: the Astro site (Strudel's REPL with the song browser), the static site deployed from it, and the stdio MCP proxy 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  };

jevstrudel's tests (nix flake check): Jev's core, the Worker (the relay's rules and log, the votes, the D1 migrations), the agreement report, the deploy's storage step, the telemetry reader, Jev's decision history, and every song evaluated and played to its end against a stand-in Jev (website/src/jev/allSongs.test.mjs). Upstream's own suites are 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  };

tools/mcp/ with its node_modules, beside the two files it reads from worker/ (the dev port, and the dev tools for when the Worker is down), at 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;

The static site alone, ready for any static host: the REPL and song browser at /, the sample pack under /jev-samples/ and /_astro/. No MCP: 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";

The stdio MCP server in .mcp.json: a proxy with no state, forwarding each message to the MCP hub in the jevstrudel Worker under wrangler dev or, switched with its use_environment tool, to the public site's hosted MCP as an OAuth client (tools/mcp/), so neither side has to restart for the other. It needs only Node and the MCP SDK (tools/mcp/package-lock.json, each package fetched by its integrity hash, apart from the site's pnpm 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  };

The proxy's own tests (nix flake check): the environment switch and 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}