Files
nodejs-nix-utils/yarn.nix
2025-07-28 17:40:07 +04:00

141 lines
4.0 KiB
Nix

{ pkgs, ... }:
let
inherit (pkgs) lib;
parseFile =
filePath:
let
fileContent = builtins.readFile filePath;
fileLines = lib.strings.splitString "\n" fileContent;
in
parseLines fileLines { } null false [ ];
parseKeyValue =
line:
let
matchResult = builtins.match ''^[ ]*"?([^" ]+)"?[ ]+"?([^"]+)"?$'' line;
in
if matchResult == null then
null
else if builtins.length matchResult < 2 then
null
else
{
key = builtins.elemAt matchResult 0;
value = builtins.elemAt matchResult 1;
};
isDependencySection =
line: builtins.match ''^[ ]*(dependencies|optionalDependencies):$'' line != null;
parsePackageHeader = line: builtins.match ''^(.*):$'' line;
deepMergeAttr =
attrName: update: attrSet:
let
previous = attrSet.${attrName} or { };
in
attrSet // { "${attrName}" = previous // update; };
cleanKey =
rawKey:
let
# Remove surrounding quotes if present
unquoted =
if builtins.match ''^"(.*)"$'' rawKey != null then
builtins.elemAt (builtins.match ''^"(.*)"$'' rawKey) 0
else
rawKey;
# Remove leading @ if present
noAtPrefix =
if builtins.match ''^@(.*)$'' unquoted != null then
builtins.elemAt (builtins.match ''^@(.*)$'' unquoted) 0
else
unquoted;
# Remove everything after (and including) the last @ if present
splitAt = builtins.match ''^([^@]+)@.*$'' noAtPrefix;
cleaned = if splitAt != null then builtins.elemAt splitAt 0 else noAtPrefix;
in
cleaned;
parseLines =
lines: acc: currentPkg: inDeps: depList:
if lines == [ ] then
acc
else
let
line = builtins.head lines;
rest = builtins.tail lines;
pkgHeader = parsePackageHeader line;
in
if inDeps then
let
kv = parseKeyValue line;
in
if kv != null then
parseLines rest acc currentPkg true (depList ++ [ kv ])
else
parseLines rest (deepMergeAttr currentPkg { deps = depList; } acc) currentPkg false [ ]
else if isDependencySection line then
parseLines rest acc currentPkg true [ ]
else if pkgHeader != null then
let
pkgName = builtins.elemAt pkgHeader 0;
in
parseLines rest (acc // { "${pkgName}" = { }; }) pkgName false depList
else if currentPkg != null then
let
kv = parseKeyValue line;
in
if kv == null then
parseLines rest acc currentPkg false depList
else
parseLines rest (deepMergeAttr currentPkg { "${kv.key}" = kv.value; } acc) currentPkg false depList
else
parseLines rest acc currentPkg false depList;
convertKey =
key:
let
hasSlash = builtins.match ".*/.*" key != null;
split = lib.strings.splitString "/" key;
first = builtins.elemAt split 0;
last = builtins.elemAt split (builtins.length split - 1);
result = if hasSlash then "_${first}_${last}___${last}" else "${key}___${key}";
in
result;
parseMap =
pkgMap:
builtins.listToAttrs (
lib.mapAttrsToList (
originalKey: pkgAttrs:
let
cleanedKey = cleanKey originalKey;
convertedKey = convertKey cleanedKey;
filenameRaw = "${convertedKey}_${pkgAttrs.version}.tgz";
filename = builtins.replaceStrings [ "-" ] [ "_" ] filenameRaw;
in
{
name = filename;
value = pkgs.fetchurl {
url = pkgAttrs.resolved;
hash = pkgAttrs.integrity;
};
}
) pkgMap
);
combineAll =
fileMap: yarnLockPath:
let
copyCmds = lib.mapAttrsToList (filename: filePath: "cp ${filePath} $out/${filename}") fileMap;
script = builtins.concatStringsSep "\n" copyCmds;
in
pkgs.runCommand "combine-all" { } ''
mkdir -p $out;
${script}
cp ${yarnLockPath} $out/yarn.lock
'';
in
{
yarnLock ? null,
}:
combineAll (parseMap (parseFile yarnLock)) yarnLock