Skip to main content
← Tilbage til bloggen
VercelGit HooksCI/CDDeploymentpnpm

Deploy til Vercel fra en git pre-push-hook: org-repo, Hobby-plan, ingen CI-secrets

På Hobby-planen afviser Vercels Git-integration et privat repo, der ejes af en GitHub-organisation, og CI-vejen kræver repo-secrets, som du måske ikke må tilføje. Her er den lettere løsning: en lokal pre-push-hook, der deployer præcis den pushede commit med Vercel-CLI'en, mens alle fortsat pusher under eget navn.

Udgivet 6. oktober 20267 min læsning
Opdatering til Deploy til Vercel fra CI, når den der committer, ikke er teammedlem. Det indlæg deployer fra GitHub Actions og er det bedre valg, når du kan tilføje repo-secrets. Dette her er til, når du ikke kan — og det kræver slet ingen omskrivning af commits.

Repoet flyttede ind i en GitHub-organisation, Vercel-projektet ligger i et Hobby-team, og alle på projektet pusher under deres eget GitHub-navn. Jeg ville have, at hvert push deployer — main til produktion, alle andre grene til en preview — uden at ændre, hvem committene tilhører.

Min første plan var den indbyggede Git-integration. Den fejlede tre gange i træk, hver gang af en anden grund:

Error: Failed to link your-org/your-repo. You need to add a Login Connection
to your GitHub account first. (400)

This action must be performed by an organization owner

Error: The repository "your-repo" is private and owned by an organization,
which is not supported on the Hobby plan. Upgrade to Pro to continue. (409)

Tre mure mellem dig og Git-integrationen

  1. Ingen GitHub-loginforbindelse. Vercel-kontoen, der ejer teamet, havde aldrig forbundet GitHub, så linkningen af repoet fejler med den første fejl ovenfor. Løsningen er Account Settings → Authentication — men tænk dig om, før du klikker: en GitHub-konto kan kun være forbundet til én Vercel-konto ad gangen. Forbinder du din til servicekontoen, flyttes den væk fra din personlige Vercel, og Vercel blokerer derefter deploys af dine personlige projekter («Git author must have access to the project»). Hooken nedenfor kræver slet ingen GitHub-forbindelse, så gør det kun, hvis du går efter Git-integrationen.
  2. Installation af appen kræver en organisationsejer. Vercel GitHub App'en skal installeres på organisationen. Et almindeligt medlem kan kun anmode om det, og indstillingssiden svarer «This action must be performed by an organization owner».
  3. Hobby kan ikke forbinde private organisationsrepos. Med alt ovenstående på plads svarer Vercel stadig 409. Ingen indstilling løser den her: planen er grænsen.

De to første er tilladelser, du kan jagte. Den tredje betyder, at Git-integrationen er udelukket, indtil teamet er på Pro. Alt herefter er en workaround — så vælg en, der er ærlig om at være en workaround.

Deploy fra den maskine, der pusher

Vercel-CLI'en autentificerer med et login eller et token, ikke med commit-forfatteren, så den kører aldrig medlemskabstjekket, der blokerer integrationen. Mit forrige indlæg kører CLI'en i GitHub Actions. Det kræver VERCEL_TOKEN i repoets secrets, hvilket betyder admin-rettigheder på repoet — plus en --amend af forfatteren i runneren, så dashboardet viser en commit.

En pre-push-hook springer begge dele over. Den kører på udviklerens maskine efter git push, deployer den commit, der netop blev pushet, og rører aldrig committen: den rigtige forfatter bliver stående på GitHub og i dashboardet. Prisen er, at den findes pr. maskine — mere om det nedenfor.

Hooken

Gem den som .git/hooks/pre-push, og gør den eksekverbar. Git kalder den med remote-navnet og URL'en og sender den én linje pr. pushet ref på stdin.

.git/hooks/pre-push
#!/bin/bash
# Deploy to Vercel after a successful push. Local only: git never pushes .git/hooks.
# main -> production, any other branch -> preview. Runs detached; log: .git/vercel-deploy.log

case "$2" in
  *your-org/your-repo*) ;;
  *) exit 0 ;;
esac

repo_root="$(git rev-parse --show-toplevel)"
gh_path="${2#*github.com[:/]}"; gh_path="${gh_path%.git}"
gh_org="${gh_path%%/*}"; gh_repo="${gh_path#*/}"
log="$repo_root/.git/vercel-deploy.log"
push_pid=$PPID

[ -d "$repo_root/.vercel" ] || { echo "vercel-deploy: run 'vercel link' first, skipping deploy" >&2; exit 0; }

while read -r _ local_sha remote_ref _; do
  [ "$local_sha" = "0000000000000000000000000000000000000000" ] && continue
  branch="${remote_ref#refs/heads/}"
  if [ "$branch" = "main" ]; then flag="--prod"; else flag=""; fi

  (
    # wait for the push itself to finish, then deploy exactly the pushed commit
    while kill -0 "$push_pid" 2>/dev/null; do sleep 1; done
    # a rejected or failed push must not deploy: only continue if the commit reached the remote
    landed="$(git ls-remote "$2" "$remote_ref" 2>/dev/null | cut -f1)"
    if [ "$landed" != "$local_sha" ]; then
      echo "=== $(date '+%F %T') $branch @ ${local_sha:0:7} push did not land, deploy skipped" >> "$log"
      exit 0
    fi
    tmp="$(mktemp -d)"
    cd "$repo_root" || exit 1
    git worktree add --detach "$tmp" "$local_sha" >/dev/null 2>&1 || exit 1
    cp -R "$repo_root/.vercel" "$tmp/.vercel"
    cd "$tmp" || exit 1
    {
      echo "=== $(date '+%F %T') $branch @ ${local_sha:0:7} ${flag:-preview}"
      vercel deploy $flag --yes \
        --build-env ENABLE_EXPERIMENTAL_COREPACK=1 \
        -m "githubDeployment=1" \
        -m "githubOrg=$gh_org" -m "githubRepo=$gh_repo" \
        -m "githubCommitOrg=$gh_org" -m "githubCommitRepo=$gh_repo" \
        -m "githubCommitMessage=$(git -C "$repo_root" log -1 --format=%s "$local_sha")" \
        -m "githubCommitSha=$local_sha" \
        -m "githubCommitRef=$branch" \
        -m "githubCommitAuthorName=$(git -C "$repo_root" log -1 --format=%an "$local_sha")" \
        -m "githubCommitAuthorEmail=$(git -C "$repo_root" log -1 --format=%ae "$local_sha")" \
        2>&1 | tail -5
    } >> "$log"
    cd "$repo_root" && git worktree remove --force "$tmp"
  ) >/dev/null 2>&1 &
  disown
done

exit 0

Fire detaljer bærer det hele:

  • Match remote-URL'en, ikke navnet. Git sender URL'en som $2. Ved et push til en hvilken som helst anden remote afslutter hooken med det samme, så et mirror eller en fork deployer aldrig.
  • Vent på pushet, og tjek så, at det landede. Hooken kører før pushet er færdigt, og et afvist push kører den også. Derfor kobler den sig fri, venter på, at git push-processen er afsluttet, og deployer kun, hvis git ls-remote viser din SHA på remoten.
  • Deploy den pushede commit, ikke dit working tree. Et midlertidigt git worktree ved den pushede SHA sikrer, at ucommittede ændringer og halvfærdige filer aldrig går live. Det genbruger dit .vercel-link.
  • main er produktion, alt andet er en preview. Ét --prod-flag, valgt ud fra den pushede ref.

Få dashboardet til at vise committen, ikke en hash

Mine første hook-deploys dukkede op i dashboardet som en tilfældig streng som AowEm2XzF i stedet for commit-beskeden. Da jeg læste deploysene tilbage gennem API'en, kunne jeg se hvorfor: det deploy, der viste en besked, havde githubOrg, githubRepo og githubDeployment i sine metadata. CLI'en tilføjer dem kun, når den genkender en GitHub-remote fra et normalt checkout — fra et detached worktree fik jeg gitCommit*-nøgler og intet andet.

Løsningen er selv at sende dem med -m, udledt af remote-URL'en, som hooken ovenfor gør. Besked, gren og forfatter kommer så fra de nøgler. Det er det, jeg har observeret på mit eget projekt; Vercel dokumenterer ikke disse nøgler, så tjek dit dashboard efter det første deploy, og behandl dem som noget, der kan ændre sig.

Autentificering: brug et token, del ikke et login

Hooken kalder vercel deploy, så maskinen skal have legitimationsoplysninger til den konto, der ejer teamet. CLI'en læser selv VERCEL_TOKEN fra miljøet:

# A token created for this purpose; the CLI reads it automatically
export VERCEL_TOKEN="..."

Foretræk et token frem for vercel login. Et token kan tilbagekaldes for sig selv; et delt login kan ikke inddrages fra én person uden at ændre det for alle. Tokens er også det, Vercel dokumenterer til automatisering.

Del den med teamet

Commit scriptet og en installer på én kommando til repoet, så ingen kopierer filer i hånden:

pnpm add -g vercel
vercel link --project my-project --scope <team-slug>
bash scripts/vercel-deploy/install.sh   # copies the hook into .git/hooks

Selve hooken ligger i .git/hooks, som git aldrig pusher — derfor findes installeren. Én linje skal du ændre, før du genbruger scriptet: repo-URL-mønsteret i case øverst.

Sammenlignet med CI-vejen

Det her er ikke entydigt en bedre version af det forrige indlæg. Den bytter rækkevidde ud med enkelhed:

GitHub Actions (forrige indlæg)pre-push-hook (dette indlæg)
Kræver admin på repoet (secrets)JaNej
Deployer alles pushesJaKun fra maskiner med hooken
Omskriver forfatteren i runnerenJa (amend, aldrig pushet)Nej
OpsætningÉn gang pr. repoÉn gang pr. udvikler
Pushes fra GitHubs web-UI, bots, andre maskinerDeployetIkke deployet
Hvor fejl dukker opActions-loggenEn lokal logfil

Hvor grænsen går

En hook er en workaround, ikke en fribillet. Før du tager den i brug, skal du være ærlig over for dig selv om tre ting:

  • Hobby er til ikke-kommerciel brug. Hvis det her er en virksomheds produkt, er planen det egentlige problem, og hooken skjuler det bare. Løsningen er Pro; hooken køber tid, indtil nogen godkender fakturaen.
  • Del ikke et login for at undgå at betale for pladser. Én konto, der holder teamets deploy-token, er fint. At flere personer logger ind som den samme person for at få dashboard-adgang er deling af login.
  • Den deployer kun det, du pusher fra din maskine. Intet forhindrer en kollega uden hooken i at pushe og efterlade produktionen forældet — beslut, hvem der ejer deploys.

Pointer

  • Vercels Git-integration er udelukket for et privat organisationsrepo på Hobby: ingen tilladelse løser en begrænsning i planen.
  • CLI'en autentificerer et token, ikke commit-forfatteren, så medlemskabstjekket gælder ikke for den.
  • En pre-push-hook deployer fra udviklerens maskine, kræver ingen repo-secrets og omskriver aldrig en commit.
  • Vent på pushet, og bekræft med git ls-remote, at det landede, før du deployer.
  • Deploy et worktree af den pushede SHA, og send selv github*-metadataene med, så dashboardet viser committen.
  • Brug et token, ikke et delt login — og se Hobby som en nødløsning, hvis projektet er kommercielt.
  • Et GitHub-login kan kun være forbundet til én Vercel-konto ad gangen — flyt ikke dit til servicekontoen; hooken har ikke brug for det.

CI-vejen er stadig det bedre standardvalg, når du kan tilføje secrets. Hooken er til den dag, du ikke kan: et par dusin linjer shell, sat op én gang pr. udvikler, og hver commit bærer stadig sin rigtige forfatter.

Fandt du en fejl?

Et forkert faktum, en skæv oversættelse, noget der virker usandt i denne artikel? Skriv til mig — på dit eget sprog.