From e50afb2a226c484c3f017515a63a3aa7beee3422 Mon Sep 17 00:00:00 2001 From: EatThePooh Date: Tue, 16 Dec 2025 20:39:41 +0700 Subject: [PATCH] perform structural deconfuckulation --- example/flake.nix | 7 +- flake-module.nix | 227 +++++++++++----------------------------------- flake.nix | 8 +- impl.nix | 137 ++++++++++++++++++++++++++++ 4 files changed, 197 insertions(+), 182 deletions(-) create mode 100644 impl.nix diff --git a/example/flake.nix b/example/flake.nix index 5bb20f4..393c0a4 100644 --- a/example/flake.nix +++ b/example/flake.nix @@ -19,14 +19,11 @@ systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; perSystem = { config, pkgs, ... }: { - deno = { - enable = true; - lockfiles = [ ./deno.lock ]; - }; + deno-with-packages.lockfiles = [ ./deno.lock ]; devShells.default = pkgs.mkShell { inputsFrom = [ config.flake-root.devShell ]; - buildInputs = [ config.packages.deno ]; + buildInputs = [ config.deno-with-packages.package ]; }; }; }; diff --git a/flake-module.nix b/flake-module.nix index bb0d82f..2785aab 100644 --- a/flake-module.nix +++ b/flake-module.nix @@ -1,188 +1,67 @@ -{ self, lib, flake-parts-lib, ... }: +{ lib, flake-parts-lib, ... }: let inherit (flake-parts-lib) mkPerSystemOption; - inherit (lib) mkOption mkEnableOption types; + inherit (lib) mkOption types; in { - options = { - flake.lib.deno = mkOption { - type = types.attrs; - default = {}; - description = "Deno-related library functions"; - }; - }; - - options.perSystem = mkPerSystemOption ({ pkgs, system, ... }: { - options.deno = { - enable = mkEnableOption "Deno with shared cache support"; - - lockfiles = mkOption { - type = types.listOf types.path; - default = []; - description = "List of deno.lock files to build cache from"; - }; - - package = mkOption { - type = types.package; - default = pkgs.deno; - description = "Base deno package to wrap"; - }; - - registryHostname = mkOption { - type = types.str; - default = "registry.npmjs.org"; - description = "NPM registry hostname"; - }; - }; - }); - - config = { - flake.lib.deno = rec { - denoLockfileToNpmDeps = { pkgs, lockfile, registryHostname }: - let - extractNpmDeps = lockfile: - let - lockJSON = builtins.fromJSON (builtins.readFile lockfile); - in - lockJSON.npm or {}; - - npmDeps = extractNpmDeps lockfile; - - mkTarballDrv = { name, version, integrity }: - let - tarballName = - if builtins.substring 0 1 name == "@" - then builtins.baseNameOf name # "@types/node" -> "node" - else name; - in - pkgs.fetchurl { - url = "https://${registryHostname}/${name}/-/${tarballName}-${version}.tgz"; - hash = integrity; - }; - - mkNpmDep = key: value: - let - match = builtins.match "(@?[^@]+)@([^_]+).*" key; - name = builtins.elemAt match 0; - version = builtins.elemAt match 1; - cachePath = "npm/${registryHostname}/${name}"; - in { - drv = pkgs.stdenv.mkDerivation { - pname = "deno-npm-${builtins.replaceStrings ["@" "/"] ["" "-"] name}"; - version = version; - src = mkTarballDrv { - inherit name version; - inherit (value) integrity; - }; - - installPhase = '' - runHook preInstall - - export OUT_PATH="$out/${cachePath}/${version}" - - mkdir -p $OUT_PATH - tar -xzf $src -C $OUT_PATH --strip-components=1 - - runHook postInstall - ''; - }; - inherit name cachePath; - }; - in - pkgs.lib.mapAttrsToList mkNpmDep npmDeps; - - denoSharedCache = { pkgs, lockfiles, registryHostname }: - let - allNpmDeps = builtins.concatMap - (lockfile: denoLockfileToNpmDeps { - inherit pkgs lockfile registryHostname; - }) - lockfiles; - sharedCacheBase = pkgs.symlinkJoin { - name = "deno-shared-cache-base"; - paths = map (d: d.drv) allNpmDeps; + options.perSystem = mkPerSystemOption ( + { pkgs, config, ... }: + let + impl = import ./impl.nix { inherit pkgs; }; + mainSubmodule = types.submodule { + options = { + basePackage = mkOption { + type = types.package; + default = pkgs.deno; + description = "Base deno package to wrap"; }; - in - pkgs.runCommand "deno-shared-cache" {} '' - runHook preInstall - mkdir -p $out - chmod -R +w $out + registryHostname = mkOption { + type = types.str; + default = "registry.npmjs.org"; + description = "NPM registry hostname"; + }; - cp -rs ${sharedCacheBase}/* $out + lockfiles = mkOption { + type = types.listOf types.path; + default = [ ]; + description = "List of deno.lock files to build cache from"; + }; - while IFS='|' read -r dep name; do - chmod +w $out/$dep -cat > $out/$dep/registry.json < $out/$dep/registry.json.tmp + dir = mkOption { + type = types.str; + default = "$HOME/.cache/deno-nix"; + description = "What to set DENO_DIR to in the wrapper script"; + }; - mv $out/$dep/registry.json.tmp $out/$dep/registry.json - done < <(find $out/$dep -mindepth 1 -maxdepth 1 -type d | xargs -n1 basename) - done < <(echo "${builtins.concatStringsSep "\n" (map (d: "${d.cachePath}|${d.name}") allNpmDeps)}" | sort -u) + package = mkOption { + type = types.package; + readOnly = true; + description = "Resulting wrapper mimicking Deno executable"; + default = impl.denoWithCache { + baseDeno = config.deno-with-packages.basePackage; + sharedCache = config.deno-with-packages.cache; + }; + }; - runHook postInstall - ''; - - denoWithCache = { pkgs, baseDeno, sharedCache }: - pkgs.writeShellScriptBin "deno" '' - set -euo pipefail - - if [ -n "''${FLAKE_ROOT:-}" ]; then - CACHE_DIR="$FLAKE_ROOT/.deno_cache" - else - echo "FLAKE_ROOT not set. Make sure you're using the flake-root devShell." >&2 - exit 1 - fi - - if [ ! -d "$CACHE_DIR" ] || [ ! "$(ls -A "$CACHE_DIR" 2>/dev/null)" ]; then - echo "Setting up Deno cache at: $CACHE_DIR" >&2 - mkdir -p "$CACHE_DIR" - - if [ -d "${sharedCache}" ]; then - echo "Copying shared cache..." >&2 - cp -rL "${sharedCache}"/* "$CACHE_DIR"/ 2>/dev/null || true - chmod -R u+w "$CACHE_DIR" 2>/dev/null || true - fi - fi - - export DENO_DIR="$CACHE_DIR" - exec "${baseDeno}/bin/deno" "$@" - ''; - }; - - perSystem = { config, pkgs, system, ... }: - lib.mkIf config.deno.enable { - packages = lib.mkIf (config.deno.lockfiles != []) { - deno = - let - sharedCache = self.lib.deno.denoSharedCache { - inherit pkgs; - lockfiles = config.deno.lockfiles; - registryHostname = config.deno.registryHostname; - }; - in - self.lib.deno.denoWithCache { - inherit pkgs sharedCache; - baseDeno = config.deno.package; - }; - - deno-cache = self.lib.deno.denoSharedCache { - inherit pkgs; - lockfiles = config.deno.lockfiles; - registryHostname = config.deno.registryHostname; + cache = mkOption { + type = types.package; + readOnly = true; + description = "Pre-generated Deno cache based on the given lockfiles"; + default = impl.denoSharedCache { + lockfiles = config.deno-with-packages.lockfiles; + registryHostname = config.deno-with-packages.registryHostname; + }; }; }; }; - }; + in + { + options.deno-with-packages = mkOption { + type = mainSubmodule; + description = "Wrapping Deno with a pre-generated cache"; + default = { }; + }; + } + ); } diff --git a/flake.nix b/flake.nix index e939da4..3bdae29 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,9 @@ { description = "Provides a Deno executable wrapper pointing to pre-built NPM dependency cache based on lock files"; - outputs = { ... }: { - flakeModule = ./flake-module.nix; - }; + outputs = + { ... }: + { + flakeModule = ./flake-module.nix; + }; } diff --git a/impl.nix b/impl.nix new file mode 100644 index 0000000..554a054 --- /dev/null +++ b/impl.nix @@ -0,0 +1,137 @@ +{ pkgs }: +rec { + denoLockfileToNpmDeps = + { lockfile, registryHostname }: + let + extractNpmDeps = + lockfile: + let + lockJSON = builtins.fromJSON (builtins.readFile lockfile); + in + lockJSON.npm or { }; + + npmDeps = extractNpmDeps lockfile; + + mkTarballDrv = + { + name, + version, + integrity, + }: + let + tarballName = + if builtins.substring 0 1 name == "@" then + builtins.baseNameOf name # "@types/node" -> "node" + else + name; + in + pkgs.fetchurl { + url = "https://${registryHostname}/${name}/-/${tarballName}-${version}.tgz"; + hash = integrity; + }; + + mkNpmDep = + key: value: + let + match = builtins.match "(@?[^@]+)@([^_]+).*" key; + name = builtins.elemAt match 0; + version = builtins.elemAt match 1; + cachePath = "npm/${registryHostname}/${name}"; + in + { + drv = pkgs.stdenv.mkDerivation { + pname = "deno-npm-${builtins.replaceStrings [ "@" "/" ] [ "" "-" ] name}"; + version = version; + src = mkTarballDrv { + inherit name version; + inherit (value) integrity; + }; + + installPhase = '' + runHook preInstall + + export OUT_PATH="$out/${cachePath}/${version}" + + mkdir -p $OUT_PATH + tar -xzf $src -C $OUT_PATH --strip-components=1 + + runHook postInstall + ''; + }; + inherit name cachePath; + }; + in + pkgs.lib.mapAttrsToList mkNpmDep npmDeps; + + denoSharedCache = + { lockfiles, registryHostname }: + let + allNpmDeps = builtins.concatMap ( + lockfile: + denoLockfileToNpmDeps { + inherit lockfile registryHostname; + } + ) lockfiles; + sharedCacheBase = pkgs.symlinkJoin { + name = "deno-shared-cache-base"; + paths = map (d: d.drv) allNpmDeps; + }; + in + pkgs.runCommand "deno-shared-cache" {} '' + runHook preInstall + + mkdir -p $out + chmod -R +w $out + + cp -rs ${sharedCacheBase}/* $out + + while IFS='|' read -r dep name; do + chmod +w $out/$dep +cat > $out/$dep/registry.json < $out/$dep/registry.json.tmp + + mv $out/$dep/registry.json.tmp $out/$dep/registry.json + done < <(find $out/$dep -mindepth 1 -maxdepth 1 -type d | xargs -n1 basename) + done < <(echo "${builtins.concatStringsSep "\n" (map (d: "${d.cachePath}|${d.name}") allNpmDeps)}" | sort -u) + + runHook postInstall + ''; + + denoWithCache = + { baseDeno, sharedCache }: + pkgs.writeShellScriptBin "deno" '' + set -euo pipefail + + if [ -n "''${FLAKE_ROOT:-}" ]; then + CACHE_DIR="$FLAKE_ROOT/.deno_cache" + else + echo "FLAKE_ROOT not set. Make sure you're using the flake-root devShell." >&2 + exit 1 + fi + + if [ ! -d "$CACHE_DIR" ] || [ ! "$(ls -A "$CACHE_DIR" 2>/dev/null)" ]; then + echo "Setting up Deno cache at: $CACHE_DIR" >&2 + mkdir -p "$CACHE_DIR" + + if [ -d "${sharedCache}" ]; then + echo "Copying shared cache..." >&2 + cp -rL "${sharedCache}"/* "$CACHE_DIR"/ 2>/dev/null || true + chmod -R u+w "$CACHE_DIR" 2>/dev/null || true + fi + fi + + export DENO_DIR="$CACHE_DIR" + exec "${baseDeno}/bin/deno" "$@" + ''; +}