3 små YAML-filer der gør dit JavaScript-projekt meget mindre kaotisk
“Det virkede jo lige før” – og hvorfor CI sparer dig for hovedpine
Første gang jeg ødelagde et ellers fint lille hobbyprojekt, var det ikke på grund af en stor refactor.
Det var én linje. Jeg fik lige “optimeret” lidt i en utils-fil sent om aftenen. Alt virkede lokalt. Næste dag puller en ven koden, kører npm test, og halvdelen af tingene vælter.
Vi havde forskellig Node-version. Forskellige dependencies. Og ingen af os gad faktisk køre tests hver gang, vi lavede en lille ændring. Klassikeren: “det virker på min maskine”.
Det er præcis her, GitHub Actions og CI (Continuous Integration) begynder at give mening. Ikke som enterprise-opsætning, men som en lille vagtpost, der automatisk:
- installerer dine dependencies fra bunden
- kører lint og format-check
- kører tests på hver
pushogpull_request
Og siger stop, før du deployer noget, der er i stykker.
I den her tekst bygger jeg den CI, jeg selv ville ønske, jeg havde sat op meget tidligere: én simpel workflow-fil til et JavaScript/Node-projekt, som du kan copy/paste, forstå og selv udvide.
Først: Hvad GitHub Actions og CI egentlig gør for dig
GitHub Actions er GitHubs egen automatiseringsmotor. Du beskriver “arbejdsgange” i små YAML-filer. GitHub kører dem for dig, når noget sker i dit repo, fx når du pusher eller åbner et pull request.
CI betyder bare: hver gang du integrerer ny kode i dit repo, kører der et sæt checks automatisk. Typisk:
- byg projektet
- kør lint
- kør tests
Pointen er, at computeren er bedre end os til at huske at gøre det hver eneste gang.
Hvis du vil have lidt mere baggrund om workflows og deploy, kan du senere kigge på artiklen om SPA vs serverrouting og hosting, men vi holder os til CI her.
En minimal workflow der kører på push og pull request
Udgangspunktet: du har et Node/JavaScript-projekt med f.eks. Vite, Jest eller Vitest og ESLint.
Strukturen ligner noget i den her stil:
my-project/
package.json
package-lock.json eller pnpm-lock.yaml / yarn.lock
src/
test/ eller tests/
Nu laver vi en første CI-fil:
- Lav mappen
.github/workflowsi roden af dit repo. - Lav en fil, fx
ci.yml.
Læg det her i den:
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Check out kode
uses: actions/checkout@v4
- name: Brug Node 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Installer dependencies
run: npm ci
- name: Byg projekt (valgfrit)
run: npm run build --if-present
- name: Kør tests
run: npm test -- --runInBand
Det her er en “one-file CI starter”:
on: hvornår workflowet kører (her: påpushogpull_requestmodmain).jobs: et job er en virtuel maskine, der laver tingene.steps: sekvensen af ting, der sker: checkout, setup Node, install, build, test.
Når du committer og pusher den fil, får du en ny faneblad på GitHub der hedder Actions, hvor du kan se dit workflow køre.
Sæt Node-version og caching, så det ikke føles langsomt
Hvis du lader være med at sætte Node-version, får du måske en anden version end lokalt. Så kan du få de der dejlige “det virker hos mig”-forskelle.
I eksemplet ovenfor satte vi:
- name: Brug Node 20
uses: actions/setup-node@v4
with:
node-version: 20
Det betyder, at Actions-miljøet bruger Node 20 hver gang. Du kan også skrive node-version-file: .nvmrc, hvis du bruger sådan en.
Caching gør det hurtigere, ved at genbruge din node_modules-installation mellem runs. En simpel måde er at lade setup-node håndtere det:
- name: Brug Node 20 med cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
Så bruger den din package-lock.json som nøgle og slipper for at hente og installere alt fra nul hver gang.
Bruger du pnpm eller yarn, kan du ændre cache til pnpm eller yarn og justere din install-kommando.
Lint og format-check med ESLint og Prettier
Næste skridt er at lade CI’en være den irriterende ven, som konsekvent fanger dine småting.
Hvis du har scripts i din package.json i stil med:
{
"scripts": {
"lint": "eslint src",
"format:check": "prettier --check "src/**/*.{js,ts,jsx,tsx}""
}
}
så kan du udvide workflowet:
- name: Kør ESLint
run: npm run lint
- name: Tjek format med Prettier
run: npm run format:check
Hvis en af de to fejler, stopper workflowet. Det føles lidt hårdt første gang, men du får:
- ens stil i projektet
- fanget små logiske fejl tidligt
Hvis du endnu ikke har fået sat ESLint/Prettier op, så er det værd at gøre. Der ligger mange gode introer, f.eks. i samme stil som SQL-øvelserne i den her artikel om JOIN-øvelser: små skridt, én kommando ad gangen.
Tests i CI med Vitest eller Jest
Når lint og format spiller, er det næste trin at få dine tests ind.
Typisk har du noget a la:
{
"scripts": {
"test": "vitest",
"test:ci": "vitest run --coverage"
}
}
Eller for Jest:
{
"scripts": {
"test": "jest",
"test:ci": "jest --runInBand"
}
}
Personligt kan jeg godt lide at have et separat test:ci-script, så jeg kan tweake det uden at ændre min lokale udviklingskommando.
Så ændrer vi workflowet:
- name: Kør tests
run: npm run test:ci
Hvis du kører Vitest i et Vite-projekt, kan du også bruge officiel setup-action eller bare køre det direkte, som her. Det vigtigste er, at npm run test:ci fejler med exit code 1, når noget er galt. Resten er bare støj.
Gate deploy – idéen om “kun grøn kode må komme videre”
Forestil dig, at du har sat automatisk deploy op til Netlify, Vercel eller GitHub Pages. Det er ret nemt, og der er en artikel om deploy-fælder på Coding Class, hvis du vil gå i den retning.
Konceptet er simpelt:
- Ét workflow kører lint, build og tests.
- KUN hvis det workflow lykkes, må deploy ske.
Der er to klassiske måder at gøre det på i GitHub Actions:
- Ét workflow med to jobs, hvor nr. 2 afhænger af nr. 1.
- To workflows, hvor det ene trigges af et
workflow_runevent.
Vi tager den simple: to jobs i samme fil.
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
# ... checkout, setup node, install, lint, test ...
deploy:
needs: build-and-test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Check out kode
uses: actions/checkout@v4
- name: Byg og deploy
run: |
npm ci
npm run build
# kald dit deploy-script her
needs: build-and-test betyder: deploy-jobbet starter kun, hvis build-and-test lykkes. Hvis lint eller tests fejler, sker der intet deploy. Det er præcis den “grøn port”, du vil have.
Secrets og miljøvariabler uden at skyde dig selv i foden
Før eller siden skal din CI bruge et token, en API-key eller et password. Det er her, mange første gang laver den helt klassiske fejl: committe hemmeligheder i repoet.
Det er en dårlig idé. Hvis du vil have et lidt længere rant om det, har jeg skrevet om hemmeligheder i kode i artiklen du skal holde op med at hardcode hemmeligheder.
Den korte version til GitHub Actions:
- Læg aldrig tokens i kode eller YAML.
- Brug GitHub Secrets til alt, der skal holdes skjult.
- Giv hvert token mindst mulige rettigheder (least privilege).
Sådan bruger du et secret i et workflow:
- Gå ind i dit repo på GitHub.
- Settings → Secrets and variables → Actions.
- Opret et nyt secret, fx
DEPLOY_TOKEN.
I din CI-fil kan du så gøre:
deploy:
needs: build-and-test
runs-on: ubuntu-latest
steps:
- name: Check out kode
uses: actions/checkout@v4
- name: Deploy med token
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
echo "Bruger token: $DEPLOY_TOKEN"
# Her ville du kalde dit rigtige deploy-script
Når workflowet kører, er DEPLOY_TOKEN sat som environment variable i det step. GitHub forsøger også at maskere værdien i logs, så den ikke bliver vist i klartekst.
Husk at rotere (genoprette) tokens, hvis du er i tvivl om, om noget er lækket. Og ja, vi har alle prøvet at lægge noget i et public repo, som ikke skulle have været der.
Typiske fejl og hvordan jeg selv leder efter dem i Actions-loggen
Nu til den del, hvor ting går galt. For det gør de. Det gode er, at Actions-loggen ofte fortæller dig præcis, hvad der er i vejen, hvis du gider læse den.
1. Permissions og “Resource not accessible by integration”
Hvis du prøver at pushe tilbage til repoet, oprette en release eller lignende fra Actions, kan du få fejl om tokens eller rettigheder.
Når du ser noget i stil med Resource not accessible by integration i loggen, så kig på:
- hvilken action du bruger (kræver den ekstra scopes?)
- om du har sat
permissions:i workflowet
Eksempel:
permissions:
contents: write
øverst i din YAML kan være nødvendigt, hvis du vil lave commits eller tags fra Actions.
2. Lockfiles og mismatch mellem lokalt og CI
En anden klassiker: du bruger npm install lokalt, men Actions kører npm ci, og pludselig fejler noget på CI.
Min tommelfingerregel:
- Brug det samme package manager-kommando lokalt og i CI (
npm ciellerpnpm install --frozen-lockfile). - Commit altid din lockfile (
package-lock.json,pnpm-lock.yamlosv.).
Hvis dependencies fejler i Actions, kigger jeg først efter:
- er der en fejl i
npm ci-steppet? - er Node-versionen den samme som lokalt?
- har jeg husket at committe lockfile efter dependency-opdateringer?
3. Flaky builds og tests der fejler “nogle gange”
Den værste kategori: ting, der fejler uden at være deterministiske. Flaky tests. Tidsafhængig logik. Netværkskald.
Her plejer jeg at gøre tre ting:
- Kør samme test lokalt med det samme kommando som CI (
npm run test:ci). - Slå evt.
--runInBandtil i CI, så tests kører sekventielt. - Mock alt netværk og tid (f.eks. dato, random) i tests.
Hvis du kan få fejlen til at opstå lokalt med en deterministisk kommando, er du halvvejs. Så er resten bare almindelig debug, som i artiklen om at begynde at debugge rigtigt.
4. Hvor jeg klikker i Actions, når noget går galt
Jeg bruger næsten altid samme lille rutine, når et workflow fejler:
- Åbn fanen Actions i repoet.
- Klik på det seneste run med rødt kryds.
- Klik på det job, der er fejlet (fx
build-and-test). - Fold det step ud, der er markeret med rødt.
- Scroll helt ned i loggen. Den sidste fejl-linje er som regel den vigtigste.
Det lyder banalt, men det er ofte der, man opdager, at det ikke er “noget med Actions”, men bare en almindelig Node-fejl, en import, der mangler, eller en env-var, der ikke er sat.
Hvordan du kommer videre uden at drukne i YAML
Hvis du først har én fil, der kan:
- køre lint
- tjekke format
- køre tests
- eventuelt gate en deploy
så er du et helt andet sted end “jeg pusher bare og håber det bedste”.
Derfra kan du roligt begynde at:
- tilføje flere jobs (fx e2e-tests)
- køre på flere Node-versioner med matrix
- splitte workflows op i
ci.ymlogdeploy.yml
Men jeg vil faktisk anbefale, at du bliver i den lille, simple én-fil-opsætning et stykke tid. Lidt ligesom at holde sig til én surdej, før man begynder at eksperimentere med fuldkorn og koldhævning.
Min erfaring er, at en lille, stabil CI, der gør få ting godt, gør dit repo langt mere “seriøst” at arbejde i, også selvom det bare er et hobbyprojekt, du bygger om aftenen ved spisebordet, mens resten af huset laver noget helt andet.
Og ja, den siger nej til dig en gang imellem. Men det er typisk der, man faktisk lærer noget.









Send kommentar
Du skal være logget ind for at skrive en kommentar.