Elke service die ik schrijf heeft precies één ding nodig voordat hij iets anders doet: draait dit in prod, of zit een of andere arme sukkel (ik, om 2 uur ‘s nachts, met een halve fles op) er lokaal nog aan te klooien. Dat is de hele eis. Jarenlang leefde het antwoord als os.Getenv("ENV") en een if van twee regels, gekopieerd in elke main.go die ik ooit heb geschreven, elke keer nét anders gespeld. Dus deed ik wat elke redelijke engineer doet bij een probleem van twee regels: ik schreef er een heel pakket voor, gaf het een README met een FAQ-sectie, tagde elf releases en leverde het met volledige testdekking. Dit is goenv.
Het Probleem Dat Eigenlijk Geen Probleem Is
Er is er geen, en dat is de hele grap. os.Getenv("ENV") is zestien tekens. Het vergelijken met "dev" is er nog eens elf. Je zou de hele check inline kunnen schrijven in minder tijd dan je nodig had om deze zin te lezen. Alleen heb je hem dan inline, in elke service, elke keer nét anders gespeld, os.Getenv("ENVIRONMENT") hier, APP_ENV daar, iemand die in een derde repo op == "development" controleert in plaats van op "dev", en opeens is je “zit ik in prod?”-logica een mijnenveld dat verspreid ligt over elke service die je hebt, elk op zijn eigen bijzondere manier verkeerd bedraad. goenv bestaat zodat die vraag precies één implementatie heeft, op precies één plek gedefinieerd, overal geïmporteerd waar het uitmaakt.
De Hele Implementatie
Hier is de hele implementatie. Geen fragment, het complete bestand:
package goenv
import (
"os"
)
const EnvVarName = "ENV"
type Type = string
const (
Prod Type = "prod"
Dev Type = "dev"
)
func Get() Type {
e := os.Getenv(EnvVarName)
switch e {
case Dev:
return Dev
default:
return Prod
}
}
func IsProd() bool {
return Get() == Prod
}
func IsDev() bool {
return Get() == Dev
}Eén import. Niet eens fmt. Een switch met één echte case en een default die altijd op Prod landt. Zet ENV=dev en je krijgt Dev terug. Zet hem op wat dan ook anders, prod, staging, test, een lege string, een typefout, of helemaal niets, en je krijgt Prod. goenv vertrouwt er niet op dat jij het goed hebt gespeld, dus hij probeert het niet eens te raden. Als het niet expliciet dev is, is het prod. Geen middenweg, geen voordeel van de twijfel.
Het geëxporteerde oppervlak is precies drie functies: Get() geeft het huidige Type terug (“prod” of “dev”), en IsProd() / IsDev() zijn de twee booleaanse gemakjes die alleen maar Get() aanroepen en vergelijken. Er zitten drie constanten onder, EnvVarName (“ENV”), Prod en Dev, plus een Type-alias. En over die wil ik eerlijk zijn, want er staat type Type = string, met het isgelijkteken, wat er een echte alias van maakt en geen gedefinieerd type. Hij is in beide richtingen uitwisselbaar met string. Je haalt er exact nul compile-time veiligheid uit, niets houdt je tegen om "banana" door te geven waar een Type wordt verwacht. Het is documentatie met extra stappen, en doen alsof het anders is zou precies het soort ding zijn waar dit pakket de spot mee hoort te drijven. Dat is de hele API. Geen config-struct, geen functionele opties, geen interface om te implementeren, geen WithLogger(). Drie functies, en je kent ze allemaal al.
Het Gebruiken (Veel Valt Er Niet Te Gebruiken)
go get github.com/psyb0t/goenvpackage main
import (
"fmt"
"github.com/psyb0t/goenv"
)
func main() {
if goenv.IsProd() {
fmt.Println("don't fuck this up")
}
if goenv.IsDev() {
fmt.Println("break whatever you want")
}
}En aan de deploy-kant is het gewoon een omgevingsvariabele als alle andere:
export ENV=dev # you're developing, go break shit
export ENV=prod # you're in production, don't
export ENV= # also production, because paranoia is a featureDit is precies de truc die servicepack gebruikt voor zijn eigen omgevingsdetectie, hetzelfde pakket, dezelfde ENV-variabele, dezelfde standaard van “tenzij je expliciet dev zegt, zit je in prod”. Eén dependency, één gedrag, overal hergebruikt in plaats van per service opnieuw uitgevonden.
Wat Er Werkelijk in de Doos Zit
- Nul dependencies,
go.modis een moduleregel en een Go-versie, verder niets. De enige import in heelgoenv.goisos. - Echte testdekking, geen README-opschepperij, ik heb de broncode binnengehaald en zelf
go test -cover ./...gedraaid:100.0% of statements. Tien subtests verdeeld over drie testfuncties dekken elke tak, dev, prod, lege string, en een niet-herkende waarde (“staging”) die ook naar prod moet doorvallen, want de tests controleren dat de regel “alles wat niet expliciet dev is, is prod” echt standhoudt. - Valt terug op prod, aantoonbaar, het staat gewoon in de switch: het enige pad dat
Devteruggeeft is een exacte match opENV=dev. Ongezet, leeg, verkeerd gespeld, “staging”, “test”, wat dan ook, het lost allemaal op naarProd. Het pakket vertrouwt je niet en doet daar niet geheimzinnig over. - Elf getagde releases,
v1.0.0(“fuck yeah prod or dev”),v1.0.1(“peen”),v1.0.2, die een ClawHub agent-skill toevoegde, en nog acht die geen enkele regel bibliotheekcode hebben veranderd. Ja. Een pakket met drie functies dat één omgevingsvariabele leest levert nu een gedocumenteerde agent-skill, zodat je een AI kunt uitleggen hoe hijIsProd()aanroept. Het skill-bestand is langer dan de bibliotheek die het documenteert. Daarna kwamen de badges, de licentievermeldingen, een Codeberg-mirror, een opsplitsing van de CI-pipeline, Go 1.26, en een release waarvan de hele changelog is dat de oppervlaktesamenvatting in de READMETypevergeten was te noemen.goenv.goheeft precies één commit in zijn hele geschiedenis, de eerste, dus elke release na de eerste is verpakking, papierwerk of een typefoutcorrectie, wat tegelijk de grappigste en de meest correcte afloop is die er te krijgen valt. Alle elf echt, alle elf door dezelfde CI-pipeline geduwd als elk ander pakket dat ik uitlever:go vet,go test -race, en een dekkingsdrempel die in de Makefile op minimaal 90% staat, en sinds v1.0.2 ook een tag-gestuurde publish-job die de skill naar ClawHub stuurt zodra lint en tests groen zijn, een lat die dit pakket met tien volle punten haalt terwijl het vrijwel niets doet.
Kort Samengevat
Als je meer dan één bit informatie uit je omgeving nodig hebt, types, standaardwaarden, structs, het hele circus, lees dan over gonfiguration, dat is de volwassen versie van dit idee. goenv zit aan de andere kant van het spectrum: het beantwoordt één vraag, beantwoordt die elke keer hetzelfde, en doet niet alsof het meer is dan dat.
Pak het van GitHub, of schrijf os.Getenv("ENV") == "dev" zelf als een normaal mens. Allebei prima. Ik ben je baas niet.
Zo Zet Je Het In Je Agent
Ja, het pakket van drie functies heeft een plugin. Nee, daar ga ik verder niets over zeggen. Alles onder .agents/ staat in één marketplace gecatalogiseerd, dus het zijn twee commando’s:
claude plugin marketplace add psyb0t/agents
claude plugin install goenv@psyb0tCodex gebruikt dezelfde marketplace met een ander werkwoord, codex plugin add goenv@psyb0t, omdat codex plugin install niet bestaat. Hij vindt de skill ook uit zichzelf in een checkout van de repo, aangezien hij .agents/skills/ native scant zonder dat er iets geïnstalleerd is.