Files
windmill/flake.nix
T
Ruben Fiszel 3cd0eac8c1 deps: bump deno_core / deno_ast / swc to the goldilocks pin set; drop serde ceiling (#9111)
* deps: bump deno_core / deno_ast / swc to the goldilocks pin set; drop serde ceiling

Bumps every deno_* and swc_* workspace dep to a hand-picked "goldilocks"
combination that drops the serde =1.0.220 ceiling without crashing into
the rustls / aws-sdk resolver wall that the obvious deno v2.6.0 target
hits.

## What's the goldilocks set

| crate            | old      | new       | source                                |
|------------------|----------|-----------|---------------------------------------|
| deno_core        | 0.336.0  | 0.352.0   | deno v2.4.0                           |
| deno_fetch       | 0.214.0  | 0.233.0   | deno v2.4.0                           |
| deno_tls         | 0.177.0  | 0.196.0   | deno v2.4.0 (last permissive-rustls)  |
| deno_console     | 0.190.0  | 0.209.0   | deno v2.4.0                           |
| deno_url         | 0.190.0  | 0.209.0   | deno v2.4.0                           |
| deno_webidl      | 0.190.0  | 0.209.0   | deno v2.4.0                           |
| deno_web         | 0.221.0  | 0.240.0   | deno v2.4.0                           |
| deno_io          | 0.100.0  | 0.119.0   | deno v2.4.0                           |
| deno_net         | 0.182.0  | 0.201.0   | deno v2.4.0                           |
| deno_permissions | 0.49.0   | 0.68.0    | deno v2.4.0                           |
| deno_telemetry   | 0.12.0   | 0.31.0    | deno v2.4.0                           |
| deno_error       | =0.5.5   | =0.6.1    | deno v2.4.0                           |
| deno_ast         | =0.44.0  | =0.51.0   | **override** — see "load-bearing" below |
| deno_fs          | (new)    | 0.119.0   | new workspace dep — FetchPermissions exposes deno_fs::CheckedPath / GetPath as public API |
| v8               | =130.0.7 | =137.1.0  | deno_core 0.352 transitive            |
| swc_common       | =0.37.5  | =14.0.4   | **the load-bearing pin**              |
| swc_ecma_ast     | =0.118.2 | =15.0.0   | matched set with swc_common 14.0.4    |
| swc_ecma_parser  | =0.149.1 | =24.0.3   | matched set                           |
| swc_ecma_visit   | =0.104.8 | =15.0.0   | matched set                           |
| serde            | =1.0.220 | ^1        | **freed** (resolves to 1.0.228+)      |

## Why this combination and not v2.6.0

The obvious target was deno v2.6.0 (with deno_ast 0.52 → swc_common 17,
well past the `__private` ceiling). That hits three resolver collisions:

1. libsqlite3-sys: deno_cache → rusqlite 0.37 → libsqlite3-sys 0.35
   vs sqlx → libsqlite3-sys 0.30. **Already killed by PR #9110** —
   we dropped deno_runtime, which is what pulled in deno_cache.
2. fqdn 0.4.6/0.4.7 yanked, required by deno_permissions 0.81.0. Solvable
   by injecting the yanked entry into Cargo.lock manually but ugly.
3. rustls: deno_tls 0.198+ hard-pins `=0.23.28`, but aws-sdk-bedrockruntime
   1.122.0 → aws-smithy-http-client 1.1.5 wants `^0.23.31`. Within-major
   conflict, no resolver path. The unbeatable wall.

Goldilocks-set choice sidesteps (2) and (3) entirely:

- `deno_tls 0.196.0` was the last version before deno tightened
  `rustls ^0.23.11` (range, accepts 0.23.31) to exact `=0.23.28`. With
  ^0.23.11, the resolver picks rustls 0.23.35 (latest 0.23 patch) which
  satisfies both deno_tls's `>=0.23.11` and aws-sdk's `>=0.23.31`. Verified
  empirically: lockfile has rustls 0.23.35 after this bump.
- `deno_permissions 0.68.0` (v2.4.0's pin) doesn't depend on fqdn at all.
  The fqdn dep was added in a later deno_permissions release.

## Why deno_ast =0.51.0 specifically (not 0.48.0 from v2.4.0)

`swc_common 14.0.4` is the first patch that **drops the
`pub use serde::__private as serde;` line** in `src/private/mod.rs`. Older
14.0.x and all 0.37.5–13.x revisions still have it, and that line is
what was capping `serde = "=1.0.220"` (the workspace pin's "stuck because
of swc" comment). Empirically verified by inspecting the tarballs of
14.0.0 / 14.0.1 / 14.0.2 / 14.0.3 / 14.0.4:

    14.0.0: has hack
    14.0.1: has hack
    14.0.2: has hack
    14.0.3: has hack
    14.0.4: NO HACK    ← inflection point

`deno_ast 0.51.0` pins `swc_common =14.0.4` exactly — older deno_ast
versions pin earlier swc_common patches that still have the hack.
Notably, deno v2.4.0 itself pins `deno_ast =0.48.0` (swc_common 9.2.0,
still has hack) — we deliberately deviate from v2.4.0's deno_ast pin
to escape the swc serde wall, while keeping the rest of v2.4.0's pin
set for resolver compatibility with aws-sdk. deno_ast 0.51 was never
shipped in any deno release (v2.4.5 used 0.49, v2.5.0 jumped to 0.50,
v2.6.0 to 0.52), but it's published on crates.io and compatible with
v2.4.0's deno_core 0.352.

## What this unblocks

- PR #9106's `serde = "=1.0.224"` bump variant can rebase onto this
  and resolve cleanly (MaterializeInc/rust-postgres' `postgres-types`
  needs `serde_core ^1.0.221`, which is satisfied now that we're on
  serde 1.0.228).
- Future deno_* / swc_* bumps no longer need to argue about the serde
  ceiling — it's gone.

## What changes in source code

This commit is Cargo.toml + Cargo.lock only. Source changes that the
new deno_core / deno_fetch API requires live in the follow-up commits:

- `parsers/windmill-parser-{ts,ts-asset,wac}`: swc 0.37 → 14
  (`code.into()` ambiguity fix at 5 sites)
- `windmill-runtime-nativets/build.rs` + `src/lib.rs`: deno_core 0.336
  → 0.352 API moves (`init_ops_and_esm()` → `init()`,
  `FetchPermissions` / `NetPermissions` trait signature updates,
  `deno_tls::Proxy` enum shape change)

A companion change in windmill-ee-private adjusts
`otel_tracing_proxy_ee.rs:521` for `deno_telemetry::init`'s second arg
becoming by-value (was `&OtelConfig`).

* fix(parsers): adapt to swc_common 14 BytesStr ambiguity

swc 0.37.5 → 14.0.4 changed `SourceMap::new_source_file`'s `src` argument
from `String` to `impl Into<BytesStr>`. With `BytesStr` available, the
existing call sites' `code.into()` on a `&str` becomes ambiguous between
`Into<Bytes>` (from the bytes crate) and `Into<BytesStr>` (from
bytes_str). Switch to `code.to_string()` to produce an owned `String`
that satisfies `From<String> for BytesStr` unambiguously.

Five call sites across three crates:
- windmill-parser-ts/src/lib.rs (3 sites)
- windmill-parser-ts-asset/src/lib.rs (1 site)
- windmill-parser-wac/src/typescript.rs (1 site)

* fix(nativets): adapt to deno_core 0.352 / deno_fetch 0.233 API changes

The goldilocks deno bump (deno_core 0.336 → 0.352, deno_fetch 0.214 →
0.233, etc.) ripples through nativets' build.rs and src/lib.rs.
Source-level changes required:

## 1. `extension!` macro: `init_ops_and_esm()` and `init_ops()` removed

deno_core 0.352's `extension!` macro now generates a single `init()`
function on the extension struct (full: ops + esm), plus `lazy_init()`
(ops only, with `needs_lazy_init = true` and a contract that the
caller invokes `JsRuntime::lazy_init_extensions` after construction).

- `build.rs` (snapshot creation, wants both ops and esm baked in):
  `X::init_ops_and_esm(...)` → `X::init(...)`.
- `src/lib.rs:create_nativets_runtime` (runtime, was using `init_ops()`
  because the snapshot already provides esm): also → `X::init(...)`.
  deno_core's snapshot path skips esm re-execution when the snapshot
  provides them, so the esm registration is a no-op at runtime. This
  is how deno's own v2.4.0 runtime works.

Avoided `lazy_init` because it requires plumbing
`JsRuntime::lazy_init_extensions(ext_args_vec)` correctly across the
codebase, which is invasive for no behavioural benefit.

## 2. Local `fetch` extension now declared in both build.rs and lib.rs

deno_core 0.352 validates extension order between snapshot and runtime.
Our snapshot's last extension is the local `fetch` ext (which provides
ext:fetch/src/runtime.js). To avoid a runtime panic:

    "Extensions from snapshot loaded in wrong order: expected fetch but got windmill"

…the runtime extension list now ends with `fetch::init()` matching the
snapshot order. The macro requires the same `esm` argument to type-check,
even though the ESM is not re-executed at runtime (it's in the snapshot).

## 3. `FetchPermissions` and `NetPermissions` trait shape

`deno_fetch::FetchPermissions` (deno_fetch 0.233.0) added new methods
and changed signatures:

- `check_read` / `check_write`: now take `path: Cow<'a, Path>` plus
  a new `get_path: &'a dyn deno_fs::GetPath` parameter, and return
  `Result<deno_fs::CheckedPath<'a>, FsError>` instead of `Result<Cow<Path>, FsError>`.
- New `check_write` (didn't exist) and `check_net_vsock` methods.

`deno_net::NetPermissions` (deno_net 0.201.0) gained `check_vsock`
and `check_write_path` now takes `Cow<'_, Path>`.

For `build.rs`'s `PermissionsContainer` (used only during snapshot
creation, where permissions are never actually checked): all methods
`unreachable!("snapshotting")`.

For `src/lib.rs`'s `PermissionsContainer` (used at runtime — the
nativets policy is "allow everything"): `check_read` / `check_write`
return `Ok(CheckedPath::Unresolved(path))`, `check_*_vsock` return
`Ok(())`. Smoke tests confirm fetch/net/url/web/blob/timers/structuredClone
behaviour is intact end-to-end.

## 4. `deno_tls::Proxy` is now an enum

`deno_tls::Proxy` was a struct, is now an enum with `Http`, `Https`,
`Socks5` variants. Our call site uses HTTP proxies — switched the
struct literal `deno_tls::Proxy { url, basic_auth }` to
`deno_tls::Proxy::Http { url, basic_auth }`.

## 5. New `deno_fs` direct workspace dep

`FetchPermissions` exposes `deno_fs::CheckedPath` and `deno_fs::GetPath`
as part of its public API. We can't avoid naming `deno_fs` directly any
more. Pinned to 0.119.0 (v2.4.0's matched version, transitively present
already through deno_fetch). Added to workspace `[dependencies]` plus
nativets's `[dependencies]` and `[build-dependencies]`.

## Validation

`cargo check --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp,run_inline`
→ clean.

`cargo test -p windmill-runtime-nativets smoke -- --ignored --skip smoke_net_`
→ 8 passed; 0 failed (the full local smoke suite covering fetch,
setTimeout/Promise.all, URL/SearchParams, Blob/btoa/atob, large payload
roundtrip, error propagation, concurrent isolates, TS enum/union
transpile).

Network smoke tests (`smoke_net_fetch_example_com`,
`smoke_net_fetch_json_and_headers`) not run as part of the validation
gate but expected to pass — the change preserves deno_fetch behaviour
through the trait surface.

* chore: update ee-repo-ref to pick up deno_telemetry::init by-value fix

Points at windmill-ee-private branch deps/bump-deno-and-swc-goldilocks
which contains the companion otel_tracing_proxy_ee.rs adjustment for
deno_telemetry 0.12 → 0.31 (second arg of `init` is now by-value).
EE-only file, doesn't affect OSS build.

* chore(nix): bump rusty_v8 in flake.nix to 137.1.0 to match Cargo.toml

Cargo.toml's v8 pin moved from =130.0.7 to =137.1.0 as part of the
deno_core 0.336 → 0.352 bump, but I missed the comment directly above
the version pin:

    # Exact version NOTE: Do not forget to update version and hash in flake.nix

flake.nix provides the prebuilt librusty_v8 binary that the v8 crate
links against. A version mismatch would either fail to fetch (if the
137.1.0 release didn't exist) or cause link-time symbol mismatches.
Nix is used by rust-client-check.yml and rust_on_release.yml in CI,
plus the dev shell — stale flake pin breaks all of those.

Updates x86_64-linux's sha256 to match the actual hash of
librusty_v8_release_x86_64-unknown-linux-gnu.a.gz at the 137.1.0 tag.
Other targets (aarch64-linux, x86_64-darwin, aarch64-darwin) remain
as lib.fakeHash — they were already placeholders in the previous
pin, so we don't regress on them.

Caught by both cubic and Pi reviewers on PR #9111.

* docs(nativets): clarify snapshot-prefix rule in extension-order comment

Claude reviewer caught that the doc comment claimed the runtime
extension list matches the snapshot's order — implying an exact match.
The truth is more permissive: deno_core 0.352 requires the snapshot's
extension list to be a *prefix* of the runtime's, not an exact match.
Runtime is allowed to append extra extensions (which we do — the
windmill `ext` carrying our ops is the last entry at runtime but absent
from the snapshot).

The code is correct as-is; only the comment wording was misleading.

Also fixes the same wording in PR description.
2026-05-11 21:13:24 +00:00

573 lines
21 KiB
Nix

{
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay.url = "github:oxalica/rust-overlay";
# Pin openapi-generator-cli to 7.10.0
nixpkgs-oapi-gen.url = "nixpkgs/2d068ae5c6516b2d04562de50a58c682540de9bf";
};
outputs = { self, nixpkgs, flake-utils, rust-overlay, nixpkgs-oapi-gen }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
overlays = [ (import rust-overlay) ];
};
lib = pkgs.lib;
stdenv = pkgs.stdenv;
# ---------------------------------------------------------------
# Rust toolchain
# ---------------------------------------------------------------
rustStable = pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" "rustfmt" ];
};
patchedClang = pkgs.llvmPackages_18.clang.overrideAttrs (oldAttrs: {
postFixup = ''
# Copy the original postFixup logic but skip add-hardening.sh
${oldAttrs.postFixup or ""}
# Remove the line that substitutes add-hardening.sh
sed -i 's/.*source.*add-hardening\.sh.*//' $out/bin/clang
'';
});
# ---------------------------------------------------------------
# Native C/C++ dependencies (required to compile the backend)
# ---------------------------------------------------------------
nativeBuildDeps = with pkgs; [
# Crypto / TLS
openssl
openssl.dev
# XML / SAML (enterprise_saml feature)
libxml2.dev
xmlsec.dev
libxslt.dev
# FFI / codegen
libclang.dev
libffi # deno_ffi on macOS
# Networking / compression
curl.dev
zlib.dev
# Auth (kafka-gssapi, mssql-kerberos)
cyrus_sasl
krb5
# Misc
libtool
postgresql
# Build tooling
pkg-config
llvmPackages_18.clang # linker — pinned to 18 to avoid SIGSEGV with mold + newer clang
mold
cmake # required by rdkafka cmake-build
];
# ---------------------------------------------------------------
# Prebuilt V8 binary (must match version in Cargo.toml)
# ---------------------------------------------------------------
rustyV8Archive = let
version = "137.1.0";
target = stdenv.hostPlatform.rust.rustcTarget;
sha256 = {
x86_64-linux =
"sha256-Tiscfy2bzYGR3s0T+SC1IB3xWvTVpVcSEdjq3MCRoRw=";
aarch64-linux = lib.fakeHash;
x86_64-darwin = lib.fakeHash;
aarch64-darwin = lib.fakeHash;
}.${system};
in pkgs.fetchurl {
name = "librusty_v8-${version}";
url =
"https://github.com/denoland/rusty_v8/releases/download/v${version}/librusty_v8_release_${target}.a.gz";
inherit sha256;
};
# ---------------------------------------------------------------
# pkg-config search path for native libraries
# ---------------------------------------------------------------
pkgConfigPath = lib.makeSearchPath "lib/pkgconfig" (with pkgs; [
openssl.dev
libxml2.dev
xmlsec.dev
libxslt.dev
cyrus_sasl.dev
krb5.dev
]);
# ---------------------------------------------------------------
# RPATH — embed Nix store library paths into compiled binaries
# ---------------------------------------------------------------
rpathLibs = lib.makeLibraryPath (with pkgs; [
openssl
libffi
cyrus_sasl
krb5
libxml2
xmlsec
libxslt
stdenv.cc.cc.lib
]);
# ---------------------------------------------------------------
# Bindgen configuration
# Bindgen uses libclang directly (not $CC), so we must explicitly
# provide all Nix header search paths.
# See: https://web.archive.org/web/20220523141208/https://hoverbear.org/blog/rust-bindgen-in-nix/
# ---------------------------------------------------------------
bindgenClangArgs = builtins.concatStringsSep " " ([
"-nostdinc"
(builtins.readFile "${stdenv.cc}/nix-support/libc-crt1-cflags")
(builtins.readFile "${stdenv.cc}/nix-support/libc-cflags")
(builtins.readFile "${stdenv.cc}/nix-support/cc-cflags")
(builtins.readFile "${stdenv.cc}/nix-support/libcxx-cxxflags")
"-idirafter ${pkgs.libiconv}/include"
] ++ lib.optionals stdenv.cc.isClang [
"-idirafter ${stdenv.cc.cc}/lib/clang/${
lib.getVersion stdenv.cc.cc
}/include"
] ++ lib.optionals stdenv.cc.isGNU [
"-isystem ${stdenv.cc.cc}/include/c++/${lib.getVersion stdenv.cc.cc}"
"-isystem ${stdenv.cc.cc}/include/c++/${
lib.getVersion stdenv.cc.cc
}/${stdenv.hostPlatform.config}"
"-idirafter ${stdenv.cc.cc}/lib/gcc/${stdenv.hostPlatform.config}/${
lib.getVersion stdenv.cc.cc
}/include"
]);
# ---------------------------------------------------------------
# Build environment variables (shared by all shells that compile Rust)
# ---------------------------------------------------------------
buildEnvVars = {
PKG_CONFIG_PATH = pkgConfigPath;
RUSTY_V8_ARCHIVE = rustyV8Archive;
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
BINDGEN_EXTRA_CLANG_ARGS = bindgenClangArgs;
# Force clang 18 as cargo linker (stdenv may bring a newer clang that causes SIGSEGV with mold)
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER =
"${pkgs.llvmPackages_18.clang}/bin/clang";
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER =
"${pkgs.llvmPackages_18.clang}/bin/clang";
# Embed rpath so binaries find Nix store .so files at runtime
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS =
"-C link-arg=-fuse-ld=mold -C link-arg=-Wl,-rpath,${rpathLibs}";
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS =
"-C link-arg=-fuse-ld=mold -C link-arg=-Wl,-rpath,${rpathLibs}";
CARGO_HOST_RUSTFLAGS = "-C link-arg=-Wl,-rpath,${rpathLibs}";
# https://github.com/NixOS/nixpkgs/issues/370494 — jemalloc build fix
CFLAGS = "-Wno-error=int-conversion";
LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.zlib stdenv.cc.cc.lib ];
};
# ---------------------------------------------------------------
# OpenAPI generator (pinned)
# ---------------------------------------------------------------
openapi-generator-cli =
(import nixpkgs-oapi-gen { inherit system; }).openapi-generator-cli;
# ---------------------------------------------------------------
# Common worker runtimes (languages the worker executes)
# ---------------------------------------------------------------
commonRuntimes = with pkgs; [
deno
python3
python3Packages.pip
uv
go
bun
nushell
typescript
flock
];
# ---------------------------------------------------------------
# Runtime PATH env vars — tells the worker where to find interpreters
# ---------------------------------------------------------------
commonRuntimeVars = {
DENO_PATH = "${pkgs.deno}/bin/deno";
GO_PATH = "${pkgs.go}/bin/go";
BUN_PATH = "${pkgs.bun}/bin/bun";
NODE_PATH = "${pkgs.nodejs}/bin/node";
NODE_BIN_PATH = "${pkgs.nodejs}/bin/node";
UV_PATH = "${pkgs.uv}/bin/uv";
NU_PATH = "${pkgs.nushell}/bin/nu";
FLOCK_PATH = "${pkgs.flock}/bin/flock";
CARGO_PATH = "${rustStable}/bin/cargo";
BASH_PATH = "bash";
GIT_PATH = "${pkgs.git}/bin/git";
};
# ---------------------------------------------------------------
# Extra language runtimes (full shell only)
# ---------------------------------------------------------------
coursier = pkgs.fetchFromGitHub {
owner = "coursier";
repo = "launchers";
rev = "79d927f7586c09ca6d8cd01862adb0d9f9d88dff";
hash = "sha256-8E0WtDFc7RcqmftDigMyy1xXUkjgL4X4kpf7h1GdE48=";
};
rWithPackages = pkgs.rWrapper.override {
packages = with pkgs.rPackages; [ renv ];
};
extraRuntimes = with pkgs; [
dotnet-sdk_9
php
php84Packages.composer
ruby_3_4
jdk21
ansible
oracle-instantclient
];
extraRuntimeVars = {
JAVA_PATH = "${pkgs.jdk21}/bin/java";
JAVAC_PATH = "${pkgs.jdk21}/bin/javac";
COURSIER_PATH = "${coursier}/coursier";
DOTNET_PATH = "${pkgs.dotnet-sdk_9}/bin/dotnet";
DOTNET_ROOT = "${pkgs.dotnet-sdk_9}/share/dotnet";
PHP_PATH = "${pkgs.php}/bin/php";
COMPOSER_PATH = "${pkgs.php84Packages.composer}/bin/composer";
RUBY_PATH = "${pkgs.ruby_3_4}/bin/ruby";
RUBY_BUNDLE_PATH = "${pkgs.ruby_3_4}/bin/bundle";
RUBY_GEM_PATH = "${pkgs.ruby_3_4}/bin/gem";
ORACLE_LIB_DIR = "${pkgs.oracle-instantclient.lib}/lib";
ANSIBLE_PLAYBOOK_PATH = "${pkgs.ansible}/bin/ansible-playbook";
ANSIBLE_GALAXY_PATH = "${pkgs.ansible}/bin/ansible-galaxy";
CARGO_SWEEP_PATH = "${pkgs.cargo-sweep}/bin/cargo-sweep";
RSCRIPT_PATH = "${rWithPackages}/bin/Rscript";
};
# ---------------------------------------------------------------
# General dev environment variables
# ---------------------------------------------------------------
devEnvVars = {
NODE_ENV = "development";
NODE_OPTIONS = "--max-old-space-size=16384";
};
# Connection-specific defaults — set via shellHook so they respect
# pre-existing values (e.g. from webmux runtime.env / .env.local).
# Nix attrs are injected unconditionally and would override per-worktree
# values set by webmux before the interactive shell starts.
devShellHook = ''
export DATABASE_URL="''${DATABASE_URL:-postgres://postgres:changeme@127.0.0.1:5432/windmill?sslmode=disable}"
export REMOTE="''${REMOTE:-http://127.0.0.1:8000}"
export REMOTE_LSP="''${REMOTE_LSP:-http://127.0.0.1:3001}"
'';
# ---------------------------------------------------------------
# Helper scripts — base set (default + full)
# ---------------------------------------------------------------
helperScriptsBase = [
(pkgs.writeScriptBin "wm" ''
cd ./frontend
npm install
npm run ${
if stdenv.isDarwin then
"generate-backend-client-mac"
else
"generate-backend-client"
}
npm run dev "$@"
'')
(pkgs.writeScriptBin "wm-build" ''
cd ./frontend
npm install
npm run ${
if stdenv.isDarwin then
"generate-backend-client-mac"
else
"generate-backend-client"
}
npm run build "$@"
'')
(pkgs.writeScriptBin "wm-migrate" ''
cd ./backend
sqlx migrate run
'')
(pkgs.writeScriptBin "wm-reset" ''
sqlx database drop -f
sqlx database create
wm-migrate
'')
(pkgs.writeScriptBin "wm-minio" ''
set -e
cd ./backend
mkdir -p .minio-data/wmill
${pkgs.minio}/bin/minio server ./.minio-data --console-address ":9001"
'')
(pkgs.writeScriptBin "wm-minio-keys" ''
set -e
cd ./backend
${pkgs.minio-client}/bin/mc alias set 'wmill-minio-dev' 'http://localhost:9000' 'minioadmin' 'minioadmin'
if [[ -f .minio-data/secrets.txt ]] && [[ -s .minio-data/secrets.txt ]]; then
echo "Access keys already exist:"
cat .minio-data/secrets.txt
echo ""
echo "Keys loaded from: ./backend/.minio-data/secrets.txt"
else
echo "Creating new access keys..."
mkdir -p .minio-data
${pkgs.minio-client}/bin/mc admin accesskey create 'wmill-minio-dev' | tee .minio-data/secrets.txt
echo ""
echo 'New keys saved to: ./backend/.minio-data/secrets.txt'
fi
echo "bucket: wmill"
echo "endpoint: http://localhost:9000"
'')
];
# ---------------------------------------------------------------
# Helper scripts — extra (full shell only)
# ---------------------------------------------------------------
helperScriptsFull = [
(pkgs.writeScriptBin "wm-caddy" ''
cd ./frontend
xcaddy build "$@" \
--with github.com/mholt/caddy-l4@145ec36251a44286f05a10d231d8bfb3a8192e09 \
--with github.com/RussellLuo/caddy-ext/layer4@ab1e18cfe426012af351a68463937ae2e934a2a1
'')
(pkgs.writeScriptBin "wm-setup" ''
sqlx database create
wm-build
wm-caddy
wm-migrate
'')
(pkgs.writeScriptBin "wm-bench" ''
deno run -A benchmarks/main.ts -e admin@windmill.dev -p changeme "$@"
'')
];
# ---------------------------------------------------------------
# Shared inputs and settings for default + full shells
# ---------------------------------------------------------------
coreBuildInputs = nativeBuildDeps ++ commonRuntimes
++ [ rustStable openapi-generator-cli ] ++ (with pkgs; [
nodejs
git
sqlx-cli
cargo-watch
jq
gnused
# CLI tools (for AI agents and dev workflow)
gh
asciinema
mermaid-cli
]);
# Playwright: use Nix-provided browsers (version-matched to playwright-driver)
# Mermaid/Puppeteer: point at Nix chromium (Puppeteer respects this env var)
browserVars = {
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium";
PUPPETEER_SKIP_DOWNLOAD = "true";
};
# Wrapper for the Nix-provided playwright CLI (version-matched to its browsers)
playwrightWrapper = pkgs.writeShellScriptBin "playwright" ''
export PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}"
exec ${pkgs.nodejs}/bin/node ${pkgs.playwright-driver}/cli.js "$@"
'';
# ---------------------------------------------------------------
# sandbox-env script — outputs env vars for browser tooling
# Usage: eval "$(sandbox-env)"
# ---------------------------------------------------------------
sandboxEnvScript = pkgs.writeShellScriptBin "sandbox-env" ''
echo "export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}"
echo "export PUPPETEER_EXECUTABLE_PATH=${pkgs.chromium}/bin/chromium"
echo "export PUPPETEER_SKIP_DOWNLOAD=true"
'';
# ---------------------------------------------------------------
# pkg-config wrapper — bakes in the Nix pkg-config search path
# so sandbox profiles (buildEnv) work without setting env vars.
# ---------------------------------------------------------------
pkgConfigWrapper = pkgs.writeShellScriptBin "pkg-config" ''
export PKG_CONFIG_PATH="${pkgConfigPath}:$PKG_CONFIG_PATH"
exec ${pkgs.pkg-config}/bin/pkg-config "$@"
'';
# ---------------------------------------------------------------
# Installable sandbox profiles (nix profile install .#sandbox)
# ---------------------------------------------------------------
sandboxEnv = pkgs.buildEnv {
name = "windmill-sandbox";
paths = coreBuildInputs ++ helperScriptsBase ++ [
playwrightWrapper
sandboxEnvScript
pkgConfigWrapper
pkgs.chromium
];
};
sandboxFullEnv = pkgs.buildEnv {
name = "windmill-sandbox-full";
paths = coreBuildInputs ++ extraRuntimes ++ helperScriptsBase
++ helperScriptsFull ++ [
playwrightWrapper
sandboxEnvScript
pkgConfigWrapper
pkgs.chromium
pkgs.cargo-sweep
pkgs.xcaddy
pkgs.nsjail
];
};
in {
# =============================================================
# Installable profiles — for Docker / nix profile install
# Usage: nix profile install .#sandbox
# =============================================================
packages.sandbox = sandboxEnv;
packages.sandbox-full = sandboxFullEnv;
packages.default = sandboxEnv;
# =============================================================
# default — daily driver for backend + frontend development
# Usage: nix develop
# =============================================================
devShells.default = pkgs.mkShell (buildEnvVars // commonRuntimeVars // devEnvVars // browserVars // {
shellHook = devShellHook;
buildInputs = coreBuildInputs;
packages = helperScriptsBase ++ [ playwrightWrapper ];
});
# =============================================================
# full — all language runtimes, k8s tooling, specialized scripts
# Usage: nix develop .#full
# =============================================================
devShells.full = pkgs.mkShell (buildEnvVars // commonRuntimeVars // extraRuntimeVars // devEnvVars // browserVars // {
shellHook = devShellHook;
buildInputs = coreBuildInputs ++ extraRuntimes ++ (with pkgs; [
# Python extras
poetry
pyright
openapi-python-client
# LSP / editor
svelte-language-server
taplo
# Extra dev tools
cargo-sweep
# Kubernetes
minikube
kubectl
kubernetes-helm
conntrack-tools
cri-tools
# Extra
xcaddy
nsjail
]);
packages = helperScriptsBase ++ helperScriptsFull
++ [ playwrightWrapper ];
});
# =============================================================
# wasm — WASM target compilation (nightly Rust)
# Usage: nix develop .#wasm
# =============================================================
devShells.wasm = pkgs.mkShell (buildEnvVars // {
hardeningDisable = [ "all" ];
# Explicitly set paths for headers and linker
# DO NOT REMOVE - if absent, breaks wasm builds on NixOS.
shellHook = ''
export CC=${patchedClang}/bin/clang
'';
buildInputs = nativeBuildDeps ++ (with pkgs; [
(rust-bin.nightly.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" ];
targets =
[ "wasm32-unknown-unknown" "wasm32-unknown-emscripten" ];
})
wasm-pack
deno
emscripten
nushell
nodejs
glibc_multi
]);
});
# =============================================================
# cli — lightweight Bun-based CLI development
# Usage: nix develop .#cli
# =============================================================
devShells.cli = pkgs.mkShell {
shellHook = ''
if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
export FLAKE_ROOT="$(git rev-parse --show-toplevel)"
else
export FLAKE_ROOT="$PWD"
fi
'';
buildInputs = with pkgs; [ bun nodejs git ];
packages = [
(pkgs.writeScriptBin "wm-cli" ''
bun run $FLAKE_ROOT/cli/src/main.ts "$@"
'')
(pkgs.writeScriptBin "wm-cli-deps" ''
pushd $FLAKE_ROOT/cli/
${if stdenv.isDarwin then
"./gen_wm_client_mac.sh && ./windmill-utils-internal/gen_wm_client_mac.sh"
else
"./gen_wm_client.sh && ./windmill-utils-internal/gen_wm_client.sh"}
popd
'')
];
};
});
}