SastQL est un linter d'analyse statique (SAST) pour fichiers SQL, ciblant PostgreSQL et SQLite. L'idée de départ : les migrations SQL sont du code, mais rien ne les lint comme du code. Les analyseurs génériques voient des chaînes de caractères ; les vraies erreurs (une colonne nullable dans un NOT IN, un LIMIT manquant, un DELETE sans WHERE) partent en production.
Le code : un AST, pas des regex
- Rust, une seule crate. Le parsing est délégué à sqlparser-rs : jamais de regex sur le SQL brut, jamais de parsing écrit à la main. - Une règle = un fichier = un test. Chaque règle implémente un trait (id, sévérité, description, check) et traverse les statements de l'AST. La logique dialecte-spécifique vit dans la règle, le parser reste agnostique. - Fail loudly. Erreur de parse : exit 2 avec le message. Violations : exit 1. Clean : exit 0. Un fichier n'est jamais sauté en silence. - Une limite assumée : pas encore de numéros de ligne précis, chaque violation est auto-portante (id, sévérité, message, extrait SQL fautif). Dire honnêtement "règle X, fichier Y" vaut mieux qu'inventer une position fausse.
L'hébergement : GitHub, protégé contre moi-même
Le repo est public, et main est protégée avec enforce_admins : même le propriétaire passe par une pull request, et une CI minimale mais stricte sert de porte (fmt, clippy en -D warnings, tests). La gouvernance est écrite : CONTRIBUTING, template de PR, CODEOWNERS, et une licence personnalisée source-available : pas d'usage commercial sans accord écrit explicite, tout arbitrage revient à l'auteur. Les PRs qui touchent la licence sont rejetées d'office.
Le CI : une porte, une chaîne de release
Deux workflows GitHub Actions. ci.yml : fmt + clippy + tests sur chaque PR. release.yml : déclenché par un tag v*, il compile six cibles nativement (Windows x64, Linux x64 glibc et musl, Linux arm64, macOS arm64 et x64), embarque chaque binaire dans un package npm de plateforme, puis publie le lanceur. La version de référence vit dans Cargo.toml : un tag = un build = 7 packages npm.
npm : le pattern esbuild
Le package principal ne contient pas de binaire. C'est un lanceur Node : il résout le package plateforme correspondant à os/cpu/libc et spawn le binaire. Six optionalDependencies, une par cible ; npm n'installe que la bonne. C'est exactement le pattern d'esbuild et de Biome.
Deux pièges rencontrés en chemin :
- Le filtre anti-typosquat de npm a rejeté le nom sastql (trop proche de fastq, un 403). Contournement : un scope, @clv.wp/sastql, la commande bin reste sastql. - Le nouveau pipeline de publication est asynchrone : le publish rend la main avant que la version soit visible ("being processed, a few minutes"). Vérifier le registre trop tôt produit de fausses alertes.
Du token d'accès au OIDC
Première release : EOTP partout. Les tokens classiques de npm ont disparu, et mon token granulaire exigeait un OTP qu'une CI ne peut pas fournir. Le contournement tenable : un token granulaire avec bypass 2FA... qui plafonne à 90 jours et cessera de publier vers janvier 2027. Pas soutenable.
La vraie solution : le trusted publishing (OIDC). Chaque package déclare sur npmjs.com son publisher de confiance (repo, workflow, environnement) ; le workflow demande id-token: write, npm vérifie le jeton OIDC émis par GitHub Actions, et plus aucun secret n'est impliqué. Bonus : --provenance attache à chaque package une attestation de build signée, traçable jusqu'au repo et au commit.
Ce que le projet m'a appris
La partie la plus longue n'a pas été les règles, mais la distribution. L'analyseur représente peut-être un tiers du travail ; les deux autres tiers, c'est tout ce qui le rend utilisable et digne de confiance : licence, gouvernance, CI, supply chain, pipeline de publication. Un linter n'est pas un script, c'est un produit.