From d142e98788c2476b7df0a5c1e621f3f5c65c130f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= <tm@tlater.net>
Date: Thu, 19 Jan 2023 07:59:58 +0000
Subject: [PATCH] flake.nix: Add update script

---
 flake.nix             | 29 +++++++++++++++++-------
 nix/scripts/update.sh | 51 +++++++++++++++++++++++++++++++++++++++++++
 nix/update.nix        | 14 ++++++++++++
 3 files changed, 86 insertions(+), 8 deletions(-)
 create mode 100644 nix/scripts/update.sh
 create mode 100644 nix/update.nix

diff --git a/flake.nix b/flake.nix
index 391c072..6f1fa64 100644
--- a/flake.nix
+++ b/flake.nix
@@ -27,14 +27,27 @@
       templates = flakeOutputs.packages.${system}.tlaternet;
     };
 
-    apps.${system}.default = let
-      inherit (self.packages.${system}) server templates;
-      inherit (nixpkgs.legacyPackages.${system}) writeShellScript;
-    in {
-      type = "app";
-      program = builtins.toString (writeShellScript "tlaternet-webserver" ''
-        ${server}/bin/tlaternet-webserver --template-directory ${templates}
-      '');
+    apps.${system} = {
+      default = let
+        inherit (self.packages.${system}) server templates;
+        inherit (nixpkgs.legacyPackages.${system}) writeShellScript;
+      in {
+        type = "app";
+        program = builtins.toString (writeShellScript "tlaternet-webserver" ''
+          ${server}/bin/tlaternet-webserver --template-directory ${templates}
+        '');
+      };
+
+      update = let
+        update-script = nixpkgs.legacyPackages.${system}.callPackage ./nix/update.nix {
+          cargo = flakeOutputs.rust-toolchain.cargo;
+          npm = nixpkgs.legacyPackages.${system}.nodePackages_latest.npm;
+          npm-check-updates = self.packages.${system}.templates.dependencies.npm-check-updates;
+        };
+      in {
+        type = "app";
+        program = "${update-script}/bin/update";
+      };
     };
 
     nixosModules.default = import ./nix/module.nix {inherit self system;};
diff --git a/nix/scripts/update.sh b/nix/scripts/update.sh
new file mode 100644
index 0000000..c5df1e1
--- /dev/null
+++ b/nix/scripts/update.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+set -eu
+
+# The backticks aren't supposed to be shell expansion
+# shellcheck disable=SC2016
+echo 'Note: Update the flake inputs with `nix flake update --commit-lockfile *first*`'
+
+# Make sure we're at the repo root
+cd "$(git rev-parse --show-toplevel)" || exit
+
+# Update cargo deps
+cd server || exit
+cargo update
+
+if ! git diff --quiet Cargo.lock Cargo.toml; then
+    git commit -m 'server: Update cargo dependencies' Cargo.lock Cargo.toml
+fi
+
+# Update template deps
+cd ../templates || exit
+
+tmpdir="$(mktemp -dt 'npm-updates.XXXXXXXXXX')"
+trap 'rm -r -- "$tmpdir"' EXIT
+
+# Ensure package.json is up to date so that npm-check-updates can read
+# it
+yq --output-format json package.yaml > package.json
+
+# Fetch package updates and prepare an update dict
+yq -P eval-all '{"dependencies": select(fi==0)} * {"devDependencies": select(fi==1)}' \
+   <(npm-check-updates --dep prod --jsonUpgraded) \
+   <(npm-check-updates --dep dev --jsonUpgraded) \
+   > "$tmpdir/updates.yaml"
+
+# Now apply these using yq
+yq -P ". *= load(\"$tmpdir/updates.yaml\")" package.yaml > "$tmpdir/package.yaml.updated"
+
+# yq's in-place replacement sadly doesn't persist newlines currently,
+# so we need to apply the changes in a roundabout way using diff.
+diff --ignore-blank-lines <(yq '.' package.yaml) "$tmpdir/package.yaml.updated" > "$tmpdir/update.patch" || true
+patch --no-backup-if-mismatch --merge --input="$tmpdir/update.patch" package.yaml
+
+# Update package.json again and get npm to update the package lock
+# file
+yq --output-format json package.yaml > package.json
+npm install --package-lock-only
+
+if ! git diff --quiet package.yaml package-lock.json; then
+    git commit -m 'templates: Update npm dependencies' package.yaml package-lock.json
+fi
diff --git a/nix/update.nix b/nix/update.nix
new file mode 100644
index 0000000..901097b
--- /dev/null
+++ b/nix/update.nix
@@ -0,0 +1,14 @@
+{
+  writeShellApplication,
+  cargo,
+  git,
+  nix,
+  npm,
+  npm-check-updates,
+  yq-go,
+}:
+writeShellApplication {
+  name = "update";
+  runtimeInputs = [cargo git nix npm npm-check-updates yq-go];
+  text = builtins.readFile ./scripts/update.sh;
+}