Skip to content

Instantly share code, notes, and snippets.

@absynce
Last active August 27, 2025 21:49
Show Gist options
  • Select an option

  • Save absynce/7bfcac7da4eb96fae6329cd4896a1ca4 to your computer and use it in GitHub Desktop.

Select an option

Save absynce/7bfcac7da4eb96fae6329cd4896a1ca4 to your computer and use it in GitHub Desktop.
Git pre-push hook elm-pages script
{
"type": "application",
"source-directories": [
"."
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"dillonkearns/elm-pages": "10.2.2",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0"
},
"indirect": {
"Chadtech/elm-bool-extra": "2.4.2",
"avh4/elm-color": "1.0.0",
"danfishgold/base64-bytes": "1.1.0",
"danyx23/elm-mimetype": "4.0.1",
"dillonkearns/elm-bcp47-language-tag": "2.0.0",
"dillonkearns/elm-cli-options-parser": "3.2.0",
"dillonkearns/elm-date-or-date-time": "2.0.0",
"dillonkearns/elm-form": "3.0.1",
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/http": "2.0.0",
"elm/json": "1.1.3",
"elm/parser": "1.1.0",
"elm/random": "1.0.0",
"elm/regex": "1.0.0",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.4",
"elm-community/basics-extra": "4.1.0",
"elm-community/list-extra": "8.7.0",
"elm-community/maybe-extra": "5.3.0",
"fredcy/elm-parseint": "2.0.1",
"jluckyiv/elm-utc-date-strings": "1.0.0",
"justinmimbs/date": "4.1.0",
"mdgriffith/elm-codegen": "5.2.0",
"miniBill/elm-codec": "2.3.0",
"miniBill/elm-unicode": "1.1.1",
"noahzgordon/elm-color-extra": "1.0.2",
"robinheghan/fnv1a": "1.0.0",
"robinheghan/murmur3": "1.0.0",
"rtfeldman/elm-css": "18.0.0",
"rtfeldman/elm-hex": "1.0.0",
"rtfeldman/elm-iso8601-date-strings": "1.1.4",
"stil4m/elm-syntax": "7.3.9",
"stil4m/structured-writer": "1.0.3",
"the-sett/elm-pretty-printer": "3.1.1",
"the-sett/elm-syntax-dsl": "6.0.3",
"wolfadex/elm-ansi": "3.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
module Main exposing (run)
import BackendTask exposing (BackendTask)
import BackendTask.File as File
import BackendTask.Stream as Stream
import FatalError
import Pages.Script as Script exposing (Script)
import Pages.Script.Spinner as Spinner
{-| Path to the Git pre-push hook file
-}
hookPath : String
hookPath =
".git/hooks/pre-push"
{-| The content for our Git pre-push hook
-}
hookBranchCheck : String
hookBranchCheck =
"""
# Warn before pushing to protected branches
# Bypass with git push --no-verify
BRANCH=`git rev-parse --abbrev-ref HEAD`
PROTECTED_BRANCHES="^(main|master|envs/*)"
if [[ "$BRANCH" =~ $PROTECTED_BRANCHES ]]; then
read -p "Are you sure you want to push to \\"$BRANCH\\" ? (y/n): " -n 1 -r < /dev/tty
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
exit 0
fi
echo "Push aborted."
exit 1
fi
exit 0
"""
run : Script
run =
Script.withoutCliOptions
(Spinner.steps
|> Spinner.withStep "Read existing hook file" (\_ -> readFile hookPath)
|> Spinner.withStep "Write pre-push hook" writeHookFile
|> Spinner.withStep "Make hook executable" (\_ -> makeExecutable hookPath)
|> Spinner.runSteps
)
{-| Write the Git pre-push hook file
-}
writeHookFile : Maybe String -> BackendTask FatalError.FatalError ()
writeHookFile maybeExistingHook =
case hookStatus maybeExistingHook of
HookAlreadyHasCheck ->
Script.log "Hook already has check"
HookExistsButNeedsCheck hookBody ->
writeFileContent (hookBody ++ "\n" ++ hookBranchCheck)
|> BackendTask.andThen
(\_ ->
Script.log "Updated existing pre-push hook"
)
HookDoesntExist ->
writeFileContent ("#!/bin/bash\n" ++ hookBranchCheck)
|> BackendTask.andThen
(\_ ->
Script.log "Created new pre-push hook"
)
writeFileContent : String -> BackendTask FatalError.FatalError ()
writeFileContent hookContent =
Script.writeFile
{ path = hookPath
, body = hookContent
}
|> BackendTask.mapError
(\error ->
FatalError.build
{ title = "Error writing hook file"
, body = "Failed to write pre-push hook"
}
)
type HookStatus
= HookAlreadyHasCheck
| HookExistsButNeedsCheck String
| HookDoesntExist
hookStatus : Maybe String -> HookStatus
hookStatus maybeExistingHook =
case maybeExistingHook of
Just hookBody ->
if String.contains "Are you sure you want to push" hookBody then
HookAlreadyHasCheck
else
HookExistsButNeedsCheck hookBody
Nothing ->
HookDoesntExist
{-| Read a file and handle errors gracefully.
Returns Nothing if the file doesn't exist.
-}
readFile : String -> BackendTask FatalError.FatalError (Maybe String)
readFile filePath =
File.rawFile filePath
-- |> Stream.read
|> BackendTask.map (\fileBody -> Just fileBody)
|> BackendTask.onError
(\error ->
case error.recoverable of
File.FileDoesntExist ->
Script.log ("Could not find " ++ filePath ++ "...creating...")
|> BackendTask.map (\_ -> Nothing)
File.FileReadError fileReadError ->
FatalError.build
{ title = "File read error"
, body = fileReadError
}
|> BackendTask.fail
File.DecodingError _ ->
FatalError.build
{ title = "Decoding error"
, body = "Failed to decode " ++ filePath
}
|> BackendTask.fail
)
makeExecutable : String -> BackendTask FatalError.FatalError ()
makeExecutable hookPath_ =
-- Make the hook file executable
Script.command "chmod"
[ "+x", hookPath_ ]
|> BackendTask.mapError
(\_ ->
FatalError.build
{ title = "Chmod error"
, body = "Failed to make pre-push hook executable"
}
)
|> BackendTask.map (\_ -> ())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment