diff --git a/README.md b/README.md index d4f9b45..77329a4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,35 @@ # nix-jekyll-builder -build static websites using jekyll, on nix \ No newline at end of file +A Nix flake for building Jekyll sites with reproducible builds and minified output. + +## Features + +- Reproducible Jekyll site builds +- HTML minification included +- Bundler environment management +- Configurable gem dependencies +- Testing framework included + +## Usage + +### Basic Usage + +In your Jekyll site's `flake.nix`: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + nix-jekyll-builder.url = "git+https://git.chobble.com/chobble/nix-jekyll-builder"; + }; + + outputs = { self, nixpkgs, nix-jekyll-builder }: + let + system = "x86_64-linux"; # or your system + in { + packages.${system}.default = nix-jekyll-builder.lib.${system}.mkJekyllSite { + pname = "my-jekyll-site"; + src = ./.; + }; + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9b89076 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1720535198, + "narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b1356d6 --- /dev/null +++ b/flake.nix @@ -0,0 +1,79 @@ +{ + description = "Jekyll site builder flake"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + mkJekyllSite = { + pname, + version ? "1.0.0", + src, + gemset ? ./gemset.nix, + gemfile ? ./Gemfile, + lockfile ? ./Gemfile.lock + }: + let + env = pkgs.bundlerEnv { + name = pname; + inherit (pkgs) ruby; + inherit gemfile lockfile gemset; + }; + in + pkgs.stdenv.mkDerivation { + inherit pname version src; + + nativeBuildInputs = with pkgs; [ + ruby_3_3 + minify + ]; + + configurePhase = '' + export HOME=$TMPDIR + mkdir -p _site + ''; + + buildPhase = '' + echo "Building site with Jekyll..." + JEKYLL_ENV=production ${env}/bin/jekyll build --source . --destination _site --trace + + echo 'Minifying HTML' + minify --all --recursive --output . _site + ''; + + installPhase = '' + mkdir -p $out + cp -r _site/* $out/ + ''; + }; + + # Import tests + tests = import ./tests.nix { inherit pkgs mkJekyllSite; }; + + in + { + packages.default = mkJekyllSite { + pname = "my-jekyll-site"; + src = builtins.filterSource + (path: type: !(builtins.elem (baseNameOf path) [ + "_site" + ".jekyll-cache" + ".git" + "node_modules" + "result" + "vendor" + ])) + ./.; + }; + + lib.mkJekyllSite = mkJekyllSite; + + inherit (tests) checks apps; + }); +} diff --git a/test-files/Gemfile b/test-files/Gemfile new file mode 100644 index 0000000..dd993dd --- /dev/null +++ b/test-files/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' +gem 'jekyll', '~> 4.3.0' +gem 'sass-embedded', '~> 1.82.0' +gem 'google-protobuf', '~> 4.29.1' diff --git a/test-files/Gemfile.lock b/test-files/Gemfile.lock new file mode 100644 index 0000000..b2a0dcb --- /dev/null +++ b/test-files/Gemfile.lock @@ -0,0 +1,102 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + bigdecimal (3.1.8) + colorator (1.1.0) + concurrent-ruby (1.3.4) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.17.0) + forwardable-extended (2.6.0) + google-protobuf (4.29.1) + bigdecimal + rake (>= 13) + http_parser.rb (0.8.0) + i18n (1.14.6) + concurrent-ruby (~> 1.0) + jekyll (4.3.4) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-watch (2.2.1) + listen (~> 3.0) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (6.0.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.3.9) + rouge (4.5.1) + safe_yaml (1.0.5) + sass-embedded (1.82.0) + google-protobuf (~> 4.28) + rake (>= 13) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.6.0) + webrick (1.9.1) + +PLATFORMS + aarch64-linux + aarch64-linux-android + aarch64-linux-gnu + aarch64-linux-musl + aarch64-mingw-ucrt + arm-linux-androideabi + arm-linux-gnu + arm-linux-gnueabihf + arm-linux-musl + arm-linux-musleabihf + arm64-darwin + riscv64-linux-android + riscv64-linux-gnu + riscv64-linux-musl + ruby + x86-cygwin + x86-linux + x86-linux-android + x86-linux-gnu + x86-linux-musl + x86-mingw-ucrt + x86_64-cygwin + x86_64-darwin + x86_64-linux-android + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + google-protobuf (~> 4.29.1) + jekyll (~> 4.3.0) + sass-embedded (~> 1.82.0) + +BUNDLED WITH + 2.5.22 diff --git a/test-files/gemset.nix b/test-files/gemset.nix new file mode 100644 index 0000000..aba3063 --- /dev/null +++ b/test-files/gemset.nix @@ -0,0 +1,326 @@ +{ + addressable = { + dependencies = ["public_suffix"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0cl2qpvwiffym62z991ynks7imsm87qmgxf0yfsmlwzkgi9qcaa6"; + type = "gem"; + }; + version = "2.8.7"; + }; + bigdecimal = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1gi7zqgmqwi5lizggs1jhc3zlwaqayy9rx2ah80sxy24bbnng558"; + type = "gem"; + }; + version = "3.1.8"; + }; + colorator = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0f7wvpam948cglrciyqd798gdc6z3cfijciavd0dfixgaypmvy72"; + type = "gem"; + }; + version = "1.1.0"; + }; + concurrent-ruby = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0chwfdq2a6kbj6xz9l6zrdfnyghnh32si82la1dnpa5h75ir5anl"; + type = "gem"; + }; + version = "1.3.4"; + }; + em-websocket = { + dependencies = ["eventmachine" "http_parser.rb"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1a66b0kjk6jx7pai9gc7i27zd0a128gy73nmas98gjz6wjyr4spm"; + type = "gem"; + }; + version = "0.5.3"; + }; + eventmachine = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0wh9aqb0skz80fhfn66lbpr4f86ya2z5rx6gm5xlfhd05bj1ch4r"; + type = "gem"; + }; + version = "1.2.7"; + }; + ffi = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "07139870npj59jnl8vmk39ja3gdk3fb5z9vc0lf32y2h891hwqsi"; + type = "gem"; + }; + version = "1.17.0"; + }; + forwardable-extended = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "15zcqfxfvsnprwm8agia85x64vjzr2w0xn9vxfnxzgcv8s699v0v"; + type = "gem"; + }; + version = "2.6.0"; + }; + google-protobuf = { + dependencies = ["bigdecimal" "rake"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1zzlmsv7djpgn6hxp5r1jg7f4nx368j0ccq6pipq1ncplnvbffij"; + type = "gem"; + }; + version = "4.29.1"; + }; + "http_parser.rb" = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1gj4fmls0mf52dlr928gaq0c0cb0m3aqa9kaa6l0ikl2zbqk42as"; + type = "gem"; + }; + version = "0.8.0"; + }; + i18n = { + dependencies = ["concurrent-ruby"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0k31wcgnvcvd14snz0pfqj976zv6drfsnq6x8acz10fiyms9l8nw"; + type = "gem"; + }; + version = "1.14.6"; + }; + jekyll = { + dependencies = ["addressable" "colorator" "em-websocket" "i18n" "jekyll-sass-converter" "jekyll-watch" "kramdown" "kramdown-parser-gfm" "liquid" "mercenary" "pathutil" "rouge" "safe_yaml" "terminal-table" "webrick"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0124fnqizh7njn99qg4f3jvf9kg2rpm88drs9p9r5hqr50n2i264"; + type = "gem"; + }; + version = "4.3.4"; + }; + jekyll-sass-converter = { + dependencies = ["sass-embedded"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "00n9v19h0qgjijygfdkdh2gwpmdlz49nw1mqk6fnp43f317ngrz2"; + type = "gem"; + }; + version = "3.0.0"; + }; + jekyll-watch = { + dependencies = ["listen"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1qd7hy1kl87fl7l0frw5qbn22x7ayfzlv9a5ca1m59g0ym1ysi5w"; + type = "gem"; + }; + version = "2.2.1"; + }; + kramdown = { + dependencies = ["rexml"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "131nwypz8b4pq1hxs6gsz3k00i9b75y3cgpkq57vxknkv6mvdfw7"; + type = "gem"; + }; + version = "2.5.1"; + }; + kramdown-parser-gfm = { + dependencies = ["kramdown"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0a8pb3v951f4x7h968rqfsa19c8arz21zw1vaj42jza22rap8fgv"; + type = "gem"; + }; + version = "1.1.0"; + }; + liquid = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1czxv2i1gv3k7hxnrgfjb0z8khz74l4pmfwd70c7kr25l2qypksg"; + type = "gem"; + }; + version = "4.0.4"; + }; + listen = { + dependencies = ["rb-fsevent" "rb-inotify"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0rwwsmvq79qwzl6324yc53py02kbrcww35si720490z5w0j497nv"; + type = "gem"; + }; + version = "3.9.0"; + }; + mercenary = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0f2i827w4lmsizrxixsrv2ssa3gk1b7lmqh8brk8ijmdb551wnmj"; + type = "gem"; + }; + version = "0.4.0"; + }; + pathutil = { + dependencies = ["forwardable-extended"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "12fm93ljw9fbxmv2krki5k5wkvr7560qy8p4spvb9jiiaqv78fz4"; + type = "gem"; + }; + version = "0.16.2"; + }; + public_suffix = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0vqcw3iwby3yc6avs1vb3gfd0vcp2v7q310665dvxfswmcf4xm31"; + type = "gem"; + }; + version = "6.0.1"; + }; + rake = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "17850wcwkgi30p7yqh60960ypn7yibacjjha0av78zaxwvd3ijs6"; + type = "gem"; + }; + version = "13.2.1"; + }; + rb-fsevent = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1zmf31rnpm8553lqwibvv3kkx0v7majm1f341xbxc0bk5sbhp423"; + type = "gem"; + }; + version = "0.11.2"; + }; + rb-inotify = { + dependencies = ["ffi"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0vmy8xgahixcz6hzwy4zdcyn2y6d6ri8dqv5xccgzc1r292019x0"; + type = "gem"; + }; + version = "0.11.1"; + }; + rexml = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1j9p66pmfgxnzp76ksssyfyqqrg7281dyi3xyknl3wwraaw7a66p"; + type = "gem"; + }; + version = "3.3.9"; + }; + rouge = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1pchwrkr0994v7mh054lcp0na3bk3mj2sk0dc33bn6bhxrnirj1a"; + type = "gem"; + }; + version = "4.5.1"; + }; + safe_yaml = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0j7qv63p0vqcd838i2iy2f76c3dgwzkiz1d1xkg7n0pbnxj2vb56"; + type = "gem"; + }; + version = "1.0.5"; + }; + sass-embedded = { + dependencies = ["google-protobuf" "rake"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0hkwmw7vkdjs5hj8m7p6659aqmp3ms1an2611lf5ys13zv5qjf99"; + type = "gem"; + }; + version = "1.82.0"; + }; + terminal-table = { + dependencies = ["unicode-display_width"]; + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "14dfmfjppmng5hwj7c5ka6qdapawm3h6k9lhn8zj001ybypvclgr"; + type = "gem"; + }; + version = "3.0.2"; + }; + unicode-display_width = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0nkz7fadlrdbkf37m0x7sw8bnz8r355q3vwcfb9f9md6pds9h9qj"; + type = "gem"; + }; + version = "2.6.0"; + }; + webrick = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "12d9n8hll67j737ym2zw4v23cn4vxyfkb6vyv1rzpwv6y6a3qbdl"; + type = "gem"; + }; + version = "1.9.1"; + }; +} diff --git a/tests.nix b/tests.nix new file mode 100644 index 0000000..5aba287 --- /dev/null +++ b/tests.nix @@ -0,0 +1,52 @@ +{ pkgs, mkJekyllSite }: +let + mkTest = name: content: test: + let + testDir = pkgs.runCommand "test-env-${name}" {} '' + mkdir -p $out + cat > $out/index.html < $out/_config.yml <Test Page" (site: '' + test -f ${site}/index.html || (echo "Missing index.html" && exit 1) + grep -q "

Test Page

" ${site}/index.html + ''); + + minification = mkTest "minify" "

Test

" (site: '' + ! grep -q " " ${site}/index.html || (echo "Not minified" && exit 1) + ''); + }; +in +{ + checks = tests; + apps.test = { + type = "app"; + program = "${pkgs.writeScriptBin "run-tests" '' + #!${pkgs.bash}/bin/bash + set -euo pipefail + ${builtins.concatStringsSep "\n" + (map (name: "echo Testing ${name}... && test -e ${tests.${name}}") + (builtins.attrNames tests))} + echo "All tests passed!" + ''}/bin/run-tests"; + }; +}