2026-06-18 13:12:52 +01:00
|
|
|
{
|
|
|
|
|
description = "FREYA development shell";
|
|
|
|
|
|
|
|
|
|
inputs = {
|
|
|
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
outputs =
|
|
|
|
|
{ nixpkgs, ... }:
|
|
|
|
|
let
|
|
|
|
|
systems = [
|
|
|
|
|
"x86_64-linux"
|
|
|
|
|
"aarch64-linux"
|
|
|
|
|
"x86_64-darwin"
|
|
|
|
|
"aarch64-darwin"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
lib = nixpkgs.lib;
|
|
|
|
|
forEachSystem = lib.genAttrs systems;
|
|
|
|
|
pkgsFor = forEachSystem (system: import nixpkgs { inherit system; });
|
|
|
|
|
|
|
|
|
|
# App outputs are for long-running local tools and dev servers.
|
|
|
|
|
appScripts = {
|
|
|
|
|
expo = "expo";
|
|
|
|
|
drizzle-studio = "drizzle-studio";
|
|
|
|
|
freya-backend = "freya-backend";
|
|
|
|
|
admin-dashboard = "admin-dashboard";
|
|
|
|
|
agent-test-cli = "agent-test-cli";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# Check outputs are the CI-like validation commands run by `nix flake check`.
|
|
|
|
|
checkCommands = {
|
|
|
|
|
format-check = "bun run format:check";
|
|
|
|
|
lint = "bun run lint";
|
|
|
|
|
test = "bun run test";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# Dev-shell conveniences mirror the common app/check commands.
|
|
|
|
|
shellScripts = appScripts // {
|
|
|
|
|
freya-test = "test";
|
|
|
|
|
lint = "lint";
|
|
|
|
|
format-check = "format:check";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# node_modules is content-addressed. If bun.lock or package manifests
|
|
|
|
|
# change, Nix will report the new hash to put here.
|
|
|
|
|
nodeModulesHashes = {
|
2026-06-18 16:24:26 +01:00
|
|
|
x86_64-linux = "sha256-8uhlaQAFfCgGdUlrz8sqhtIkC/WfdasbTCi3p/NkU/w=";
|
2026-06-18 13:12:52 +01:00
|
|
|
};
|
|
|
|
|
checkSystems = lib.attrNames nodeModulesHashes;
|
|
|
|
|
|
|
|
|
|
# Dependency derivations only need the lockfile and workspace manifests,
|
|
|
|
|
# so source-only edits do not force Bun to reinstall.
|
|
|
|
|
dependencySource = lib.fileset.toSource {
|
|
|
|
|
root = ./.;
|
2026-06-18 16:24:26 +01:00
|
|
|
fileset = lib.fileset.fileFilter (
|
|
|
|
|
file: file.name == "bun.lock" || file.name == "package.json" || file.name == "bunfig.toml"
|
|
|
|
|
) ./.;
|
2026-06-18 13:12:52 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# Checks run against a clean source tree, even when using `path:.`.
|
|
|
|
|
# Without this filter, local node_modules can sneak into the Nix sandbox.
|
|
|
|
|
projectSource = builtins.path {
|
|
|
|
|
name = "freya-source";
|
|
|
|
|
path = ./.;
|
|
|
|
|
filter =
|
|
|
|
|
path: type:
|
|
|
|
|
let
|
|
|
|
|
name = builtins.baseNameOf path;
|
|
|
|
|
in
|
|
|
|
|
!(type == "directory" && (name == ".git" || name == "node_modules")) && name != "result";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkBunScriptCommands =
|
|
|
|
|
pkgs: scripts:
|
|
|
|
|
let
|
|
|
|
|
mkBunScript =
|
|
|
|
|
name: script:
|
|
|
|
|
pkgs.writeShellApplication {
|
|
|
|
|
inherit name;
|
|
|
|
|
runtimeInputs = with pkgs; [
|
|
|
|
|
bun
|
|
|
|
|
git
|
|
|
|
|
];
|
|
|
|
|
text = ''
|
|
|
|
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
|
|
|
cd "$repo_root"
|
|
|
|
|
exec bun run ${lib.escapeShellArg script} "$@"
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
in
|
|
|
|
|
lib.mapAttrs mkBunScript scripts;
|
|
|
|
|
mkBunApps =
|
|
|
|
|
commands:
|
|
|
|
|
lib.mapAttrs (name: command: {
|
|
|
|
|
type = "app";
|
|
|
|
|
program = "${command}/bin/${name}";
|
|
|
|
|
}) commands;
|
|
|
|
|
mkBunNodeModules =
|
|
|
|
|
system: pkgs:
|
|
|
|
|
pkgs.stdenvNoCC.mkDerivation {
|
|
|
|
|
pname = "freya-node-modules";
|
|
|
|
|
version = "1";
|
|
|
|
|
__structuredAttrs = true;
|
|
|
|
|
|
|
|
|
|
src = dependencySource;
|
|
|
|
|
nativeBuildInputs = with pkgs; [
|
|
|
|
|
bun
|
|
|
|
|
cacert
|
|
|
|
|
nodejs
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
|
|
|
|
GIT_SSL_CAINFO = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
|
|
|
|
|
|
|
|
|
outputHashAlgo = "sha256";
|
|
|
|
|
outputHashMode = "recursive";
|
|
|
|
|
outputHash = nodeModulesHashes.${system};
|
|
|
|
|
|
|
|
|
|
# `patchShebangs` embeds Nix store interpreters in package bins. The
|
|
|
|
|
# check derivations also depend on bun/node, so this dependency blob
|
|
|
|
|
# can safely drop those references after its hash is verified.
|
|
|
|
|
unsafeDiscardReferences.out = true;
|
|
|
|
|
|
|
|
|
|
dontConfigure = true;
|
|
|
|
|
# Workspace package links are completed inside each check's source tree,
|
|
|
|
|
# so they are intentionally dangling in this dependency-only output.
|
|
|
|
|
dontFixup = true;
|
|
|
|
|
|
|
|
|
|
buildPhase = ''
|
|
|
|
|
runHook preBuild
|
|
|
|
|
|
|
|
|
|
export HOME="$TMPDIR/home"
|
|
|
|
|
mkdir -p "$HOME"
|
|
|
|
|
|
|
|
|
|
# Keep the real workspace manifest for `--frozen-lockfile`, but
|
|
|
|
|
# filter out frontend workspaces that do not participate in checks.
|
|
|
|
|
# `--force` matters in the Nix sandbox: without it, Bun can accept
|
|
|
|
|
# manifest-only cached packages and leave tool binaries missing.
|
|
|
|
|
bun install \
|
|
|
|
|
--force \
|
|
|
|
|
--frozen-lockfile \
|
|
|
|
|
--ignore-scripts \
|
|
|
|
|
--backend copyfile \
|
|
|
|
|
--filter freya \
|
|
|
|
|
--filter '@freya/*' \
|
|
|
|
|
--filter '@freya/backend' \
|
|
|
|
|
--no-progress
|
|
|
|
|
|
|
|
|
|
patchShebangs node_modules
|
|
|
|
|
|
|
|
|
|
runHook postBuild
|
|
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
installPhase = ''
|
|
|
|
|
runHook preInstall
|
|
|
|
|
|
|
|
|
|
mkdir -p "$out"
|
|
|
|
|
|
|
|
|
|
# Keep the root install in the store; checks symlink this directly.
|
|
|
|
|
cp -a node_modules "$out/node_modules"
|
|
|
|
|
|
|
|
|
|
# Bun also creates per-workspace node_modules directories. These are
|
|
|
|
|
# mostly relative symlinks, so checks copy the symlink entries into
|
|
|
|
|
# their writable source tree instead of symlinking the directory.
|
|
|
|
|
find apps packages -mindepth 2 -maxdepth 2 -type d -name node_modules -print |
|
|
|
|
|
while IFS= read -r node_modules_dir; do
|
|
|
|
|
mkdir -p "$out/$(dirname "$node_modules_dir")"
|
|
|
|
|
cp -a "$node_modules_dir" "$out/$node_modules_dir"
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
runHook postInstall
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
mkBunCheck =
|
|
|
|
|
pkgs: nodeModules: name: command:
|
|
|
|
|
pkgs.stdenvNoCC.mkDerivation {
|
|
|
|
|
pname = "freya-${name}";
|
|
|
|
|
version = "1";
|
|
|
|
|
|
|
|
|
|
src = projectSource;
|
|
|
|
|
nativeBuildInputs = with pkgs; [
|
|
|
|
|
bun
|
|
|
|
|
nodejs
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
dontConfigure = true;
|
|
|
|
|
|
|
|
|
|
buildPhase = ''
|
|
|
|
|
runHook preBuild
|
|
|
|
|
|
|
|
|
|
export HOME="$TMPDIR/home"
|
|
|
|
|
mkdir -p "$HOME"
|
|
|
|
|
|
|
|
|
|
# Root dependencies are read-only and shared across checks.
|
|
|
|
|
ln -s "${nodeModules}/node_modules" node_modules
|
|
|
|
|
|
|
|
|
|
# Workspace node_modules contain relative symlinks back to packages/
|
|
|
|
|
# and apps/, so copy just those symlink entries into this source tree.
|
|
|
|
|
for node_modules_dir in "${nodeModules}"/apps/*/node_modules "${nodeModules}"/packages/*/node_modules; do
|
|
|
|
|
if [ -d "$node_modules_dir" ]; then
|
|
|
|
|
relative_path="''${node_modules_dir#"${nodeModules}/"}"
|
|
|
|
|
mkdir -p "$relative_path"
|
|
|
|
|
cp -a "$node_modules_dir/." "$relative_path/"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
${command}
|
|
|
|
|
|
|
|
|
|
runHook postBuild
|
|
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
installPhase = ''
|
|
|
|
|
runHook preInstall
|
|
|
|
|
|
|
|
|
|
mkdir -p "$out"
|
|
|
|
|
touch "$out/${name}"
|
|
|
|
|
|
|
|
|
|
runHook postInstall
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
in
|
|
|
|
|
{
|
|
|
|
|
apps = forEachSystem (
|
|
|
|
|
system:
|
|
|
|
|
let
|
|
|
|
|
pkgs = pkgsFor.${system};
|
|
|
|
|
in
|
|
|
|
|
mkBunApps (mkBunScriptCommands pkgs appScripts)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
checks = lib.genAttrs checkSystems (
|
|
|
|
|
system:
|
|
|
|
|
let
|
|
|
|
|
pkgs = pkgsFor.${system};
|
|
|
|
|
nodeModules = mkBunNodeModules system pkgs;
|
|
|
|
|
in
|
|
|
|
|
lib.mapAttrs (mkBunCheck pkgs nodeModules) checkCommands
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
devShells = forEachSystem (
|
|
|
|
|
system:
|
|
|
|
|
let
|
|
|
|
|
pkgs = pkgsFor.${system};
|
|
|
|
|
bunScriptCommands = lib.attrValues (mkBunScriptCommands pkgs shellScripts);
|
|
|
|
|
commonPackages = with pkgs; [
|
|
|
|
|
bun
|
2026-06-18 14:50:58 +01:00
|
|
|
eas-cli
|
2026-06-18 13:12:52 +01:00
|
|
|
git
|
2026-06-18 13:24:43 +01:00
|
|
|
gh
|
2026-06-18 13:12:52 +01:00
|
|
|
gnumake
|
|
|
|
|
nixfmt
|
|
|
|
|
nodejs
|
|
|
|
|
openssl
|
|
|
|
|
pkg-config
|
|
|
|
|
postgresql
|
|
|
|
|
python3
|
|
|
|
|
watchman
|
|
|
|
|
];
|
|
|
|
|
linuxPackages = with pkgs; [
|
|
|
|
|
gcc
|
|
|
|
|
inotify-tools
|
|
|
|
|
tailscale
|
|
|
|
|
];
|
|
|
|
|
in
|
|
|
|
|
{
|
|
|
|
|
default = pkgs.mkShell {
|
|
|
|
|
packages =
|
|
|
|
|
commonPackages ++ bunScriptCommands ++ pkgs.lib.optionals pkgs.stdenv.isLinux linuxPackages;
|
|
|
|
|
|
|
|
|
|
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
|
|
|
|
|
|
|
|
|
shellHook = ''
|
|
|
|
|
export PATH="$PWD/node_modules/.bin:$PATH"
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
formatter = forEachSystem (system: pkgsFor.${system}.nixfmt);
|
|
|
|
|
};
|
|
|
|
|
}
|