2026-03-21 01:40:42 +01:00
2026-03-16 17:42:50 +01:00
2026-03-21 01:27:42 +01:00
2026-03-21 01:40:42 +01:00
2026-03-21 01:27:56 +01:00
2026-03-15 17:10:26 +01:00
2026-03-04 05:07:26 +01:00
2026-03-21 01:27:42 +01:00
2026-03-21 01:27:42 +01:00
2026-03-21 01:27:56 +01:00
2026-03-21 01:27:56 +01:00

repo-lib

repo-lib is a pure-first Nix flake library for repo-level developer workflows:

  • mkRepo for devShells, checks, formatter, and optional packages.release
  • structured tool banners driven from package-backed tool specs
  • structured release steps (writeFile, replace, versionMetaSet, versionMetaUnset)
  • a Bun-only Moonrepo + TypeScript + Varlock template in template/

Audit and replacement review: docs/reviews/2026-03-21-repo-lib-audit.md

Prerequisites

  • Nix with flakes enabled
  • direnv (recommended)

Use the template

nix flake new myapp -t 'git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.5.1#default' --refresh

The generated repo includes:

  • a repo-lib-managed Nix flake
  • Bun as the only JS runtime and package manager
  • Moonrepo root tasks
  • shared TypeScript configs adapted from ../moon
  • Varlock with a committed .env.schema
  • empty apps/ and packages/ directories for new projects

Use the library

Add this flake input:

inputs.repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.5.1";
inputs.repo-lib.inputs.nixpkgs.follows = "nixpkgs";

Build your repo outputs from mkRepo:

outputs = { self, nixpkgs, repo-lib, ... }:
  repo-lib.lib.mkRepo {
    inherit self nixpkgs;
    src = ./.;

    config = {
      checks.tests = {
        command = "echo 'No tests defined yet.'";
        stage = "pre-push";
        passFilenames = false;
      };

      release = {
        steps = [ ];
      };
    };

    perSystem = { pkgs, system, ... }: {
      tools = [
        (repo-lib.lib.tools.fromCommand {
          name = "Nix";
          version.args = [ "--version" ];
          command = "nix";
        })
      ];

      shell.packages = [
        self.packages.${system}.release
      ];
    };
  };

mkRepo generates:

  • devShells.${system}.default
  • checks.${system}.hook-check
  • checks.${system}.lefthook-check
  • formatter.${system}
  • packages.${system}.release when config.release != null
  • merged packages and apps from perSystem

Checks are installed through lefthook, with pre-commit and pre-push commands configured to run in parallel. repo-lib also sets Lefthook output = [ "failure" "summary" ] by default.

For advanced Lefthook features, use raw config.lefthook or perSystem.lefthook. Those attrsets are merged after generated checks, so you can augment a generated command with fields that the simple checks abstraction does not carry, such as stage_fixed:

config.lefthook.pre-push.commands.tests.stage_fixed = true;

Tool banners

Tools are declared once. Package-backed tools are added to the shell automatically, and both package-backed and command-backed tools are rendered in the startup banner.

(repo-lib.lib.tools.fromPackage {
  name = "Go";
  package = pkgs.go;
  version.args = [ "version" ];
  banner.color = "CYAN";
})

Required tools fail shell startup if their version probe fails. This keeps banner output honest instead of silently hiding misconfiguration.

When a tool should come from the host environment instead of nixpkgs, use fromCommand:

(repo-lib.lib.tools.fromCommand {
  name = "Nix";
  command = "nix";
  version.args = [ "--version" ];
})

Purity model

The default path is pure: declare tools and packages in Nix, then let mkRepo assemble the shell.

Impure bootstrap work is still possible, but it must be explicit:

config.shell = {
  bootstrap = ''
    export GOBIN="$PWD/.tools/bin"
    export PATH="$GOBIN:$PATH"
  '';
  allowImpureBootstrap = true;
};

Release steps

Structured release steps are preferred over raw sed snippets:

config.release = {
  steps = [
    {
      writeFile = {
        path = "src/version.ts";
        text = ''
          export const APP_VERSION = "$FULL_VERSION" as const;
        '';
      };
    }
    {
      replace = {
        path = "README.md";
        regex = ''^(version = ")[^"]*(")$'';
        replacement = ''\1$FULL_VERSION\2'';
      };
    }
    {
      versionMetaSet = {
        key = "desktop_binary_version_max";
        value = "$FULL_VERSION";
      };
    }
  ];
};

The generated release command still supports:

release
release select
release --dry-run patch
release patch
release patch --commit
release patch --commit --tag
release patch --commit --tag --push
release beta
release minor beta
release stable
release set 1.2.3

By default, release updates repo files, runs structured release steps, executes postVersion, and runs nix fmt, but it does not commit, tag, or push unless you opt in with flags.

  • --commit stages all changes and creates chore(release): <tag>
  • --tag creates the Git tag after commit
  • --push pushes the current branch and, when tagging is enabled, pushes tags too
  • --dry-run resolves and prints the plan without mutating the repo

When release runs with no args in an interactive terminal, it opens a Bubble Tea picker so you can preview the exact command, flags, and resolved next version before executing it. Use release select to force that picker explicitly.

Low-level APIs

mkRelease remains available for repos that want lower-level control over release automation.

Common command

nix fmt
Description
Personal lib for bootstrapping repositories
Readme 16 MiB
Languages
Go 60.9%
Nix 32.7%
Shell 6.4%