chatdesk-ui/postgrest_v12.2.8/nix/tools/devTools.nix

395 lines
13 KiB
Nix

{ 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 <num> parallel curl requests on the same <host>";
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' </dev/urandom | head -c32
'';
in
buildToolbox
{
name = "postgrest-dev";
tools = {
inherit
check
dumpMinimalImports
gitHooks
hsieGraphModules
hsieGraphSymbols
hsieMinimalImports
parallelCurl
genCtags
genJwt
genSecret
pushCachix
watch;
};
}