yarn2nix: drop (#513497)

This commit is contained in:
Leona Maroni 2026-04-26 05:53:38 +00:00 committed by GitHub
commit 1136f7c56d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 18 additions and 7840 deletions

View file

@ -45,17 +45,14 @@ If a particular lock file is present, it is a strong indication of which package
It's better to try to use a Nix tool that understands the lock file.
Using a different tool might give you a hard-to-understand error because different packages have been installed.
An example of problems that could arise can be found [here](https://github.com/NixOS/nixpkgs/pull/126629).
Upstream use npm, but this is an attempt to package it with `yarn2nix` (that uses yarn.lock).
Using a different tool forces you to commit a lock file to the repository.
These files are fairly large, so when packaging for nixpkgs, this approach does not scale well.
Exceptions to this rule are:
- When you encounter one of the bugs from a Nix tool. In each of the tool-specific instructions, known problems will be detailed. If you have a problem with a particular tool, then it's best to try another tool, even if this means you will have to re-create a lock file and commit it to Nixpkgs. In general `yarn2nix` has fewer known problems, and so a simple search in Nixpkgs will reveal many `yarn.lock` files committed.
- When you encounter one of the bugs from a Nix tool. In each of the tool-specific instructions, known problems will be detailed. If you have a problem with a particular tool, then it's best to try another tool, even if this means you will have to re-create a lock file and commit it to Nixpkgs.
- Some lock files contain particular version of a package that has been pulled off npm for some reason. In that case, you can recreate upstream lock (by removing the original and `npm install`, `yarn`, ...) and commit this to nixpkgs.
- The only tool that supports workspaces (a feature of npm that helps manage sub-directories with different package.json from a single top level package.json) is `yarn2nix`. If upstream has workspaces you should try `yarn2nix`.
### Try to use upstream package.json {#javascript-upstream-package-json}
@ -92,7 +89,6 @@ Exceptions to this rule are:
Each tool has an abstraction to just build the node_modules (dependencies) directory.
You can always use the `stdenv.mkDerivation` with the node_modules to build the package (symlink the node_modules directory and then use the package build command).
The node_modules abstraction can be also used to build some web framework frontends.
For an example of this see how [plausible](https://github.com/NixOS/nixpkgs/blob/master/pkgs/servers/web-apps/plausible/default.nix) is built. `mkYarnModules` to make the derivation containing node_modules.
Then when building the frontend you can just symlink the node_modules directory.
## Tool-specific instructions {#javascript-tool-specific}
@ -598,139 +594,6 @@ To install the package `yarnInstallHook` uses both `npm` and `yarn` to cleanup p
- `yarnKeepDevDeps`: Disables the removal of devDependencies from `node_modules` before installation.
#### yarn2nix {#javascript-yarn2nix}
> [!WARNING]
> The `yarn2nix` functions have been deprecated in favor of `yarnConfigHook`, `yarnBuildHook` and `yarnInstallHook` (for Yarn v1) and `yarn-berry_*.*` tooling (Yarn v3 and v4). Documentation for `yarn2nix` functions still appears here for the sake of the packages that still use them. See also a tracking issue [#324246](https://github.com/NixOS/nixpkgs/issues/324246).
##### Preparation {#javascript-yarn2nix-preparation}
You will need at least a `yarn.lock` file. If upstream does not have one you need to generate it and reference it in your package definition.
If the downloaded files contain the `package.json` and `yarn.lock` files they can be used like this:
```nix
{
offlineCache = fetchYarnDeps {
yarnLock = src + "/yarn.lock";
hash = "....";
};
}
```
##### mkYarnPackage {#javascript-yarn2nix-mkYarnPackage}
> [!WARNING]
> The `mkYarnPackage` functions have been deprecated in favor of `yarnConfigHook`, `yarnBuildHook` and `yarnInstallHook` (for Yarn v1) and `yarn-berry_*.*` tooling (Yarn v3 and v4). Documentation for `mkYarnPackage` functions still appears here for the sake of the packages that still use them. See also a tracking issue [#324246](https://github.com/NixOS/nixpkgs/issues/324246).
`mkYarnPackage` will by default try to generate a binary. For packages only generating static assets (Svelte, Vue, React, Webpack, ...), you will need to explicitly override the build step with your instructions.
It's important to use the `--offline` flag. For example if you script is `"build": "something"` in `package.json` use:
```nix
{
nativeBuildInputs = [ writableTmpDirAsHomeHook ];
buildPhase = ''
runHook preBuild
yarn --offline build
runHook postBuild
'';
}
```
The `distPhase` is packing the package's dependencies in a tarball using `yarn pack`. You can disable it using:
```nix
{ doDist = false; }
```
The configure phase can sometimes fail because it makes many assumptions that may not always apply. One common override is:
```nix
{
configurePhase = ''
runHook preConfigure
ln -s $node_modules node_modules
runHook postConfigure
'';
}
```
or if you need a writeable node_modules directory:
```nix
{
configurePhase = ''
runHook preConfigure
cp -r $node_modules node_modules
chmod +w node_modules
runHook postConfigure
'';
}
```
##### mkYarnModules {#javascript-yarn2nix-mkYarnModules}
This will generate a derivation including the `node_modules` directory.
If you have to build a derivation for an integrated web framework (Rails, Phoenix, etc.), this is probably the easiest way.
#### Overriding dependency behavior {#javascript-mkYarnPackage-overriding-dependencies}
In the `mkYarnPackage` record the property `pkgConfig` can be used to override packages when you encounter problems building.
For instance, say your package is throwing errors when trying to invoke node-sass:
```
ENOENT: no such file or directory, scandir '/build/source/node_modules/node-sass/vendor'
```
To fix this we will specify different versions of build inputs to use, as well as some post install steps to get the software built the way we want:
```nix
mkYarnPackage rec {
pkgConfig = {
node-sass = {
buildInputs = with final; [
python
libsass
pkg-config
];
postInstall = ''
LIBSASS_EXT=auto yarn --offline run build
rm build/config.gypi
'';
};
};
}
```
##### Pitfalls {#javascript-yarn2nix-pitfalls}
- If version is missing from upstream package.json, yarn will silently install nothing. In that case, you will need to override package.json as shown in the [package.json section](#javascript-upstream-package-json)
- Having trouble with `node-gyp`? Try adding these lines to the `yarnPreBuild` steps:
```nix
{
yarnPreBuild = ''
mkdir -p $HOME/.node-gyp/${nodejs.version}
echo 9 > $HOME/.node-gyp/${nodejs.version}/installVersion
ln -sfv ${nodejs}/include $HOME/.node-gyp/${nodejs.version}
export npm_config_nodedir=${nodejs}
'';
}
```
- The `echo 9` steps comes from this answer: <https://stackoverflow.com/a/49139496>
- Exporting the headers in `npm_config_nodedir` comes from this issue: <https://github.com/nodejs/node-gyp/issues/1191#issuecomment-301243919>
- `offlineCache` (described [above](#javascript-yarn2nix-preparation)) must be specified to avoid [Import From Derivation](#ssec-import-from-derivation) (IFD) when used inside Nixpkgs.
#### Yarn Berry v3/v4 {#javascript-yarn-v3-v4}
Yarn Berry (v3 / v4) have similar formats, they start with blocks like these:

View file

@ -448,7 +448,13 @@
"index.html#overriding-the-generator",
"index.html#javascript-node2nix",
"index.html#javascript-node2nix-preparation",
"index.html#javascript-node2nix-pitfalls"
"index.html#javascript-node2nix-pitfalls",
"index.html#javascript-yarn2nix-mkYarnPackage",
"index.html#javascript-yarn2nix",
"index.html#javascript-yarn2nix-preparation",
"index.html#javascript-yarn2nix-mkYarnModules",
"index.html#javascript-mkYarnPackage-overriding-dependencies",
"index.html#javascript-yarn2nix-pitfalls"
],
"sec-nixpkgs-release-26.05-lib": [
"release-notes.html#sec-nixpkgs-release-26.05-lib"
@ -3720,24 +3726,6 @@
"javascript-yarninstallhook": [
"index.html#javascript-yarninstallhook"
],
"javascript-yarn2nix": [
"index.html#javascript-yarn2nix"
],
"javascript-yarn2nix-preparation": [
"index.html#javascript-yarn2nix-preparation"
],
"javascript-yarn2nix-mkYarnPackage": [
"index.html#javascript-yarn2nix-mkYarnPackage"
],
"javascript-yarn2nix-mkYarnModules": [
"index.html#javascript-yarn2nix-mkYarnModules"
],
"javascript-mkYarnPackage-overriding-dependencies": [
"index.html#javascript-mkYarnPackage-overriding-dependencies"
],
"javascript-yarn2nix-pitfalls": [
"index.html#javascript-yarn2nix-pitfalls"
],
"javascript-yarnBerry-missing-hashes": [
"index.html#javascript-yarnBerry-missing-hashes"
],

View file

@ -87,6 +87,8 @@
adding `pkg-config`, `xfce4-dev-tools`, and `wrapGAppsHook3` to your `nativeBuildInputs` and
`--enable-maintainer-mode` to your `configureFlags`.
- `yarn2nix`/`yarn2nix-moretea` and its tooling(`mkYarnPackage`, `mkYarnModules`, and `fixup_yarn_lock`) have been removed as they were unmaintainable in nixpkgs. If you want to build with Yarn V1 going forward, use the hooks instead(`yarnBuildHook`, `yarnConfigHook`, and `yarnInstallHook`). See the yarn v1 documentation in the nixpkgs manual for more details.
- `albert` has been updated to the version 34.0.5. This release redesigns the query system to support stateful asynchronous handlers and infinite scrolling, and adds internationalized tokenization.
This update introduces several breaking changes: the Python plugin interface is now v5.0, the `PATH` plugin has been renamed to `Commandline`, and the QStylesheets-based widgets box model frontend has been removed.
For more information read the [changelog for 34.0.0](https://albertlauncher.github.io/2026/01/19/albert-v34.0.0-released/).

View file

@ -3,7 +3,7 @@
desktop-file-utils,
fetchFromGitHub,
fetchYarnDeps,
fixup_yarn_lock,
fixup-yarn-lock,
gjs,
glib-networking,
gobject-introspection,
@ -41,7 +41,7 @@ stdenv.mkDerivation (finalAttrs: {
pkg-config
wrapGAppsHook4
yarn
fixup_yarn_lock
fixup-yarn-lock
];
buildInputs = [
@ -63,7 +63,7 @@ stdenv.mkDerivation (finalAttrs: {
preConfigure = ''
export HOME="$PWD"
yarn config --offline set yarn-offline-mirror $yarnOfflineCache
fixup_yarn_lock yarn.lock
fixup-yarn-lock yarn.lock
'';
mesonFlags = [

View file

@ -8,7 +8,6 @@
jq,
nix-prefetch-github,
yarn,
yarn2nix,
}:
stdenvNoCC.mkDerivation rec {
@ -29,7 +28,6 @@ stdenvNoCC.mkDerivation rec {
jq
nix-prefetch-github
yarn
yarn2nix
];
meta = {

View file

@ -1,543 +0,0 @@
{
pkgs ? import <nixpkgs> { },
nodejs ? pkgs.nodejs,
yarn ? pkgs.yarn,
allowAliases ? pkgs.config.allowAliases,
}@inputs:
let
inherit (pkgs)
stdenv
lib
callPackage
git
rsync
runCommandLocal
;
compose =
f: g: x:
f (g x);
id = x: x;
composeAll = builtins.foldl' compose id;
# https://docs.npmjs.com/files/package.json#license
# TODO: support expression syntax (OR, AND, etc)
getLicenseFromSpdxId =
licstr: if licstr == "UNLICENSED" then lib.licenses.unfree else lib.getLicenseFromSpdxId licstr;
in
rec {
# Export yarn again to make it easier to find out which yarn was used.
inherit yarn;
# Re-export pkgs
inherit pkgs;
unlessNull = item: alt: if item == null then alt else item;
reformatPackageName =
pname:
let
# regex adapted from `validate-npm-package-name`
# will produce 3 parts e.g.
# "@someorg/somepackage" -> [ "@someorg/" "someorg" "somepackage" ]
# "somepackage" -> [ null null "somepackage" ]
parts = builtins.tail (builtins.match "^(@([^/]+)/)?([^/]+)$" pname);
# if there is no organisation we need to filter out null values.
non-null = builtins.filter (x: x != null) parts;
in
builtins.concatStringsSep "-" non-null;
inherit getLicenseFromSpdxId;
# Generates the yarn.nix from the yarn.lock file
mkYarnNix =
{
yarnLock,
flags ? [ ],
}:
pkgs.runCommand "yarn.nix" { }
"${yarn2nix}/bin/yarn2nix --lockfile ${yarnLock} --no-patch --builtin-fetchgit ${lib.escapeShellArgs flags} > $out";
# Loads the generated offline cache. This will be used by yarn as
# the package source.
importOfflineCache =
yarnNix:
let
pkg = callPackage yarnNix { };
in
pkg.offline_cache;
defaultYarnFlags = [
"--offline"
"--frozen-lockfile"
"--ignore-engines"
];
mkYarnModules =
{
name ? "${pname}-${version}", # safe name and version, e.g. testcompany-one-modules-1.0.0
pname, # original name, e.g @testcompany/one
version,
packageJSON,
yarnLock,
yarnNix ? mkYarnNix { inherit yarnLock; },
offlineCache ? importOfflineCache yarnNix,
yarnFlags ? [ ],
ignoreScripts ? true,
nodejs ? inputs.nodejs,
yarn ? inputs.yarn.override { inherit nodejs; },
pkgConfig ? { },
preBuild ? "",
postBuild ? "",
workspaceDependencies ? [ ], # List of yarn packages
packageResolutions ? { },
}:
let
extraNativeBuildInputs = lib.concatMap (key: pkgConfig.${key}.nativeBuildInputs or [ ]) (
builtins.attrNames pkgConfig
);
extraBuildInputs = lib.concatMap (key: pkgConfig.${key}.buildInputs or [ ]) (
builtins.attrNames pkgConfig
);
postInstall = map (
key:
if (pkgConfig.${key} ? postInstall) then
''
for f in $(find -L -path '*/node_modules/${key}' -type d); do
(cd "$f" && (${pkgConfig.${key}.postInstall}))
done
''
else
""
) (builtins.attrNames pkgConfig);
# build-time JSON generation to avoid IFD
# see https://wiki.nixos.org/wiki/Import_From_Derivation
workspaceJSON =
pkgs.runCommand "${name}-workspace-package.json"
{
nativeBuildInputs = [ pkgs.jq ];
inherit packageJSON;
passAsFile = [ "baseJSON" ];
baseJSON = builtins.toJSON {
private = true;
workspaces = [ "deps/**" ];
resolutions = packageResolutions;
};
}
''
jq --slurpfile packageJSON "$packageJSON" '.resolutions = $packageJSON[0].resolutions + .resolutions' <"$baseJSONPath" >$out
'';
workspaceDependencyLinks = lib.concatMapStringsSep "\n" (dep: ''
mkdir -p "deps/${dep.pname}"
ln -sf ${dep.packageJSON} "deps/${dep.pname}/package.json"
'') workspaceDependencies;
in
stdenv.mkDerivation {
inherit preBuild postBuild name;
dontUnpack = true;
dontInstall = true;
nativeBuildInputs = [
yarn
nodejs
git
]
++ extraNativeBuildInputs;
buildInputs = extraBuildInputs;
configurePhase =
lib.optionalString (offlineCache ? outputHash) ''
if ! cmp -s ${yarnLock} ${offlineCache}/yarn.lock; then
echo "yarn.lock changed, you need to update the fetchYarnDeps hash"
exit 1
fi
''
+ ''
# Yarn writes cache directories etc to $HOME.
export HOME=$PWD/yarn_home
'';
buildPhase = ''
runHook preBuild
mkdir -p "deps/${pname}"
cp ${packageJSON} "deps/${pname}/package.json"
cp ${workspaceJSON} ./package.json
cp ${yarnLock} ./yarn.lock
chmod +w ./yarn.lock
yarn config --offline set yarn-offline-mirror ${offlineCache}
# Do not look up in the registry, but in the offline cache.
${fixup_yarn_lock}/bin/fixup_yarn_lock yarn.lock
${workspaceDependencyLinks}
yarn install ${
lib.escapeShellArgs (defaultYarnFlags ++ lib.optional ignoreScripts "--ignore-scripts" ++ yarnFlags)
}
${lib.concatStringsSep "\n" postInstall}
mkdir $out
mv node_modules $out/
mv deps $out/
patchShebangs $out
runHook postBuild
'';
dontCheckForBrokenSymlinks = true;
};
# This can be used as a shellHook in mkYarnPackage. It brings the built node_modules into
# the shell-hook environment.
linkNodeModulesHook = ''
if [[ -d node_modules || -L node_modules ]]; then
echo "./node_modules is present. Replacing."
rm -rf node_modules
fi
ln -s "$node_modules" node_modules
'';
mkYarnWorkspace =
{
src,
packageJSON ? src + "/package.json",
yarnLock ? src + "/yarn.lock",
nodejs ? inputs.nodejs,
yarn ? inputs.yarn.override { inherit nodejs; },
packageOverrides ? { },
...
}@attrs:
let
package = lib.importJSON packageJSON;
packageGlobs =
if lib.isList package.workspaces then package.workspaces else package.workspaces.packages;
packageResolutions = package.resolutions or { };
globElemToRegex = lib.replaceStrings [ "*" ] [ ".*" ];
# PathGlob -> [PathGlobElem]
splitGlob = lib.splitString "/";
# Path -> [PathGlobElem] -> [Path]
# Note: Only directories are included, everything else is filtered out
expandGlobList =
base: globElems:
let
elemRegex = globElemToRegex (lib.head globElems);
rest = lib.tail globElems;
children = lib.attrNames (
lib.filterAttrs (name: type: type == "directory") (builtins.readDir base)
);
matchingChildren = lib.filter (child: builtins.match elemRegex child != null) children;
in
if globElems == [ ] then
[ base ]
else
lib.concatMap (child: expandGlobList (base + ("/" + child)) rest) matchingChildren;
# Path -> PathGlob -> [Path]
expandGlob = base: glob: expandGlobList base (splitGlob glob);
packagePaths = lib.concatMap (expandGlob src) packageGlobs;
packages = lib.listToAttrs (
map (
src:
let
packageJSON = src + "/package.json";
package = lib.importJSON packageJSON;
allDependencies = lib.foldl (a: b: a // b) { } (
map (field: lib.attrByPath [ field ] { } package) [
"dependencies"
"devDependencies"
]
);
# { [name: String] : { pname : String, packageJSON : String, ... } } -> { [pname: String] : version } -> [{ pname : String, packageJSON : String, ... }]
getWorkspaceDependencies =
packages: allDependencies:
let
packageList = lib.attrValues packages;
in
composeAll [
(lib.filter (x: x != null))
(lib.mapAttrsToList (
pname: _version: lib.findFirst (package: package.pname == pname) null packageList
))
] allDependencies;
workspaceDependencies = getWorkspaceDependencies packages allDependencies;
name = reformatPackageName package.name;
in
{
inherit name;
value = mkYarnPackage (
removeAttrs attrs [ "packageOverrides" ]
// {
inherit
src
packageJSON
yarnLock
nodejs
yarn
packageResolutions
workspaceDependencies
;
}
// lib.attrByPath [ name ] { } packageOverrides
);
}
) packagePaths
);
in
packages;
mkYarnPackage =
{
name ? null,
src,
packageJSON ? src + "/package.json",
yarnLock ? src + "/yarn.lock",
yarnNix ? mkYarnNix { inherit yarnLock; },
offlineCache ? importOfflineCache yarnNix,
nodejs ? inputs.nodejs,
yarn ? inputs.yarn.override { inherit nodejs; },
yarnFlags ? [ ],
yarnPreBuild ? "",
yarnPostBuild ? "",
pkgConfig ? { },
extraBuildInputs ? [ ],
publishBinsFor ? null,
workspaceDependencies ? [ ], # List of yarnPackages
packageResolutions ? { },
...
}@attrs:
let
package = lib.importJSON packageJSON;
pname = attrs.pname or package.name;
safeName = reformatPackageName package.name;
version = attrs.version or package.version;
baseName = unlessNull name "${safeName}-${version}";
workspaceDependenciesTransitive = lib.unique (
(lib.flatten (map (dep: dep.workspaceDependencies) workspaceDependencies)) ++ workspaceDependencies
);
deps = mkYarnModules {
pname = package.name;
name = "${safeName}-modules-${version}";
preBuild = yarnPreBuild;
postBuild = yarnPostBuild;
workspaceDependencies = workspaceDependenciesTransitive;
inherit
packageJSON
version
yarnLock
offlineCache
nodejs
yarn
yarnFlags
pkgConfig
packageResolutions
;
};
publishBinsFor_ = unlessNull publishBinsFor [ package.name ];
linkDirFunction = ''
linkDirToDirLinks() {
target=$1
if [ ! -f "$target" ]; then
mkdir -p "$target"
elif [ -L "$target" ]; then
local new=$(mktemp -d)
trueSource=$(realpath "$target")
if [ "$(ls $trueSource | wc -l)" -gt 0 ]; then
ln -s $trueSource/* $new/
fi
rm -r "$target"
mv "$new" "$target"
fi
}
'';
workspaceDependencyCopy = lib.concatMapStringsSep "\n" (dep: ''
# ensure any existing scope directory is not a symlink
linkDirToDirLinks "$(dirname node_modules/${dep.package.name})"
mkdir -p "deps/${dep.package.name}"
tar -xf "${dep}/tarballs/${dep.name}.tgz" --directory "deps/${dep.package.name}" --strip-components=1
if [ ! -e "deps/${dep.package.name}/node_modules" ]; then
ln -s "${deps}/deps/${dep.package.name}/node_modules" "deps/${dep.package.name}/node_modules"
fi
'') workspaceDependenciesTransitive;
in
stdenv.mkDerivation (
removeAttrs attrs [
"yarnNix"
"pkgConfig"
"workspaceDependencies"
"packageResolutions"
]
// {
inherit pname version src;
name = baseName;
buildInputs = [
yarn
nodejs
rsync
]
++ extraBuildInputs;
node_modules = deps + "/node_modules";
configurePhase =
attrs.configurePhase or ''
runHook preConfigure
for localDir in npm-packages-offline-cache node_modules; do
if [[ -d $localDir || -L $localDir ]]; then
echo "$localDir dir present. Removing."
rm -rf $localDir
fi
done
# move convent of . to ./deps/${package.name}
mv $PWD $NIX_BUILD_TOP/temp
mkdir -p "$PWD/deps/${package.name}"
rm -fd "$PWD/deps/${package.name}"
mv $NIX_BUILD_TOP/temp "$PWD/deps/${package.name}"
cd $PWD
ln -s ${deps}/deps/${package.name}/node_modules "deps/${package.name}/node_modules"
cp -r $node_modules node_modules
chmod -R +w node_modules
${linkDirFunction}
linkDirToDirLinks "$(dirname node_modules/${package.name})"
ln -s "deps/${package.name}" "node_modules/${package.name}"
${workspaceDependencyCopy}
# Help yarn commands run in other phases find the package
echo "--cwd deps/${package.name}" > .yarnrc
runHook postConfigure
'';
# Replace this phase on frontend packages where only the generated
# files are an interesting output.
installPhase =
attrs.installPhase or ''
runHook preInstall
mkdir -p $out/{bin,libexec/${package.name}}
mv node_modules $out/libexec/${package.name}/node_modules
mv deps $out/libexec/${package.name}/deps
node ${./internal/fixup_bin.js} $out/bin $out/libexec/${package.name}/node_modules ${lib.concatStringsSep " " publishBinsFor_}
runHook postInstall
'';
dontCheckForBrokenSymlinks = true;
doDist = attrs.doDist or true;
distPhase =
attrs.distPhase or ''
# pack command ignores cwd option
rm -f .yarnrc
cd $out/libexec/${package.name}/deps/${package.name}
mkdir -p $out/tarballs/
yarn pack --offline --ignore-scripts --filename $out/tarballs/${baseName}.tgz
'';
passthru = {
inherit package packageJSON deps;
workspaceDependencies = workspaceDependenciesTransitive;
}
// (attrs.passthru or { });
meta = {
inherit (nodejs.meta) platforms;
}
// lib.optionalAttrs (package ? description) { inherit (package) description; }
// lib.optionalAttrs (package ? homepage) { inherit (package) homepage; }
// lib.optionalAttrs (package ? license) { license = getLicenseFromSpdxId package.license; }
// (attrs.meta or { });
}
);
yarn2nix = mkYarnPackage {
src = ./yarn2nix;
# yarn2nix is the only package that requires the yarnNix option.
# All the other projects can auto-generate that file.
yarnNix = ./yarn.nix;
# Using the filter above and importing package.json from the filtered
# source results in an error in restricted mode. To circumvent this,
# we import package.json from the unfiltered source
packageJSON = ./yarn2nix/package.json;
yarnFlags = defaultYarnFlags ++ [
"--ignore-scripts"
"--production=true"
];
nativeBuildInputs = [ pkgs.makeWrapper ];
buildPhase = ''
source ${./nix/expectShFunctions.sh}
expectFilePresent ./node_modules/.yarn-integrity
# check dependencies are installed
expectFilePresent ./node_modules/@yarnpkg/lockfile/package.json
# check devDependencies are not installed
expectFileOrDirAbsent ./node_modules/.bin/eslint
expectFileOrDirAbsent ./node_modules/eslint/package.json
'';
postInstall = ''
wrapProgram $out/bin/yarn2nix --prefix PATH : "${pkgs.nix-prefetch-git}/bin"
'';
};
fixup_yarn_lock =
runCommandLocal "fixup_yarn_lock"
{
buildInputs = [ nodejs ];
}
''
mkdir -p $out/lib
mkdir -p $out/bin
cp ${./yarn2nix/lib/urlToName.js} $out/lib/urlToName.js
cp ${./internal/fixup_yarn_lock.js} $out/bin/fixup_yarn_lock
patchShebangs $out
'';
}
// lib.optionalAttrs allowAliases {
# Aliases
spdxLicense = getLicenseFromSpdxId; # added 2021-12-01
}

View file

@ -1,53 +0,0 @@
#!/usr/bin/env node
/* Usage:
* node fixup_bin.js <bin_dir> <modules_dir> [<bin_pkg_1>, <bin_pkg_2> ... ]
*/
const fs = require("fs");
const path = require("path");
const derivationBinPath = process.argv[2];
const nodeModules = process.argv[3];
const packagesToPublishBin = process.argv.slice(4);
function processPackage(name) {
console.log("fixup_bin: Processing ", name);
const packagePath = `${nodeModules}/${name}`;
const packageJsonPath = `${packagePath}/package.json`;
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
if (!packageJson.bin) {
console.log("fixup_bin: No binaries provided");
return;
}
// There are two alternative syntaxes for `bin`
// a) just a plain string, in which case the name of the package is the name of the binary.
// b) an object, where key is the name of the eventual binary, and the value the path to that binary.
if (typeof packageJson.bin === "string") {
const binName = packageJson.bin;
packageJson.bin = {};
packageJson.bin[packageJson.name] = binName;
}
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const binName in packageJson.bin) {
const binPath = packageJson.bin[binName];
const normalizedBinName = binName.replace("@", "").replace("/", "-");
const targetPath = path.normalize(`${packagePath}/${binPath}`);
const createdPath = `${derivationBinPath}/${normalizedBinName}`;
console.log(
`fixup_bin: creating link ${createdPath} that points to ${targetPath}`
);
fs.symlinkSync(targetPath, createdPath);
}
}
packagesToPublishBin.forEach(pkg => {
processPackage(pkg);
});

View file

@ -1,49 +0,0 @@
#!/usr/bin/env node
/* Usage:
* node fixup_yarn_lock.js yarn.lock
*/
const fs = require("fs");
const readline = require("readline");
const urlToName = require("../lib/urlToName");
const yarnLockPath = process.argv[2];
const readFile = readline.createInterface({
input: fs.createReadStream(yarnLockPath, { encoding: "utf8" }),
// Note: we use the crlfDelay option to recognize all instances of CR LF
// ('\r\n') in input.txt as a single line break.
crlfDelay: Infinity,
terminal: false // input and output should be treated like a TTY
});
const result = [];
readFile
.on("line", line => {
const arr = line.match(/^ {2}resolved "([^#]+)(#[^"]+)?"$/);
if (arr !== null) {
const [_, url, shaOrRev] = arr;
const fileName = urlToName(url);
result.push(` resolved "${fileName}${shaOrRev ?? ""}"`);
} else {
result.push(line);
}
})
.on("close", () => {
fs.writeFile(yarnLockPath, result.join("\n"), "utf8", err => {
if (err) {
console.error(
"fixup_yarn_lock: fatal error when trying to write to yarn.lock",
err
);
}
});
});

View file

@ -1,30 +0,0 @@
expectFilePresent () {
if [ -f "$1" ]; then
echo "Test passed: file is present - $1"
else
echo "Test failed: file is absent - $1"
exit 1
fi
}
expectFileOrDirAbsent () {
if [ ! -e "$1" ];
then
echo "Test passed: file or dir is absent - $1"
else
echo "Test failed: file or dir is present - $1"
exit 1
fi
}
expectEqual () {
if [ "$1" == "$2" ];
then
echo "Test passed: output is equal to expected_output"
else
echo "Test failed: output is not equal to expected_output:"
echo " output - $1"
echo " expected_output - $2"
exit 1
fi
}

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
node_modules

View file

@ -1,90 +0,0 @@
#!/usr/bin/env node
const fs = require("fs");
const lockfile = require("@yarnpkg/lockfile");
const { docopt } = require("docopt");
const deepEqual = require("deep-equal");
const R = require("ramda");
const fixPkgAddMissingSha1 = require("../lib/fixPkgAddMissingSha1");
const mapObjIndexedReturnArray = require("../lib/mapObjIndexedReturnArray");
const generateNix = require("../lib/generateNix");
const USAGE = `
Usage: yarn2nix [options]
Options:
-h --help Shows this help.
--no-nix Hide the nix output
--no-patch Don't patch the lockfile if hashes are missing
--lockfile=FILE Specify path to the lockfile [default: ./yarn.lock].
--builtin-fetchgit Use builtin fetchGit for git dependencies to support on-the-fly generation of yarn.nix without an internet connection
`;
const options = docopt(USAGE);
const data = fs.readFileSync(options["--lockfile"], "utf8");
// json example:
// {
// type:'success',
// object:{
// 'abbrev@1':{
// version:'1.0.9',
// resolved:'https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135'
// },
// 'shell-quote@git+https://github.com/srghma/node-shell-quote.git#without_unlicenced_jsonify':{
// version:'1.6.0',
// resolved:'git+https://github.com/srghma/node-shell-quote.git#0aa381896e0cd7409ead15fd444f225807a61e0a'
// },
// '@graphile/plugin-supporter@git+https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git':{
// version:'1.6.0',
// resolved:'git+https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git#1234commit'
// },
// }
// }
const json = lockfile.parse(data);
if (json.type !== "success") {
throw new Error("yarn.lock parse error");
}
// Check for missing hashes in the yarn.lock and patch if necessary
let pkgs = R.pipe(
mapObjIndexedReturnArray((value, key) => ({
...value,
nameWithVersion: key,
})),
R.uniqBy(R.prop("resolved")),
)(json.object);
(async () => {
if (!options["--no-patch"]) {
pkgs = await Promise.all(R.map(fixPkgAddMissingSha1, pkgs));
}
const origJson = lockfile.parse(data);
if (!deepEqual(origJson, json)) {
console.error("found changes in the lockfile", options["--lockfile"]);
if (options["--no-patch"]) {
console.error("...aborting");
process.exit(1);
}
fs.writeFileSync(options["--lockfile"], lockfile.stringify(json.object));
}
if (!options["--no-nix"]) {
// print to stdout
console.log(generateNix(pkgs, options["--builtin-fetchgit"]));
}
})().catch((error) => {
console.error(error);
process.exit(1);
});

View file

@ -1,24 +0,0 @@
import js from "@eslint/js";
import globals from "globals";
import { defineConfig } from "eslint/config";
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"],
plugins: { js },
extends: ["js/recommended"],
rules: {
"no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_"
}
]
},
},
{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } },
{ files: ["**/*.{js,mjs,cjs}"], languageOptions: { globals: globals.node } },
]);

View file

@ -1,64 +0,0 @@
const https = require("https");
const crypto = require("crypto");
// TODO:
// make test case where getSha1 function is used, i.e. the case when resolved is without sha1?
// consider using https://github.com/request/request-promise-native
function getSha1(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
const { statusCode } = res;
const hash = crypto.createHash("sha1");
if (statusCode !== 200) {
const err = new Error(`Request Failed.\nStatus Code: ${statusCode}`);
// consume response data to free up memory
res.resume();
reject(err);
}
res.on("data", (chunk) => {
hash.update(chunk);
});
res.on("end", () => {
resolve(hash.digest("hex"));
});
res.on("error", reject);
});
});
}
// Object -> Object
async function fixPkgAddMissingSha1(pkg) {
// local dependency
if (!pkg.resolved) {
console.error(
`yarn2nix: can't find "resolved" field for package ${pkg.nameWithVersion}, you probably required it using "file:...", this feature is not supported, ignoring`,
);
return pkg;
}
const [url, sha1] = pkg.resolved.split("#", 2);
if (sha1 || url.startsWith("https://codeload.github.com/")) {
return pkg;
}
// if there is no sha1 in resolved url
// (this could happen if yarn.lock was generated by older version of yarn)
// - request it from registry by https and add it to pkg
const newSha1 = await getSha1(url);
return {
...pkg,
resolved: `${url}#${newSha1}`,
};
}
module.exports = fixPkgAddMissingSha1;

View file

@ -1,179 +0,0 @@
const R = require("ramda");
const ssri = require("ssri");
const urlToName = require("./urlToName");
const { execFileSync } = require("child_process");
// fetchgit transforms
//
// "shell-quote@git+https://github.com/srghma/node-shell-quote.git#without_unlicenced_jsonify":
// version "1.6.0"
// resolved "git+https://github.com/srghma/node-shell-quote.git#1234commit"
//
// to
//
// fetchGit {
// url = "https://github.com/srghma/node-shell-quote.git";
// ref = "without_unlicenced_jsonify";
// rev = "1234commit";
// }
//
// and transforms
//
// "@graphile/plugin-supporter@git+https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git":
// version "0.6.0"
// resolved "git+https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git#1234commit"
//
// to
//
// fetchGit {
// url = "https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git";
// ref = "master";
// rev = "1234commit";
// }
function prefetchgit(url, rev) {
return JSON.parse(
execFileSync(
"nix-prefetch-git",
["--rev", rev, url, "--fetch-submodules"],
{
stdio: ["ignore", "pipe", "ignore"],
timeout: 60000,
},
),
).sha256;
}
function fetchgit(fileName, url, rev, branch, builtinFetchGit) {
const repo = builtinFetchGit
? `fetchGit ({
url = "${url}";
ref = "${branch}";
rev = "${rev}";
} // (if builtins.compareVersions "2.4pre" builtins.nixVersion < 0 then {
# workaround for https://github.com/NixOS/nix/issues/5128
allRefs = true;
} else {}))`
: `fetchgit {
url = "${url}";
rev = "${rev}";
sha256 = "${prefetchgit(url, rev)}";
}`;
return ` {
name = "${fileName}";
path =
let repo = ${repo};
in runCommand "${fileName}" { buildInputs = [gnutar]; } ''
# Set u+w because tar-fs can't unpack archives with read-only dirs
# https://github.com/mafintosh/tar-fs/issues/79
tar cf $out --mode u+w -C \${repo} .
'';
}`;
}
/**
* Parse an integrity hash out of an SSRI string.
*
* Provides a default and uses the "best" supported algorithm if there are multiple.
*/
function parseIntegrity(maybeIntegrity, fallbackHash) {
if (!maybeIntegrity && fallbackHash) {
return { algo: "sha1", hash: fallbackHash };
}
const integrities = ssri.parse(maybeIntegrity);
for (const key in integrities) {
if (!/^sha(1|256|512)$/.test(key)) {
delete integrities[key];
}
}
const algo = integrities.pickAlgorithm();
const hash = integrities[algo][0].digest;
return { algo, hash };
}
function fetchLockedDep(builtinFetchGit) {
return function (pkg) {
const { integrity, nameWithVersion, resolved } = pkg;
if (!resolved) {
console.error(
`yarn2nix: can't find "resolved" field for package ${nameWithVersion}, you probably required it using "file:...", this feature is not supported, ignoring`,
);
return "";
}
const [url, sha1OrRev] = resolved.split("#");
const fileName = urlToName(url);
if (resolved.startsWith("https://codeload.github.com/")) {
const s = resolved.split("/");
const githubUrl = `https://github.com/${s[3]}/${s[4]}.git`;
const githubRev = s[6];
const [_, branch] = nameWithVersion.split("#");
return fetchgit(
fileName,
githubUrl,
githubRev,
branch || "master",
builtinFetchGit,
);
}
if (url.startsWith("git+") || url.startsWith("git:")) {
const rev = sha1OrRev;
const [_, branch] = nameWithVersion.split("#");
const urlForGit = url.replace(/^git\+/, "");
return fetchgit(
fileName,
urlForGit,
rev,
branch || "master",
builtinFetchGit,
);
}
const { algo, hash } = parseIntegrity(integrity, sha1OrRev);
return ` {
name = "${fileName}";
path = fetchurl {
name = "${fileName}";
url = "${url}";
${algo} = "${hash}";
};
}`;
};
}
const HEAD = `
{ fetchurl, fetchgit, linkFarm, runCommand, gnutar }: rec {
offline_cache = linkFarm "offline" packages;
packages = [
`.trim();
// Object -> String
function generateNix(pkgs, builtinFetchGit) {
const nameWithVersionAndPackageNix = R.map(
fetchLockedDep(builtinFetchGit),
pkgs,
);
const packagesDefinition = R.join(
"\n",
R.values(nameWithVersionAndPackageNix),
);
return R.join("\n", [HEAD, packagesDefinition, " ];", "}"]);
}
module.exports = generateNix;

View file

@ -1,21 +0,0 @@
const _curry2 = require("ramda/src/internal/_curry2");
const _map = require("ramda/src/internal/_map");
const keys = require("ramda/src/keys");
// mapObjIndexed: ((v, k, {k: v}) → v') → {k: v} → {k: v'}
// mapObjIndexedReturnArray: ((v, k, {k: v}) → v') → {k: v} → [v']
/*
* @example
*
* const xyz = { x: 1, y: 2, z: 3 };
* const prependKeyAndDouble = (num, key, obj) => key + (num * 2);
*
* mapObjIndexedReturnArray(prependKeyAndDouble, xyz); //=> ['x2', 'y4', 'z6']
*/
const mapObjIndexedReturnArray = _curry2((fn, obj) =>
_map((key) => fn(obj[key], key, obj), keys(obj)),
);
module.exports = mapObjIndexedReturnArray;

View file

@ -1,28 +0,0 @@
const path = require("path");
// String -> String
// @url examples:
// - https://registry.yarnpkg.com/acorn-es7-plugin/-/acorn-es7-plugin-1.1.7.tgz
// - https://registry.npmjs.org/acorn-es7-plugin/-/acorn-es7-plugin-1.1.7.tgz
// - git+https://github.com/srghma/node-shell-quote.git
// - git+https://1234user:1234pass@git.graphile.com/git/users/1234user/postgraphile-supporter.git
// - https://codeload.github.com/Gargron/emoji-mart/tar.gz/934f314fd8322276765066e8a2a6be5bac61b1cf
function urlToName(url) {
// Yarn generates `codeload.github.com` tarball URLs, where the final
// path component (file name) is the git hash. See #111.
// See also https://github.com/yarnpkg/yarn/blob/989a7406/src/resolvers/exotics/github-resolver.js#L24-L26
let isCodeloadGitTarballUrl =
url.startsWith("https://codeload.github.com/") && url.includes("/tar.gz/");
if (url.startsWith("git+") || isCodeloadGitTarballUrl) {
return path.basename(url);
}
return url
.replace(/https:\/\/(.)*(.com)\//g, "") // prevents having long directory names
.replace(/[@/%:-]/g, "_"); // replace @ and : and - and % characters with underscore
}
module.exports = urlToName;

View file

@ -1,49 +0,0 @@
{
"name": "yarn2nix",
"version": "1.0.0",
"description": "Convert packages.json and yarn.lock into a Nix expression that downloads all the dependencies",
"main": "index.js",
"repository": ".",
"author": "Maarten Hoogendoorn <maarten@moretea.nl>",
"license": "MIT",
"scripts": {
"yarn2nix": "bin/yarn2nix.js",
"format": "prettier-eslint --write './**/*.{js,jsx,json}'",
"lint": "eslint ."
},
"bin": {
"yarn2nix": "bin/yarn2nix.js"
},
"engines": {
"node": ">=22.0.0"
},
"dependencies": {
"@yarnpkg/lockfile": "^1.1.0",
"deep-equal": "^2.2.3",
"docopt": "^0.6.2",
"ramda": "^0.32",
"ssri": "^13.0.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"babel-eslint": "^10.0.1",
"eslint": "^9.39.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^10.1.8",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react": "^7.12.2",
"globals": "^16.5.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"prettier-eslint-cli": "^8.0.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -712,6 +712,7 @@ mapAliases {
firmwareLinuxNonfree = throw "'firmwareLinuxNonfree' has been renamed to/replaced by 'linux-firmware'"; # Converted to throw 2025-10-27
fishfight = throw "'fishfight' has been renamed to/replaced by 'jumpy'"; # Converted to throw 2025-10-27
fit-trackee = throw "'fit-trackee' has been renamed to/replaced by 'fittrackee'"; # Converted to throw 2025-10-27
fixup_yarn_lock = throw "`fixup_yarn_lock` has been removed, please use `fixup-yarn-lock` instead."; # Added 2026-04-25
flashrom-stable = throw "'flashrom-stable' has been renamed to/replaced by 'flashprog'"; # Converted to throw 2025-10-27
flatbuffers_2_0 = throw "'flatbuffers_2_0' has been renamed to/replaced by 'flatbuffers'"; # Converted to throw 2025-10-27
flattenReferencesGraph = warnAlias "'flattenReferencesGraph' has been renamed to 'flatten-references-graph'" flatten-references-graph; # Added 2026-02-08
@ -1372,6 +1373,8 @@ mapAliases {
minizincide = warnAlias "'minizincide' has been renamed to 'minizinc-ide'" minizinc-ide; # Added 2026-01-03
minizip2 = throw "'minizip2' has been renamed to/replaced by 'minizip-ng'"; # Converted to throw 2025-10-27
miru = throw "'miru' has been removed due to lack maintenance"; # Added 2025-08-21
mkYarnModules = throw "'yarn2nix' and its tooling has been removed as it was unusable within nodePackages. Use the standard yarn v1 hooks available in nixpkgs instead."; # Added 2026-04-25
mkYarnPackage = throw "'yarn2nix' and its tooling has been removed as it was unusable within nodePackages. Use the standard yarn v1 hooks available in nixpkgs instead."; # Added 2026-04-25
mlir_16 = throw "mlir_16 has been removed, as it is unmaintained and obsolete"; # Added 2025-08-09
mlir_17 = throw "mlir_17 has been removed, as it is unmaintained and obsolete"; # Added 2025-08-09
MMA = mma; # Added 2026-02-08
@ -2497,6 +2500,8 @@ mapAliases {
yaml-cpp_0_3 = throw "yaml-cpp_0_3 has been removed, as it was unused"; # Added 2025-09-16
yamlpath = throw "'yamlpath' has been removed because it has been marked as broken since at least November 2024."; # Added 2025-10-01
yarGen = warnAlias "'yarGen' has been renamed to 'yargen'" yargen; # Added 2026-02-12
yarn2nix = throw "'yarn2nix' and its tooling has been removed as it was unusable within nodePackages. Use the standard yarn v1 hooks available in nixpkgs instead."; # Added 2026-04-25
yarn2nix-moretea = throw "'yarn2nix' and its tooling has been removed as it was unusable within nodePackages. Use the standard yarn v1 hooks available in nixpkgs instead."; # Added 2026-04-25
yeahwm = throw "'yeahwm' has been removed, as it was broken and unmaintained upstream."; # Added 2025-06-12
yosys-synlig = throw "yosys-synlig has been removed because it is unmaintained upstream and incompatible with current Yosys versions."; # Added 2026-01-06
youtube-music = lib.warnOnInstantiate "'youtube-music' has been renamed to 'pear-desktop'" pear-desktop; # Added 2025-12-23

View file

@ -3569,15 +3569,6 @@ with pkgs;
yarn-berry = yarn-berry_3;
};
yarn2nix-moretea = callPackage ../development/tools/yarn2nix-moretea { };
inherit (yarn2nix-moretea)
yarn2nix
mkYarnPackage
mkYarnModules
fixup_yarn_lock
;
yamllint = with python3Packages; toPythonApplication yamllint;
# To expose more packages for Yi, override the extraPackages arg.