diff --git a/modules/misc/news/2025/12/2025-12-06_11-03-01.nix b/modules/misc/news/2025/12/2025-12-06_11-03-01.nix new file mode 100644 index 00000000..79cd2651 --- /dev/null +++ b/modules/misc/news/2025/12/2025-12-06_11-03-01.nix @@ -0,0 +1,10 @@ +{ + time = "2025-12-06T10:03:01+00:00"; + condition = true; + message = '' + A new module is available: `programs.npm` + + It allows you manage your npm user configuration (`.npmrc`) + and install a specific version of the package. + ''; +} diff --git a/modules/programs/npm.nix b/modules/programs/npm.nix new file mode 100644 index 00000000..015b80b8 --- /dev/null +++ b/modules/programs/npm.nix @@ -0,0 +1,74 @@ +# https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/programs/npm.nix +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.programs.npm; + + xdgConfigHome = lib.removePrefix config.home.homeDirectory config.xdg.configHome; + configFile = if config.home.preferXdgDirectories then "${xdgConfigHome}/npm/npmrc" else ".npmrc"; + + iniFormat = pkgs.formats.ini { + listsAsDuplicateKeys = true; + }; + + toNpmrc = + let + mkLine = lib.generators.mkKeyValueDefault { } "="; + mkLines = k: v: if lib.isList v then map (x: mkLine "${k}[]" x) v else [ (mkLine k v) ]; + in + attrs: lib.concatLines (lib.concatLists (lib.mapAttrsToList mkLines attrs)); +in +{ + meta.maintainers = with lib.maintainers; [ mirkolenz ]; + + options = { + programs.npm = { + enable = lib.mkEnableOption "{command}`npm` user config"; + + package = lib.mkPackageOption pkgs [ "nodejs" ] { + example = "nodejs_24"; + nullable = true; + }; + + settings = lib.mkOption { + type = lib.types.attrsOf iniFormat.lib.types.atom; + description = '' + The user-specific npm configuration. + See and + + for more information. + ''; + default = { + prefix = "\${HOME}/.npm"; + }; + example = lib.literalExpression '' + { + color = true; + include = [ + "dev" + "prod" + ]; + init-license = "MIT"; + prefix = "''${HOME}/.npm"; + } + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + home = { + packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + file.${configFile} = lib.mkIf (cfg.settings != { }) { + text = toNpmrc cfg.settings; + }; + sessionVariables = lib.mkIf (cfg.settings != { }) { + NPM_CONFIG_USERCONFIG = "${config.home.homeDirectory}/${configFile}"; + }; + }; + }; +} diff --git a/tests/modules/programs/npm/default.nix b/tests/modules/programs/npm/default.nix new file mode 100644 index 00000000..d66921ec --- /dev/null +++ b/tests/modules/programs/npm/default.nix @@ -0,0 +1,4 @@ +{ + npm-example-settings = ./example-settings.nix; + npm-no-settings = ./no-settings.nix; +} diff --git a/tests/modules/programs/npm/example-settings.nix b/tests/modules/programs/npm/example-settings.nix new file mode 100644 index 00000000..b8ce0858 --- /dev/null +++ b/tests/modules/programs/npm/example-settings.nix @@ -0,0 +1,36 @@ +{ pkgs, ... }: + +{ + programs.npm = { + enable = true; + settings = { + color = true; + include = [ + "dev" + "prod" + ]; + init-license = "MIT"; + prefix = "\${HOME}/.npm"; + }; + }; + + test.stubs.nodejs = { }; + + nmt.script = + let + configPath = "home-files/.npmrc"; + expectedConfig = pkgs.writeText "npmrc-expected" '' + color=true + include[]=dev + include[]=prod + init-license=MIT + prefix=''${HOME}/.npm + ''; + in + '' + assertFileExists "${configPath}" + assertFileContent "${configPath}" "${expectedConfig}" + assertFileContains home-path/etc/profile.d/hm-session-vars.sh \ + 'export NPM_CONFIG_USERCONFIG="/home/hm-user/.npmrc"' + ''; +} diff --git a/tests/modules/programs/npm/no-settings.nix b/tests/modules/programs/npm/no-settings.nix new file mode 100644 index 00000000..21b0b677 --- /dev/null +++ b/tests/modules/programs/npm/no-settings.nix @@ -0,0 +1,17 @@ +{ ... }: + +{ + programs.npm = { + enable = true; + settings = { }; + }; + + test.stubs.nodejs = { }; + + nmt.script = '' + assertPathNotExists home-files/.npmrc + assertFileExists home-path/etc/profile.d/hm-session-vars.sh + assertFileNotRegex home-path/etc/profile.d/hm-session-vars.sh \ + "export NPM_CONFIG_USERCONFIG=" + ''; +}