Files
nix-flake-lib/flake.nix
2026-03-04 07:36:05 +01:00

307 lines
11 KiB
Nix

# flake.nix — devshell-lib
{
description = "Shared devshell boilerplate library";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
git-hooks.url = "github:cachix/git-hooks.nix";
treefmt-nix.url = "github:numtide/treefmt-nix";
};
outputs =
{
self,
nixpkgs,
treefmt-nix,
git-hooks,
...
}:
let
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
in
{
lib = {
# ── mkDevShell ───────────────────────────────────────────────────────
mkDevShell =
{
system,
extraPackages ? [ ],
extraShellHook ? "",
additionalHooks ? { },
tools ? [ ],
includeStandardPackages ? true,
# tools = list of { name, bin, versionCmd, color? }
# e.g. { name = "Bun"; bin = "${pkgs.bun}/bin/bun"; versionCmd = "--version"; color = "YELLOW"; }
formatters ? { },
# formatters = treefmt-nix programs attrset, merged over { nixfmt.enable = true; }
# e.g. { gofmt.enable = true; shfmt.enable = true; }
formatterSettings ? { },
# formatterSettings = treefmt-nix settings.formatter attrset
# e.g. { shfmt.options = [ "-i" "2" "-s" "-w" ]; }
features ? { },
# features.oxfmt = true → adds pkgs.oxfmt + pkgs.oxlint, enables oxfmt in treefmt
}:
let
pkgs = import nixpkgs { inherit system; };
standardPackages = with pkgs; [
nixfmt
gitlint
gitleaks
shfmt
];
selectedStandardPackages = pkgs.lib.optionals includeStandardPackages standardPackages;
oxfmtEnabled = features.oxfmt or false;
oxfmtPackages = pkgs.lib.optionals oxfmtEnabled [
pkgs.oxfmt
pkgs.oxlint
];
oxfmtFormatters = pkgs.lib.optionalAttrs oxfmtEnabled {
oxfmt.enable = true;
};
treefmtEval = treefmt-nix.lib.evalModule pkgs {
projectRootFile = "flake.nix";
programs = {
nixfmt.enable = true; # always on — every repo has a flake.nix
}
// oxfmtFormatters
// formatters;
settings.formatter = { } // formatterSettings;
};
pre-commit-check = git-hooks.lib.${system}.run {
src = ./.;
hooks = {
treefmt = {
enable = true;
entry = "${treefmtEval.config.build.wrapper}/bin/treefmt";
pass_filenames = true;
};
gitlint.enable = true;
gitleaks = {
enable = true;
entry = "${pkgs.gitleaks}/bin/gitleaks protect --staged";
pass_filenames = false;
};
}
// additionalHooks;
};
toolBannerScript = pkgs.lib.concatMapStrings (
t:
let
colorVar = "$" + (t.color or "YELLOW");
in
''
if command -v ${t.bin} >/dev/null 2>&1; then
printf " $CYAN ${t.name}:$RESET\t${colorVar}%s$RESET\n" "$(${t.bin} ${t.versionCmd})"
fi
''
) tools;
in
{
inherit pre-commit-check;
formatter = treefmtEval.config.build.wrapper;
shell = pkgs.mkShell {
packages = selectedStandardPackages ++ extraPackages ++ oxfmtPackages;
buildInputs = pre-commit-check.enabledPackages;
shellHook = ''
${pre-commit-check.shellHook}
if [ -t 1 ]; then
command -v tput >/dev/null 2>&1 && tput clear || printf '\033c'
fi
GREEN='\033[1;32m'
CYAN='\033[1;36m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
RESET='\033[0m'
printf "\n$GREEN 🚀 Dev shell ready$RESET\n\n"
${toolBannerScript}
printf "\n"
${extraShellHook}
'';
};
};
# ── mkRelease ────────────────────────────────────────────────────────
mkRelease =
{
system,
# Source of truth is always $ROOT_DIR/VERSION.
# Format:
# line 1: X.Y.Z
# line 2: CHANNEL (stable|alpha|beta|rc|internal|...)
# line 3: N (prerelease number, 0 for stable)
postVersion ? "",
# Shell string — runs after VERSION + release steps are written/run, before git add.
# Same env vars available.
release ? [ ],
# Unified list processed in declaration order:
# { file = "path/to/file"; content = ''...$FULL_VERSION...''; } # write file
# { run = ''...shell snippet...''; } # run script
# Example:
# release = [
# {
# file = "src/version.ts";
# content = ''export const APP_VERSION = "$FULL_VERSION" as const;'';
# }
# {
# file = "internal/version/version.go";
# content = ''
# package version
#
# const Version = "$FULL_VERSION"
# '';
# }
# {
# run = ''
# sed -E -i "s#^([[:space:]]*my-lib\\.url = \")github:org/my-lib[^"]*(\";)#\\1github:org/my-lib?ref=$FULL_TAG\\2#" "$ROOT_DIR/flake.nix"
# '';
# }
# ];
# Runtime env includes: BASE_VERSION, CHANNEL, PRERELEASE_NUM, FULL_VERSION, FULL_TAG.
channels ? [
"alpha"
"beta"
"rc"
"internal"
],
# Valid release channels beyond "stable". Validated at runtime.
extraRuntimeInputs ? [ ],
# Extra packages available in the release script's PATH.
}:
let
pkgs = import nixpkgs { inherit system; };
channelList = pkgs.lib.concatStringsSep " " channels;
releaseStepsScript = pkgs.lib.concatMapStrings (
entry:
if entry ? file then
''
mkdir -p "$(dirname "${entry.file}")"
cat > "${entry.file}" << NIXEOF
${entry.content}
NIXEOF
log "Generated version file: ${entry.file}"
''
else if entry ? run then
''
${entry.run}
''
else
builtins.throw "release entry must have either 'file' or 'run'"
) release;
script =
builtins.replaceStrings
[
"__CHANNEL_LIST__"
"__RELEASE_STEPS__"
"__POST_VERSION__"
]
[
channelList
releaseStepsScript
postVersion
]
(builtins.readFile ./packages/release/release.sh);
in
pkgs.writeShellApplication {
name = "release";
runtimeInputs =
with pkgs;
[
git
gnugrep
gawk
gnused
coreutils
]
++ extraRuntimeInputs;
text = script;
};
};
# ── packages ────────────────────────────────────────────────────────────
packages = forAllSystems (system: {
# Expose a no-op release package for the lib repo itself (dogfood)
release = self.lib.mkRelease {
inherit system;
release = [
{
run = ''
sed -E -i "s#^([[:space:]]*devshell-lib\\.url = \")git\\+https://git\\.dgren\\.dev/eric/nix-flake-lib[^\"]*(\";)#\\1git+https://git.dgren.dev/eric/nix-flake-lib?ref=$FULL_TAG\\2#" "$ROOT_DIR/template/flake.nix"
log "Updated template/flake.nix devshell-lib ref to $FULL_TAG"
'';
}
];
};
});
# ── devShells ───────────────────────────────────────────────────────────
devShells = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
env = self.lib.mkDevShell {
inherit system;
extraPackages = with pkgs; [
self.packages.${system}.release
];
tools = [
{
name = "Nix";
bin = "${pkgs.nix}/bin/nix";
versionCmd = "--version";
color = "YELLOW";
}
];
};
in
{
default = env.shell;
}
);
# ── checks ──────────────────────────────────────────────────────────────
checks = forAllSystems (
system:
let
env = self.lib.mkDevShell { inherit system; };
in
{
inherit (env) pre-commit-check;
}
);
# ── formatter ───────────────────────────────────────────────────────────
formatter = forAllSystems (system: (self.lib.mkDevShell { inherit system; }).formatter);
# ── templates ───────────────────────────────────────────────────────────
templates = {
default = {
path = ./template;
description = "Product repo using devshell-lib";
};
};
};
}