Cada servicio que escribo necesita exactamente un dato antes de hacer nada más: ¿esto está corriendo en prod, o algún pobre desgraciado (yo, a las 2 de la mañana, con media botella encima) sigue toqueteándolo en local? Ese es el requisito entero. Durante años la respuesta vivió como un os.Getenv("ENV") y un if de dos líneas, copiado y pegado en cada main.go que he escrito en mi vida, escrito un poco distinto cada vez. Así que hice lo que hace cualquier ingeniero razonable ante un problema de dos líneas: escribí un paquete entero para él, le puse un README con sección de FAQ, le saqué once releases y lo entregué con cobertura de tests completa. Esto es goenv.
El Problema Que No Es Realmente Un Problema
No hay ninguno, y ese es el chiste entero. os.Getenv("ENV") son dieciséis caracteres. Compararlo con "dev" son once más. Podrías escribir la comprobación entera inline en menos tiempo del que has tardado en leer esta frase. Solo que entonces la tendrías inline, en cada servicio, escrita un poco distinta cada vez, os.Getenv("ENVIRONMENT") aquí, APP_ENV allá, alguien comprobando == "development" en vez de "dev" en un tercer repo, y de pronto tu lógica de “¿estoy en prod?” es un campo de minas desperdigado por todos los servicios que tienes, cada uno mal cableado a su manera particular. goenv existe para que esa pregunta tenga exactamente una implementación, definida exactamente en un sitio, importada allá donde importe.
La Implementación Entera
Aquí está la implementación entera. No un extracto, el fichero completo:
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
}Un solo import. Ni siquiera fmt. Un switch con un único caso real y un default que aterriza siempre en Prod. Pones ENV=dev y recibes Dev. Lo pones en cualquier otra cosa, prod, staging, test, una cadena vacía, una errata, o nada en absoluto, y recibes Prod. goenv no se fía de que lo hayas escrito bien, así que ni lo intenta adivinar. Si no es explícitamente dev, es prod. Sin término medio, sin presunción de inocencia.
La superficie exportada son exactamente tres funciones: Get() devuelve el Type actual (“prod” o “dev”), y IsProd() / IsDev() son las dos comodidades booleanas que se limitan a llamar a Get() y comparar. Tres constantes las respaldan, EnvVarName (“ENV”), Prod y Dev, más un alias Type. Y quiero ser honesto con ese, porque está escrito type Type = string, con el signo igual, lo que lo convierte en un alias de verdad, no en un tipo definido. Es intercambiable con string en ambas direcciones. No sacas absolutamente ninguna seguridad en tiempo de compilación de él, nada te impide pasar "banana" donde se espera un Type. Es documentación con pasos de más, y fingir lo contrario sería justo la clase de cosa de la que este paquete existe para reírse. Esa es toda la API. Sin struct de config, sin opciones funcionales, sin interfaz que implementar, sin WithLogger(). Tres funciones, y ya te las sabes todas.
Usarlo (No Hay Mucho Que Usar)
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")
}
}Y por el lado del despliegue, es solo una variable de entorno como cualquier otra:
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 featureEste es exactamente el truco que usa servicepack para su propia detección de entorno, mismo paquete, misma variable ENV, mismo valor por defecto de “si no dices explícitamente dev, estás en prod”. Una dependencia, un comportamiento, reutilizado en todas partes en lugar de reinventado servicio a servicio.
Qué Hay Realmente en la Caja
- Cero dependencias,
go.modes una línea de módulo y una versión de Go, nada más. El único import en todogoenv.goesos. - Cobertura de tests real, no una fanfarronada de README, me bajé la fuente y ejecuté
go test -cover ./...yo mismo:100.0% of statements. Diez subtests repartidos en tres funciones de test cubren cada rama, dev, prod, cadena vacía, y un valor no reconocido (“staging”) que también tiene que caer en prod, porque los tests comprueban que la regla de “todo lo que no sea explícitamente dev es prod” se cumple de verdad. - Cae en prod, y se puede verificar, está ahí escrito en el switch: el único camino que devuelve
Deves una coincidencia exacta conENV=dev. Sin definir, vacío, mal escrito, “staging”, “test”, lo que sea, todo se resuelve enProd. El paquete no se fía de ti y no le da vergüenza decirlo. - Once releases etiquetadas,
v1.0.0(“fuck yeah prod or dev”),v1.0.1(“peen”),v1.0.2, que añadió un skill de agente en ClawHub, y ocho más que no cambiaron ni una línea de la librería. Sí. Un paquete con tres funciones que lee una variable de entorno ahora entrega un skill de agente documentado, para que se le pueda explicar a una IA cómo llamar aIsProd(). El fichero del skill es más largo que la librería que documenta. Luego llegaron las insignias, los avisos de licencia, un mirror en Codeberg, una división del pipeline de CI, Go 1.26, y una release cuyo changelog entero es que el resumen de superficie del README se había dejado sin mencionarType.goenv.gotiene exactamente un commit en toda su historia, el inicial, así que cada release posterior a la primera es empaquetado, papeleo o una corrección de errata, lo cual es a la vez el desenlace más gracioso y el más correcto disponible. Las once reales, las once pasadas por el mismo pipeline de CI que cualquier otro paquete que publico:go vet,go test -race, y un umbral de cobertura puesto al 90% mínimo en el Makefile, y desde la v1.0.2 también un job de publicación atado al tag que manda el skill a ClawHub una vez pasan el lint y los tests, un listón que este paquete supera por diez puntos enteros sin hacer prácticamente nada.
En Resumen
Si necesitas más de un bit de información de tu entorno, tipos, valores por defecto, structs, el paquete completo, vete a leer sobre gonfiguration, esa es la versión adulta de esta idea. goenv está en el otro extremo del espectro: responde a una pregunta, la responde igual cada vez, y no pretende ser nada más que eso.
Cógelo de GitHub, o escribe os.Getenv("ENV") == "dev" tú mismo como una persona normal. Cualquiera de las dos vale. No soy tu jefe.
Cómo Instalarlo En Tu Agente
Sí, el paquete de tres funciones tiene plugin. No, no voy a hablar más del tema. Todo lo que hay bajo .agents/ está catalogado en un solo marketplace, así que son dos comandos:
claude plugin marketplace add psyb0t/agents
claude plugin install goenv@psyb0tCodex usa el mismo marketplace con un verbo distinto, codex plugin add goenv@psyb0t, porque no existe codex plugin install. También encuentra el skill por su cuenta en un checkout del repo, ya que escanea .agents/skills/ de forma nativa sin nada instalado en absoluto.