diff --git a/README.md b/README.md index 7b47ddd..6426a12 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -> [!WARNING] -> Permanently moved to https://codeberg.org/expede/nix-command-utils - # Nix Command Utils Helpers for defining commands for Nix shells under Flakes. @@ -27,11 +24,11 @@ Helpers for defining commands for Nix shells under Flakes. command_menu = command-utils.commands.${system} { hello = cmd "Print a hello world message" "echo 'Hello, world!'"; - bench = cmd "Run benchmarks, including test utils" - "${cargo} bench --features test_utils"; + "project:build" = cmd "Build the project" + "${cargo} build"; - "bench:host" = cmd "Run host Criterion benchmarks" - "${cargo} criterion"; + "project:test" = cmd "Run tests" + "${cargo} test"; }; #... in @@ -57,15 +54,15 @@ $ menu | |__| (_) | | | | | | | | | | | (_| | | | | (_| \__ \ \____\___/|_| |_| |_|_| |_| |_|\__,_|_| |_|\__,_|___/ -┌────────────┬───────────────────────────────────┐ -│ Command │ Description │ -╞════════════╪═══════════════════════════════════╡ -│ bench │ Run benchmarks, including test utils │ -├────────────┼───────────────────────────────────┤ -│ bench:host │ Run host Criterion benchmarks │ -├────────────┼───────────────────────────────────┤ -│ hello │ Print a hello world message │ -└────────────┴───────────────────────────────────┘ +┌───────────────┬───────────────────────────────┐ +│ Command │ Description │ +╞═══════════════╪═══════════════════════════════╡ +│ hello │ Print a hello world message │ +├───────────────┼───────────────────────────────┤ +│ project:build │ Build the project │ +├───────────────┼───────────────────────────────┤ +│ project:test │ Run tests │ +└───────────────┴───────────────────────────────┘ $ hello ⚙️ Running hello... @@ -80,34 +77,246 @@ Use `withHelp` to add detailed help text accessible via `--help` or `-h`: command_menu = command-utils.commands.${system} { hello = cmd "Print a hello world message" "echo 'Hello, world!'"; - bench = withHelp '' - Usage: bench [OPTIONS] + "rust:bench" = withHelp '' + Usage: rust:bench [OPTIONS] Options: --release Run in release mode --save FILE Save results to FILE Examples: - bench --release - bench --save baseline.json + rust:bench --release + rust:bench --save baseline.json '' (cmd "Run benchmarks" "${cargo} bench"); }; ``` ``` console -$ bench --help -bench - Run benchmarks +$ rust:bench --help +rust:bench - Run benchmarks -Usage: bench [OPTIONS] +Usage: rust:bench [OPTIONS] Options: --release Run in release mode --save FILE Save results to FILE Examples: - bench --release - bench --save baseline.json + rust:bench --release + rust:bench --save baseline.json ``` Commands without `withHelp` pass `--help` through to the underlying script. +## Rust Commands + +Pre-built command sets for Rust development, organized by module: + +| Module | Commands | +|---------------|:-----------------------------------------------------------| +| `rust.test` | `rust:test`, `rust:test:doc`, `rust:test:watch` | +| `rust.bench` | `rust:bench`, `rust:bench:criterion`, `rust:bench:open` | +| `rust.lint` | `rust:clippy`, `rust:clippy:pedantic`, `rust:clippy:fix` | +| `rust.fmt` | `rust:fmt`, `rust:fmt:check` | +| `rust.build` | `rust:build`, `rust:build:release`, `rust:check` | +| `rust.doc` | `rust:doc`, `rust:doc:open`, `rust:doc:check` | +| `rust.watch` | `rust:watch:build`, `rust:watch:clippy`, `rust:watch:test` | +| `rust.audit` | `rust:audit` | +| `rust.semver` | `rust:semver` | +| `rust.msrv` | `rust:msrv:verify`, `rust:msrv:find` | +| `rust.ci` | `rust:ci` (fmt, clippy, test, doc, release build) | +| `rust.all` | All of the above | + +### Importing Specific Modules + +``` nix +let + cargo = "${rust-toolchain}/bin/cargo"; + cargo-watch = "${pkgs.cargo-watch}/bin/cargo-watch"; + + rust = command-utils.rust.${system}; + + command_menu = command-utils.commands.${system} ( + # Pick only what you need + rust.test { inherit cargo cargo-watch; } + // rust.lint { inherit cargo; } + // rust.fmt { inherit cargo; } + // { + # Add your own commands + hello = cmd "Say hello" "echo 'Hello!'"; + } + ); +in + # ... +``` + +### Importing All Rust Commands + +``` nix +let + cargo = "${rust-toolchain}/bin/cargo"; + cargo-watch = "${pkgs.cargo-watch}/bin/cargo-watch"; + cargo-criterion = "${pkgs.cargo-criterion}/bin/cargo-criterion"; + cargo-audit = "${pkgs.cargo-audit}/bin/cargo-audit"; + cargo-semver-checks = "${pkgs.cargo-semver-checks}/bin/cargo-semver-checks"; + xdg-open = "${pkgs.xdg-utils}/bin/xdg-open"; + + rust = command-utils.rust.${system}; + + command_menu = command-utils.commands.${system} ( + rust.all { + inherit cargo cargo-watch cargo-criterion cargo-audit cargo-semver-checks xdg-open; + } + // { + # Add project-specific commands + "db:migrate" = cmd "Run database migrations" "${cargo} run --bin migrate"; + } + ); +in + # ... +``` + +### Module Parameters + +Each module takes a configuration attrset. Optional tools enable additional commands: + +| Module | Required | Optional | +|---------------|:----------------------|:-------------------------------------------------------------------------------------------------| +| `rust.test` | `cargo` | `cargo-watch` → `rust:test:watch` | +| `rust.bench` | `cargo` | `cargo-criterion` → `rust:bench:criterion`, `xdg-open` → `rust:bench:open` | +| `rust.lint` | `cargo` | — | +| `rust.fmt` | `cargo` | — | +| `rust.build` | `cargo` | — | +| `rust.doc` | `cargo` | — | +| `rust.watch` | `cargo-watch` | — | +| `rust.audit` | `cargo-audit` | — | +| `rust.semver` | `cargo-semver-checks` | — | +| `rust.msrv` | `cargo` | `cargo-msrv` → `rust:msrv:find` | +| `rust.ci` | `cargo` | — | +| `rust.all` | `cargo` | `cargo-watch`, `cargo-criterion`, `cargo-audit`, `cargo-semver-checks`, `cargo-msrv`, `xdg-open` | + +## pnpm Commands + +Pre-built command sets for JavaScript/TypeScript development with pnpm: + +| Module | Commands | +|------------------|:-----------------------------------------------------| +| `pnpm.install` | `pnpm:install`, `pnpm:install:frozen` | +| `pnpm.build` | `pnpm:build`, `pnpm:build:watch` | +| `pnpm.dev` | `pnpm:dev` | +| `pnpm.test` | `pnpm:test`, `pnpm:test:watch`, `pnpm:test:coverage` | +| `pnpm.lint` | `pnpm:lint`, `pnpm:lint:fix` | +| `pnpm.fmt` | `pnpm:fmt`, `pnpm:fmt:check` | +| `pnpm.typecheck` | `pnpm:typecheck`, `pnpm:typecheck:watch` | +| `pnpm.ci` | `pnpm:ci` (install, lint, typecheck, test, build) | +| `pnpm.all` | All of the above | + +### Importing Specific Modules + +``` nix +let + pnpm' = "${pkgs.pnpm}/bin/pnpm"; + + pnpm = command-utils.pnpm.${system}; + + command_menu = command-utils.commands.${system} ( + pnpm.install { pnpm = pnpm'; } + // pnpm.test { pnpm = pnpm'; } + // pnpm.lint { pnpm = pnpm'; } + ); +in + # ... +``` + +### Importing All pnpm Commands + +``` nix +let + pnpm' = "${pkgs.pnpm}/bin/pnpm"; + + pnpm = command-utils.pnpm.${system}; + + command_menu = command-utils.commands.${system} ( + pnpm.all { pnpm = pnpm'; } + // { + # Add project-specific commands + storybook = cmd "Start Storybook" "${pnpm'} storybook"; + } + ); +in + # ... +``` + +### Module Parameters + +All pnpm modules require only the `pnpm` executable path. + +## Wasm Commands + +Pre-built command sets for wasm-pack and wasm-bindgen development: + +| Module | Commands | +|----------------|:------------------------------------------------------------------------------| +| `wasm.build` | `wasm:build:web`, `wasm:build:nodejs`, `wasm:build:bundler` | +| `wasm.release` | `wasm:release:web`, `wasm:release:nodejs`, `wasm:release:bundler` | +| `wasm.test` | `wasm:test:node`, `wasm:test:chrome`, `wasm:test:firefox`, `wasm:test:safari` | +| `wasm.doc` | `wasm:doc`, `wasm:doc:open` | +| `wasm.watch` | `wasm:watch:build`, `wasm:watch:test` | +| `wasm.ci` | `wasm:ci` (test node + chrome) | +| `wasm.all` | All of the above | + +### Importing Wasm Commands + +``` nix +let + wasm-pack = "${pkgs.wasm-pack}/bin/wasm-pack"; + cargo = "${rust-toolchain}/bin/cargo"; + cargo-watch = "${pkgs.cargo-watch}/bin/cargo-watch"; + gzip = "${pkgs.gzip}/bin/gzip"; + + wasm = command-utils.wasm.${system}; + + command_menu = command-utils.commands.${system} ( + wasm.build { inherit wasm-pack; } + // wasm.release { inherit wasm-pack gzip; } + // wasm.test { inherit wasm-pack; features = "browser_test"; } + ); +in + # ... +``` + +### Monorepo Support + +For projects where Wasm lives in a subdirectory, use the `path` parameter: + +``` nix +wasm.build { inherit wasm-pack; path = "./my_wasm_crate"; } +wasm.release { inherit wasm-pack gzip; path = "./my_wasm_crate"; } +wasm.test { inherit wasm-pack; path = "./my_wasm_crate"; } +``` + +### Module Parameters + +| Module | Required | Optional | +|----------------|:---------------------|:------------------------------------------------------| +| `wasm.build` | `wasm-pack` | `path` (default: `.`) | +| `wasm.release` | `wasm-pack` | `path`, `gzip` → compress output | +| `wasm.test` | `wasm-pack` | `path`, `features` → `--features` flag | +| `wasm.doc` | `cargo` | `xdg-open` → `wasm:doc:open` | +| `wasm.watch` | `cargo-watch` | — | +| `wasm.ci` | `wasm-pack` | `path`, `features` | +| `wasm.all` | `wasm-pack`, `cargo` | `path`, `gzip`, `features`, `cargo-watch`, `xdg-open` | + +## Project Structure + +``` +nix-command-utils/ +├── flake.nix # Main entry point +├── lib/ +│ ├── core.nix # cmd, withHelp, command, commands +│ ├── rust.nix # Rust command modules +│ ├── pnpm.nix # pnpm command modules +│ └── wasm.nix # Wasm command modules +├── README.md +└── LICENSE +``` diff --git a/flake.lock b/flake.lock index 7f8fa71..18d75d4 100644 --- a/flake.lock +++ b/flake.lock @@ -19,16 +19,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1709569716, - "narHash": "sha256-iOR44RU4jQ+YPGrn+uQeYAp7Xo7Z/+gT+wXJoGxxLTY=", + "lastModified": 1769089682, + "narHash": "sha256-9yA/LIuAVQq0lXelrZPjLuLVuZdm03p8tfmHhnDIkms=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "617579a787259b9a6419492eaac670a5f7663917", + "rev": "078d69f03934859a181e81ba987c2bb033eebfc5", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-23.11", + "ref": "nixos-25.11", "type": "indirect" } }, diff --git a/flake.nix b/flake.nix index 1f8c078..2da8367 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "nix-command-utils"; inputs = { - nixpkgs.url = "nixpkgs/nixos-23.11"; + nixpkgs.url = "nixpkgs/nixos-25.11"; }; outputs = { self, nixpkgs, flake-utils }: @@ -10,132 +10,13 @@ let pkgs = import nixpkgs { inherit system; }; - cmd = description: script: { inherit description script; help = null; }; + core = import ./lib/core.nix { inherit pkgs; }; + inherit (core) cmd withHelp command commands; - withHelp = help: def: def // { inherit help; }; - - command = { name, script, description ? "", help ? null }: - let - helpHandler = - if help != null then '' - if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then - echo "${name} - ${description}" - echo "" - echo "${help}" - exit 0 - fi - '' else ""; - in - { - inherit name description; - - package = - pkgs.writeScriptBin name '' - #!${pkgs.bash}/bin/bash - ${helpHandler} - echo "⚙️ Running ${name}..." - ${script} - ''; - }; - - commands = defs: - let - names = - builtins.attrNames defs; - - helper = - let - # Calculate max widths - cmdHeader = "Command"; - descHeader = "Description"; - - nameLengths = map builtins.stringLength names; - descLengths = map (n: builtins.stringLength (builtins.getAttr n defs).description) names; - - maxNameLen = builtins.foldl' - (acc: x: if x > acc then x else acc) - (builtins.stringLength cmdHeader) - nameLengths; - - maxDescLen = builtins.foldl' - (acc: x: if x > acc then x else acc) - (builtins.stringLength descHeader) - descLengths; - - # Generate padding strings - repeat = char: len: - let - go = acc: - if builtins.stringLength acc >= len - then builtins.substring 0 len acc - else go (acc + char); - in - go ""; - - pad = str: width: - let - strLen = builtins.stringLength str; - padLen = width - strLen; - in - str + repeat " " padLen; - - # Table components (double line only for header separator) - hLineDouble = repeat "═"; - hLineSingle = repeat "─"; - - topBorder = "┌─${hLineSingle maxNameLen}─┬─${hLineSingle maxDescLen}─┐"; - headerSep = "╞═${hLineDouble maxNameLen}═╪═${hLineDouble maxDescLen}═╡"; - rowDivider = "├─${hLineSingle maxNameLen}─┼─${hLineSingle maxDescLen}─┤"; - bottomBorder = "└─${hLineSingle maxNameLen}─┴─${hLineSingle maxDescLen}─┘"; - headerRow = "│ ${pad cmdHeader maxNameLen} │ ${pad descHeader maxDescLen} │"; - - # Build data rows - mkRow = name: - let - desc = (builtins.getAttr name defs).description; - in - "│ ${pad name maxNameLen} │ ${pad desc maxDescLen} │"; - - dataRows = map mkRow names; - - # Interleave rows with dividers - interleave = rows: - if builtins.length rows <= 1 - then rows - else [ (builtins.head rows) rowDivider ] ++ interleave (builtins.tail rows); - - # Combine all lines - allLines = [ topBorder headerRow headerSep ] ++ interleave dataRows ++ [ bottomBorder ]; - echoLines = builtins.concatStringsSep " && " (map (l: "echo '${l}'") allLines); - - in - pkgs.writeScriptBin "menu" '' - #!${pkgs.stdenv.shell} - ${pkgs.figlet}/bin/figlet "Commands" | ${pkgs.lolcat}/bin/lolcat - echo "" - ${echoLines} - ''; - - mapper = name: - let - element = - builtins.getAttr name defs; - - task = command { - inherit name; - inherit (element) description script; - help = element.help or null; - }; - in - task.package; - - packages = - map mapper names; - - in - [ helper ] ++ packages; + rust = import ./lib/rust.nix { inherit cmd; }; + pnpm = import ./lib/pnpm.nix { inherit cmd; }; + wasm = import ./lib/wasm.nix { inherit cmd; }; in - { inherit cmd withHelp command commands; } + { inherit cmd withHelp command commands rust pnpm wasm; } ); } - diff --git a/lib/core.nix b/lib/core.nix new file mode 100644 index 0000000..a25aa96 --- /dev/null +++ b/lib/core.nix @@ -0,0 +1,130 @@ +{ pkgs }: + +let + cmd = description: script: { inherit description script; help = null; }; + + withHelp = help: def: def // { inherit help; }; + + command = { name, script, description ? "", help ? null }: + let + helpHandler = + if help != null then '' + if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then + echo "${name} - ${description}" + echo "" + echo "${help}" + exit 0 + fi + '' else ""; + in + { + inherit name description; + + package = + pkgs.writeScriptBin name '' + #!${pkgs.bash}/bin/bash + ${helpHandler} + echo "⚙️ Running ${name}..." + ${script} + ''; + }; + + commands = defs: + let + names = + builtins.attrNames defs; + + helper = + let + # Calculate max widths + cmdHeader = "Command"; + descHeader = "Description"; + + nameLengths = map builtins.stringLength names; + descLengths = map (n: builtins.stringLength (builtins.getAttr n defs).description) names; + + maxNameLen = builtins.foldl' + (acc: x: if x > acc then x else acc) + (builtins.stringLength cmdHeader) + nameLengths; + + maxDescLen = builtins.foldl' + (acc: x: if x > acc then x else acc) + (builtins.stringLength descHeader) + descLengths; + + # Generate padding strings + repeat = char: len: + let + go = acc: + if builtins.stringLength acc >= len + then builtins.substring 0 len acc + else go (acc + char); + in + go ""; + + pad = str: width: + let + strLen = builtins.stringLength str; + padLen = width - strLen; + in + str + repeat " " padLen; + + # Table components (double line only for header separator) + hLineDouble = repeat "═"; + hLineSingle = repeat "─"; + + topBorder = "┌─${hLineSingle maxNameLen}─┬─${hLineSingle maxDescLen}─┐"; + headerSep = "╞═${hLineDouble maxNameLen}═╪═${hLineDouble maxDescLen}═╡"; + rowDivider = "├─${hLineSingle maxNameLen}─┼─${hLineSingle maxDescLen}─┤"; + bottomBorder = "└─${hLineSingle maxNameLen}─┴─${hLineSingle maxDescLen}─┘"; + headerRow = "│ ${pad cmdHeader maxNameLen} │ ${pad descHeader maxDescLen} │"; + + # Build data rows + mkRow = name: + let + desc = (builtins.getAttr name defs).description; + in + "│ ${pad name maxNameLen} │ ${pad desc maxDescLen} │"; + + dataRows = map mkRow names; + + # Interleave rows with dividers + interleave = rows: + if builtins.length rows <= 1 + then rows + else [ (builtins.head rows) rowDivider ] ++ interleave (builtins.tail rows); + + # Combine all lines + allLines = [ topBorder headerRow headerSep ] ++ interleave dataRows ++ [ bottomBorder ]; + echoLines = builtins.concatStringsSep " && " (map (l: "echo '${l}'") allLines); + + in + pkgs.writeScriptBin "menu" '' + #!${pkgs.stdenv.shell} + ${pkgs.figlet}/bin/figlet "Commands" | ${pkgs.lolcat}/bin/lolcat + echo "" + ${echoLines} + ''; + + mapper = name: + let + element = + builtins.getAttr name defs; + + task = command { + inherit name; + inherit (element) description script; + help = element.help or null; + }; + in + task.package; + + packages = + map mapper names; + + in + [ helper ] ++ packages; + +in + { inherit cmd withHelp command commands; } diff --git a/lib/pnpm.nix b/lib/pnpm.nix new file mode 100644 index 0000000..81abbc2 --- /dev/null +++ b/lib/pnpm.nix @@ -0,0 +1,117 @@ +{ cmd }: + +rec { + # Install commands + install = { pnpm }: + { + "pnpm:install" = cmd "Install dependencies" '' + ${pnpm} install "$@" + ''; + + "pnpm:install:frozen" = cmd "Install with frozen lockfile" '' + ${pnpm} install --frozen-lockfile + ''; + }; + + # Build commands + build = { pnpm }: + { + "pnpm:build" = cmd "Build the project" '' + ${pnpm} build "$@" + ''; + + "pnpm:build:watch" = cmd "Build in watch mode" '' + ${pnpm} build --watch + ''; + }; + + # Development server + dev = { pnpm }: + { + "pnpm:dev" = cmd "Start development server" '' + ${pnpm} dev "$@" + ''; + }; + + # Test commands + test = { pnpm }: + { + "pnpm:test" = cmd "Run tests" '' + ${pnpm} test "$@" + ''; + + "pnpm:test:watch" = cmd "Run tests in watch mode" '' + ${pnpm} test --watch + ''; + + "pnpm:test:coverage" = cmd "Run tests with coverage" '' + ${pnpm} test --coverage "$@" + ''; + }; + + # Linting commands (eslint) + lint = { pnpm }: + { + "pnpm:lint" = cmd "Run ESLint" '' + ${pnpm} lint "$@" + ''; + + "pnpm:lint:fix" = cmd "Auto-fix ESLint issues" '' + ${pnpm} lint --fix "$@" + ''; + }; + + # Formatting commands (prettier) + fmt = { pnpm }: + { + "pnpm:fmt" = cmd "Format code with Prettier" '' + ${pnpm} format "$@" + ''; + + "pnpm:fmt:check" = cmd "Check code formatting" '' + ${pnpm} format --check "$@" + ''; + }; + + # TypeScript + typecheck = { pnpm }: + { + "pnpm:typecheck" = cmd "Run TypeScript type checking" '' + ${pnpm} typecheck "$@" + ''; + + "pnpm:typecheck:watch" = cmd "Type check in watch mode" '' + ${pnpm} typecheck --watch + ''; + }; + + # CI workflow + ci = { pnpm }: + { + "pnpm:ci" = cmd "Run full CI suite (install, lint, typecheck, test, build)" '' + set -e + echo "Installing dependencies..." + ${pnpm} install --frozen-lockfile + echo "Running linter..." + ${pnpm} lint + echo "Running type checker..." + ${pnpm} typecheck + echo "Running tests..." + ${pnpm} test + echo "Building..." + ${pnpm} build + echo "CI passed!" + ''; + }; + + # Convenience: all standard commands + all = { pnpm }: + install { inherit pnpm; } + // build { inherit pnpm; } + // dev { inherit pnpm; } + // test { inherit pnpm; } + // lint { inherit pnpm; } + // fmt { inherit pnpm; } + // typecheck { inherit pnpm; } + // ci { inherit pnpm; }; +} diff --git a/lib/rust.nix b/lib/rust.nix new file mode 100644 index 0000000..5d6da72 --- /dev/null +++ b/lib/rust.nix @@ -0,0 +1,181 @@ +{ cmd }: + +rec { + # Testing commands + test = { cargo, cargo-watch ? null }: + { + "rust:test" = cmd "Run all tests" '' + ${cargo} test "$@" + ''; + + "rust:test:doc" = cmd "Run documentation tests" '' + ${cargo} test --doc "$@" + ''; + } // (if cargo-watch != null then { + "rust:test:watch" = cmd "Run tests in watch mode" '' + ${cargo-watch} --clear -x "test $*" + ''; + } else {}); + + # Benchmarking commands + bench = { cargo, cargo-criterion ? null, xdg-open ? null }: + { + "rust:bench" = cmd "Run benchmarks" '' + ${cargo} bench "$@" + ''; + } // (if cargo-criterion != null then { + "rust:bench:criterion" = cmd "Run Criterion benchmarks" '' + ${cargo-criterion} "$@" + ''; + } else {}) + // (if xdg-open != null then { + "rust:bench:open" = cmd "Open benchmark report in browser" '' + ${xdg-open} ./target/criterion/report/index.html + ''; + } else {}); + + # Linting commands + lint = { cargo }: + { + "rust:clippy" = cmd "Run Clippy lints" '' + ${cargo} clippy --all-targets --all-features "$@" + ''; + + "rust:clippy:pedantic" = cmd "Run Clippy with pedantic lints" '' + ${cargo} clippy --all-targets --all-features -- -W clippy::pedantic -W clippy::nursery "$@" + ''; + + "rust:clippy:fix" = cmd "Auto-fix Clippy warnings" '' + ${cargo} clippy --fix --allow-dirty --allow-staged "$@" + ''; + }; + + # Formatting commands + fmt = { cargo }: + { + "rust:fmt" = cmd "Format code" '' + ${cargo} fmt --all "$@" + ''; + + "rust:fmt:check" = cmd "Check code formatting" '' + ${cargo} fmt --all -- --check + ''; + }; + + # Build commands + build = { cargo }: + { + "rust:build" = cmd "Build the project" '' + ${cargo} build "$@" + ''; + + "rust:build:release" = cmd "Build in release mode" '' + ${cargo} build --release "$@" + ''; + + "rust:check" = cmd "Check the project compiles" '' + ${cargo} check --all-targets --all-features "$@" + ''; + }; + + # Documentation commands + doc = { cargo }: + { + "rust:doc" = cmd "Build documentation" '' + ${cargo} doc --no-deps "$@" + ''; + + "rust:doc:open" = cmd "Build and open documentation" '' + ${cargo} doc --no-deps --open "$@" + ''; + + "rust:doc:check" = cmd "Check docs build without warnings" '' + RUSTDOCFLAGS="-D warnings" ${cargo} doc --no-deps --all-features "$@" + ''; + }; + + # Watch mode commands + watch = { cargo-watch }: + { + "rust:watch:build" = cmd "Rebuild on changes" '' + ${cargo-watch} --clear + ''; + + "rust:watch:clippy" = cmd "Run Clippy on changes" '' + ${cargo-watch} --clear -x clippy + ''; + + "rust:watch:test" = cmd "Run tests on changes" '' + ${cargo-watch} --clear -x test + ''; + }; + + # Security and compatibility commands + audit = { cargo-audit }: + { + "rust:audit" = cmd "Scan for security vulnerabilities" '' + ${cargo-audit} audit "$@" + ''; + }; + + semver = { cargo-semver-checks }: + { + "rust:semver" = cmd "Check API compatibility for libraries" '' + ${cargo-semver-checks} semver-checks check-release "$@" + ''; + }; + + msrv = { cargo, cargo-msrv ? null }: + { + "rust:msrv:verify" = cmd "Verify MSRV from Cargo.toml" '' + ${cargo} check --all-targets --all-features "$@" + ''; + } // (if cargo-msrv != null then { + "rust:msrv:find" = cmd "Find minimum supported Rust version" '' + ${cargo-msrv} find "$@" + ''; + } else {}); + + # CI workflow + ci = { cargo }: + { + "rust:ci" = cmd "Run full CI suite (fmt, clippy, test)" '' + set -e + echo "Checking formatting..." + ${cargo} fmt --all -- --check + echo "Running Clippy..." + ${cargo} clippy --all-targets --all-features -- -D warnings + echo "Running Clippy (pedantic)..." + ${cargo} clippy --all-targets --all-features -- -W clippy::pedantic -W clippy::nursery + echo "Running tests..." + ${cargo} test --all-features + echo "Checking docs..." + RUSTDOCFLAGS="-D warnings" ${cargo} doc --no-deps --all-features + echo "Building release..." + ${cargo} build --release + echo "CI passed!" + ''; + }; + + # Convenience: all standard commands + all = { + cargo, + cargo-watch ? null, + cargo-criterion ? null, + cargo-audit ? null, + cargo-semver-checks ? null, + cargo-msrv ? null, + xdg-open ? null + }: + test { inherit cargo cargo-watch; } + // bench { inherit cargo cargo-criterion xdg-open; } + // lint { inherit cargo; } + // fmt { inherit cargo; } + // build { inherit cargo; } + // doc { inherit cargo; } + // (if cargo-watch != null then watch { inherit cargo-watch; } else {}) + // (if cargo-audit != null then audit { inherit cargo-audit; } else {}) + // (if cargo-semver-checks != null then semver { inherit cargo-semver-checks; } else {}) + // msrv { inherit cargo cargo-msrv; } + // ci { inherit cargo; }; +} diff --git a/lib/wasm.nix b/lib/wasm.nix new file mode 100644 index 0000000..190dc76 --- /dev/null +++ b/lib/wasm.nix @@ -0,0 +1,107 @@ +{ cmd }: + +rec { + # Build commands (dev mode) + build = { wasm-pack, path ? "." }: + { + "wasm:build:web" = cmd "Build Wasm for web target" '' + ${wasm-pack} build ${path} --dev --target=web "$@" + ''; + + "wasm:build:nodejs" = cmd "Build Wasm for Node.js target" '' + ${wasm-pack} build ${path} --dev --target=nodejs "$@" + ''; + + "wasm:build:bundler" = cmd "Build Wasm for bundler target" '' + ${wasm-pack} build ${path} --dev --target=bundler "$@" + ''; + }; + + # Release commands + release = { wasm-pack, path ? ".", gzip ? null }: + let + compress = if gzip != null then " && ${gzip} -f ${path}/pkg/*_bg.wasm" else ""; + in { + "wasm:release:web" = cmd "Build Wasm release for web target" '' + ${wasm-pack} build ${path} --release --target=web "$@"${compress} + ''; + + "wasm:release:nodejs" = cmd "Build Wasm release for Node.js target" '' + ${wasm-pack} build ${path} --release --target=nodejs "$@"${compress} + ''; + + "wasm:release:bundler" = cmd "Build Wasm release for bundler target" '' + ${wasm-pack} build ${path} --release --target=bundler "$@"${compress} + ''; + }; + + # Test commands + test = { wasm-pack, path ? ".", features ? null }: + let + featuresFlag = if features != null then " --features='${features}'" else ""; + in { + "wasm:test:node" = cmd "Run wasm-pack tests in Node.js" '' + ${wasm-pack} test --node ${path}${featuresFlag} "$@" + ''; + + "wasm:test:chrome" = cmd "Run wasm-pack tests in headless Chrome" '' + ${wasm-pack} test --headless --chrome ${path}${featuresFlag} "$@" + ''; + + "wasm:test:firefox" = cmd "Run wasm-pack tests in headless Firefox" '' + ${wasm-pack} test --headless --firefox ${path}${featuresFlag} "$@" + ''; + + "wasm:test:safari" = cmd "Run wasm-pack tests in headless Safari" '' + ${wasm-pack} test --headless --safari ${path}${featuresFlag} "$@" + ''; + }; + + # Documentation commands + doc = { cargo, xdg-open ? null }: + { + "wasm:doc" = cmd "Build docs for wasm32-unknown-unknown" '' + ${cargo} doc --target=wasm32-unknown-unknown "$@" + ''; + } // (if xdg-open != null then { + "wasm:doc:open" = cmd "Build and open Wasm docs" '' + ${cargo} doc --target=wasm32-unknown-unknown --open "$@" + ''; + } else {}); + + # Watch commands + watch = { cargo-watch }: + { + "wasm:watch:build" = cmd "Rebuild Wasm on changes" '' + ${cargo-watch} --clear -- cargo build --target=wasm32-unknown-unknown + ''; + + "wasm:watch:test" = cmd "Run Wasm tests on changes" '' + ${cargo-watch} --clear -x "test --target=wasm32-unknown-unknown" + ''; + }; + + # CI workflow + ci = { wasm-pack, path ? ".", features ? null }: + let + featuresFlag = if features != null then " --features='${features}'" else ""; + in { + "wasm:ci" = cmd "Run Wasm CI suite (test node + chrome)" '' + set -e + echo "Running wasm-pack tests in Node.js..." + ${wasm-pack} test --node ${path}${featuresFlag} + echo "Running wasm-pack tests in Chrome..." + ${wasm-pack} test --headless --chrome ${path}${featuresFlag} + echo "Wasm CI passed!" + ''; + }; + + # Convenience: all wasm commands + all = { wasm-pack, cargo, path ? ".", gzip ? null, features ? null, cargo-watch ? null, xdg-open ? null }: + build { inherit wasm-pack path; } + // release { inherit wasm-pack path gzip; } + // test { inherit wasm-pack path features; } + // doc { inherit cargo xdg-open; } + // (if cargo-watch != null then watch { inherit cargo-watch; } else {}) + // ci { inherit wasm-pack path features; }; +}