perform structural deconfuckulation

This commit is contained in:
EatThePooh 2025-12-16 20:39:41 +07:00
parent 9f290b4a88
commit e50afb2a22
4 changed files with 197 additions and 182 deletions

View file

@ -19,14 +19,11 @@
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
perSystem = { config, pkgs, ... }: { perSystem = { config, pkgs, ... }: {
deno = { deno-with-packages.lockfiles = [ ./deno.lock ];
enable = true;
lockfiles = [ ./deno.lock ];
};
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
inputsFrom = [ config.flake-root.devShell ]; inputsFrom = [ config.flake-root.devShell ];
buildInputs = [ config.packages.deno ]; buildInputs = [ config.deno-with-packages.package ];
}; };
}; };
}; };

View file

@ -1,28 +1,16 @@
{ self, lib, flake-parts-lib, ... }: { lib, flake-parts-lib, ... }:
let let
inherit (flake-parts-lib) mkPerSystemOption; inherit (flake-parts-lib) mkPerSystemOption;
inherit (lib) mkOption mkEnableOption types; inherit (lib) mkOption types;
in in
{ {
options.perSystem = mkPerSystemOption (
{ pkgs, config, ... }:
let
impl = import ./impl.nix { inherit pkgs; };
mainSubmodule = types.submodule {
options = { options = {
flake.lib.deno = mkOption { basePackage = 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; type = types.package;
default = pkgs.deno; default = pkgs.deno;
description = "Base deno package to wrap"; description = "Base deno package to wrap";
@ -33,156 +21,47 @@ in
default = "registry.npmjs.org"; default = "registry.npmjs.org";
description = "NPM registry hostname"; description = "NPM registry hostname";
}; };
};
});
config = { lockfiles = mkOption {
flake.lib.deno = rec { type = types.listOf types.path;
denoLockfileToNpmDeps = { pkgs, lockfile, registryHostname }: default = [ ];
let description = "List of deno.lock files to build cache from";
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: dir = mkOption {
let type = types.str;
match = builtins.match "(@?[^@]+)@([^_]+).*" key; default = "$HOME/.cache/deno-nix";
name = builtins.elemAt match 0; description = "What to set DENO_DIR to in the wrapper script";
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 = '' package = mkOption {
runHook preInstall type = types.package;
readOnly = true;
export OUT_PATH="$out/${cachePath}/${version}" description = "Resulting wrapper mimicking Deno executable";
default = impl.denoWithCache {
mkdir -p $OUT_PATH baseDeno = config.deno-with-packages.basePackage;
tar -xzf $src -C $OUT_PATH --strip-components=1 sharedCache = config.deno-with-packages.cache;
};
runHook postInstall };
'';
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;
};
};
}; };
inherit name cachePath;
}; };
in 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;
};
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 <<EOF
{ {
"name": "$name", options.deno-with-packages = mkOption {
"dist-tags": {}, type = mainSubmodule;
"versions": {} description = "Wrapping Deno with a pre-generated cache";
} default = { };
EOF
while IFS= read -r ver; do
SUBSET_FILTER='to_entries | map(select(.key == "version" or .key == "bin" or .key == "dependencies" or .key == "peerDependencies")) | from_entries'
PACKAGE_SUBSET=$(${pkgs.jq}/bin/jq "$SUBSET_FILTER" $out/$dep/$ver/package.json)
${pkgs.jq}/bin/jq --arg version "$ver" --argjson subset "$PACKAGE_SUBSET" \
'.versions[$version] = $subset' \
$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 = { 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;
};
};
};
}; };
} }
);
}

View file

@ -1,7 +1,9 @@
{ {
description = "Provides a Deno executable wrapper pointing to pre-built NPM dependency cache based on lock files"; description = "Provides a Deno executable wrapper pointing to pre-built NPM dependency cache based on lock files";
outputs = { ... }: { outputs =
{ ... }:
{
flakeModule = ./flake-module.nix; flakeModule = ./flake-module.nix;
}; };
} }

137
impl.nix Normal file
View file

@ -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 <<EOF
{
"name": "$name",
"dist-tags": {},
"versions": {}
}
EOF
while IFS= read -r ver; do
SUBSET_FILTER='to_entries | map(select(.key == "version" or .key == "bin" or .key == "dependencies" or .key == "peerDependencies")) | from_entries'
PACKAGE_SUBSET=$(${pkgs.jq}/bin/jq "$SUBSET_FILTER" $out/$dep/$ver/package.json)
${pkgs.jq}/bin/jq --arg version "$ver" --argjson subset "$PACKAGE_SUBSET" \
'.versions[$version] = $subset' \
$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" "$@"
'';
}