{ buildToolbox , cabal-install , cachix , checkedShellScript , curl , devCabalOptions , entr , git , graphviz , hsie , nix , silver-searcher , style , tests , withTools , haskellPackages , ctags , openssl }: let watch = checkedShellScript { name = "postgrest-watch"; docs = '' Watch the project for changes and reinvoke the given command. Example: postgrest-watch postgrest-test-io ''; args = [ "ARG_POSITIONAL_SINGLE([command], [Command to run])" "ARG_LEFTOVERS([command arguments])" ]; positionalCompletion = "_command"; redirectTixFiles = false; # will be done by sub-command workingDir = "/"; } '' while true; do (! ${silver-searcher}/bin/ag -l . | ${entr}/bin/entr -dr "$_arg_command" "''${_arg_leftovers[@]}") done ''; pushCachix = checkedShellScript { name = "postgrest-push-cachix"; docs = '' Push all build artifacts to cachix. Requires authentication with `cachix authtoken ...`. ''; workingDir = "/"; } '' ${nix}/bin/nix-instantiate \ | xargs ${nix}/bin/nix-store -qR --include-outputs \ | ${cachix}/bin/cachix push postgrest ''; check = checkedShellScript { name = "postgrest-check"; docs = '' Run most checks that will also run on CI, but only against the latest PostgreSQL version. This currently excludes the memory and spec-idempotence tests, as those are particularly expensive. ''; workingDir = "/"; } '' ${tests}/bin/postgrest-test-spec ${tests}/bin/postgrest-test-doctests ${tests}/bin/postgrest-test-io ${tests}/bin/postgrest-test-big-schema ${tests}/bin/postgrest-test-replica ${style}/bin/postgrest-lint ${style}/bin/postgrest-style-check ''; gitHooks = let name = "postgrest-git-hooks"; in checkedShellScript { inherit name; docs = '' Enable or disable git pre-commit and pre-push hooks. Basic is faster and will only run: - pre-commit: postgrest-style - pre-push: postgrest-lint Full takes a lot more time and will run: - pre-commit: postgrest-style && postgrest-lint - pre-push: postgrest-check Changes made by postgrest-style will be staged automatically. Example usage: postgrest-git-hooks disable postgrest-git-hooks enable basic postgrest-git-hooks enable full The "run" operation and "--hook" argument are only used internally. ''; args = [ "ARG_POSITIONAL_SINGLE([operation], [Operation])" "ARG_TYPE_GROUP_SET([OPERATION], [OPERATION], [operation], [disable,enable,run])" "ARG_POSITIONAL_SINGLE([mode], [Mode], [basic])" "ARG_TYPE_GROUP_SET([MODE], [MODE], [mode], [basic,full])" "ARG_OPTIONAL_SINGLE([hook], , [Hook], [pre-commit])" "ARG_TYPE_GROUP_SET([HOOK], [HOOK], [hook], [pre-commit,pre-push])" ]; positionalCompletion = '' if test "$prev" == "${name}"; then COMPREPLY=( $(compgen -W "enable disable" -- "$cur") ) elif test "$prev" == "enable" || test "$prev" == "disable"; then COMPREPLY=( $(compgen -W "basic full" -- "$cur") ) fi ''; workingDir = "/"; } '' if [ run != "$_arg_operation" ]; then # Remove all hooks first and ignore failures because the file might be missing. # This assumes that we're only adding lines that include "postgrest-git-hooks" # to the hook file. sed -i -e '/postgrest-git-hooks/d' .git/hooks/pre-{commit,push} 2> /dev/null || true if [ disable != "$_arg_operation" ]; then # The nix-shell && + nix-shell || pattern makes sure we can run the hook # in a pure nix-shell, where nix-shell itself is not available, too. # The $(nix-shell --run "command -v ...") pattern ensures we only need to enable # the hooks once and still run the latest of our hook scripts, even when we # update them in the repo. echo 'command -v nix-shell > /dev/null || postgrest-git-hooks --hook=pre-commit run' "$_arg_mode" \ >> .git/hooks/pre-commit # shellcheck disable=SC2016 echo 'command -v nix-shell > /dev/null && $(nix-shell --quiet -Q --run "command -v postgrest-git-hooks") --hook=pre-commit run' "$_arg_mode" \ >> .git/hooks/pre-commit chmod +x .git/hooks/pre-commit echo 'command -v nix-shell > /dev/null || postgrest-git-hooks --hook=pre-push run' "$_arg_mode" \ >> .git/hooks/pre-push # shellcheck disable=SC2016 echo 'command -v nix-shell > /dev/null && $(nix-shell --quiet -Q --run "command -v postgrest-git-hooks") --hook=pre-push run' "$_arg_mode" \ >> .git/hooks/pre-push chmod +x .git/hooks/pre-push fi else # When run from a git hook, the GIT_ environment variables conflict with our withGit helper. # The following unsets all GIT_ variables. unset "''${!GIT_@}" # shellcheck disable=SC2317 function restore () { ref="$(git stash list --format=format:%gD --grep "$1" -n1)" # this will avoid merge conflicts when applying the stash ${git}/bin/git restore --source="$ref" . # restore untracked files, too. could fail with no files if [ "$(git show --numstat --format=oneline "$ref^3" | wc -l)" -gt 1 ]; then ${git}/bin/git restore --overlay --source="$ref^3" . fi ${git}/bin/git stash drop "$ref" } case "$_arg_mode" in basic) case "$_arg_hook" in pre-commit) # To be able to automatically add only changes from postgrest-style to the staging area, # we need to run postgrest-style twice. Otherwise we'd risk merge conflicts when popping # the stash afterwards. ${style}/bin/postgrest-style stash="postgrest-git-hooks-$RANDOM" ${git}/bin/git stash push --include-untracked --keep-index -m "$stash" if [ "$(git stash list --grep $stash)" ]; then # Only create the stash pop trap, if we actually created a stash. # Otherwise stash pop will cause havoc. trap 'restore "$stash"' EXIT fi ${style}/bin/postgrest-style ${git}/bin/git add . ;; pre-push) # Create a clean working tree without any uncomitted changes. ${withTools.withGit} HEAD ${style}/bin/postgrest-lint ;; esac ;; full) case "$_arg_hook" in pre-commit) # To be able to automatically add only changes from postgrest-style to the staging area, # we need to run postgrest-style twice. Otherwise we'd risk merge conflicts when popping # the stash afterwards. ${style}/bin/postgrest-style stash="postgrest-git-hooks-$RANDOM" ${git}/bin/git stash push --include-untracked --keep-index -m "$stash" if [ "$(git stash list --grep $stash)" ]; then # Only create the stash pop trap, if we actually created a stash. # Otherwise stash pop will cause havoc. trap 'restore "$stash"' EXIT fi ${style}/bin/postgrest-style ${git}/bin/git add . ${style}/bin/postgrest-lint ;; pre-push) # Create a clean working tree without any uncomitted changes. ${withTools.withGit} HEAD ${check} ;; esac ;; esac fi ''; dumpMinimalImports = checkedShellScript { name = "postgrest-dump-minimal-imports"; docs = "Dump minimal imports into given directory."; args = [ "ARG_POSITIONAL_SINGLE([dumpdir], [Output directory])" ]; workingDir = "/"; withTmpDir = true; } '' mkdir -p "$_arg_dumpdir" ${cabal-install}/bin/cabal v2-build ${devCabalOptions} \ --builddir="$tmpdir" \ --ghc-option=-ddump-minimal-imports \ --ghc-option=-dumpdir="$_arg_dumpdir" \ 1>&2 # Fix OverloadedRecordFields imports # shellcheck disable=SC2016 sed -E 's/\$sel:.*://g' -i "$_arg_dumpdir"/* ''; hsieMinimalImports = checkedShellScript { name = "postgrest-hsie-minimal-imports"; docs = "Run hsie with a provided dump of minimal imports."; args = [ "ARG_LEFTOVERS([hsie arguments])" ]; withTmpDir = true; } '' ${dumpMinimalImports} "$tmpdir" ${hsie} "$tmpdir" "''${_arg_leftovers[@]}" ''; hsieGraphModules = checkedShellScript { name = "postgrest-hsie-graph-modules"; docs = "Create a PNG graph of modules imported within the codebase."; args = [ "ARG_POSITIONAL_SINGLE([outfile], [Output filename])" ]; } '' ${hsie} graph-modules main src | ${graphviz}/bin/dot -Tpng -o "$_arg_outfile" ''; hsieGraphSymbols = checkedShellScript { name = "postgrest-hsie-graph-symbols"; docs = "Create a PNG graph of symbols imported within the codebase."; args = [ "ARG_POSITIONAL_SINGLE([outfile], [Output filename])" ]; } '' ${hsieMinimalImports} graph-symbols | ${graphviz}/bin/dot -Tpng -o "$_arg_outfile" ''; parallelCurl = checkedShellScript { name = "postgrest-parallel-curl"; docs = "wrapper for using parallel curl requests on the same "; args = [ "ARG_POSITIONAL_SINGLE([num], [number of parallel requests])" "ARG_POSITIONAL_SINGLE([host], [host])" "ARG_LEFTOVERS([extra arguments for curl])" ]; } '' curl_command="${curl}/bin/curl --parallel --parallel-immediate " curl_command+="''${_arg_leftovers[*]} " x=1 while [ $x -le "$1" ] do curl_command+="$_arg_host " x=$((x + 1)) done eval "$curl_command" ''; genCtags = checkedShellScript { name = "postgrest-gen-ctags"; docs = "Generate ctags for Haskell and Python code"; workingDir = "/"; } '' ${haskellPackages.haskdogs}/bin/haskdogs ${ctags}/bin/ctags -a -R --fields=+l --languages=python --python-kinds=-iv -f ./tags test/io/ ''; genJwt = checkedShellScript { name = "postgrest-gen-jwt"; docs = "Generate a JWT"; args = [ "ARG_POSITIONAL_SINGLE([role], [role for the jwt payload])" "ARG_OPTIONAL_SINGLE([secret],, [secret used to sign the JWT], [reallyreallyreallyreallyverysafe])" ]; } '' # From https://stackoverflow.com/questions/59002949/how-to-create-a-json-web-token-jwt-using-openssl-shell-commands # Construct the header jwt_header=$(echo -n '{"alg":"HS256","typ":"JWT"}' | base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//) # Construct the payload payload=$(echo -n "{\"role\":\"$_arg_role\"}" | base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$//) # Convert secret to hex hexsecret=$(echo -n "$_arg_secret" | xxd -p | paste -sd "") # Calculate hmac signature -- note option to pass in the key as hex bytes hmac_signature=$(echo -n "$jwt_header.$payload" | ${openssl}/bin/openssl dgst -sha256 -mac HMAC -macopt hexkey:"$hexsecret" -binary \ | base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//) # Create the full token jwt="$jwt_header.$payload.$hmac_signature" echo -n "$jwt" ''; genSecret = checkedShellScript { name = "postgrest-gen-secret"; docs = "Generate a JWT secret"; } '' export LC_CTYPE=C LC_ALL=C tr -dc 'A-Za-z0-9'