crabster

Crabster et les autres

{% raw %} Ce que font les outils du même genre, ce qu'ils ont résolu que Crabster n'a pas, et ce qu'ils ont tranché autrement. Écrit pour arbitrer — pas pour vendre.

Sur quoi cette page s'appuie. Les affirmations sur JHipster ont été vérifiées sur sa documentation et ses tickets, listés en fin de page. Celles qui ne l'ont pas été sont marquées (non vérifié). JHipster évolue vite : revérifiez avant de vous appuyer sur un détail.

Celles sur Crabster ont été vérifiées contre le dépôt, en l'exécutant, le 16 septembre 2026. C'est daté parce que cette page a déjà eu tort dans le sens le plus bête : elle a annoncé pendant plusieurs jours trois manques que le code avait fermés entre-temps. Une page de comparaison vieillit dans les deux sens.

Et le biais est évident : cette page est écrite depuis Crabster. Ce qui suit essaie de compenser en étant plus dur avec Crabster qu'avec les autres, parce que c'est le seul des deux dont les défauts nous coûtent quelque chose.

Quatre familles, et une seule a le problème de fusion🔗

Le classement qui explique tout le reste : qu'advient-il du code généré ?

FamilleExemplesRégénérer, ça donne quoi
1. Générer et déposséderrails g scaffold, Spring Initializr, cargo newRien : il n'y a pas de régénération. Le code est à vous à la seconde où il est écrit
2. Générer une part qu'on ne touche jamaissqlc, Prisma Client, OpenAPI GeneratorIndolore, parce que la frontière est physique : le code généré est dans des fichiers que vous n'éditez pas
3. Ne rien générerHasura, PostgREST, SupabaseSans objet : l'API est dérivée du schéma à l'exécution
4. Générer un projet entier destiné à être éditéJHipster, CrabsterUn problème de fusion, et c'est le prix de la promesse

La famille 4 est la seule qui promette « une application complète et c'est votre code ». La fusion est ce que coûte cette promesse — pas un défaut d'implémentation qu'on pourrait corriger en travaillant mieux.

Les familles 2 et 3 méritent d'être regardées avant de choisir la 4. Si votre besoin est « une API CRUD sur un schéma », Hasura ou PostgREST vous la donnent sans une ligne de code, et le prix — vous ne possédez pas le moteur, la logique arbitraire n'y entre pas — n'est pas toujours le mauvais choix.

JHipster🔗

Dix ans d'existence, un écosystème considérable, du frontend, des microservices, des dizaines d'options de base et de framework. Crabster est un projet jeune d'un seul auteur. Toute comparaison qui oublierait cela serait malhonnête.

L'architecture est pourtant étonnamment proche — Crabster suit le même patron :

JHipsterCrabster
JDL (entity, relationship, enum, application)CDL (record, ref, enum, service)
.yo-rc.json + .jhipster/*.json.crabster/project.toml + model.cdl
Needles — marqueurs jhipster-needle-* où le générateur injecteSlots — _into/<fichier>/<slot>.tera
Blueprints (generator-jhipster-* sur npm)Blueprints (répertoires de modules)
jhipster upgradecrabster upgrade — même intention, autre mécanique (voir plus bas)

Les needles et les slots sont la même idée : un point d'injection nommé dans un fichier qu'un autre générateur possède. Les blueprints aussi, en plus mature chez eux — un blueprint JHipster peut remplacer un sous-générateur entier, pas seulement un template.

Ce que JHipster a résolu, et où en est Crabster🔗

Cette section listait quatre manques. Trois sont fermés depuis, le quatrième à moitié — et ce qui reste vraiment devant n'est aucun des quatre.

JHipsterCrabster
Objets liés dans les réponsesLes DTO MapStruct portent l'objet liéFermé. ?expand=customer,product remplace l'identifiant par l'enregistrement, avec une requête par référence pour toute la page et non une par ligne. Un niveau, pas une arborescence : ce qui est expandé porte l'identifiant du suivant
Filtres richesequals, notEquals, specified, in, contains, greaterThan, lessThan… générés en JPA CriteriaFermé, sauf in. Les mêmes opérateurs, choisis d'après le type de la colonne — contains sur du texte, les quatre comparaisons sur ce qui s'ordonne, specified sur ce qui est facultatif. Et un paramètre mal orthographié reçoit un 400 qui liste ceux qui existent, là où l'ignorer renverrait la table entière — ce qui ressemble à un résultat
Recherchesearch with elasticsearch : une intégration complèteFermé. search meilisearch donne un endpoint plein texte par enregistrement, sur les tables comme sur les documents : ce qui est indexé est le document que l'API renvoie, donc le module ne demande jamais où les enregistrements sont rangés
Colonnes d'auditAbstractAuditingEntity — createdBy, createdDate, lastModifiedBy, lastModifiedDate (non vérifié)À moitié. @audited donne createdAt et updatedAt, écrites par le code au moment de l'écriture. createdBy et lastModifiedBy demandent une identité, donc un lien entre le module d'authentification et celui des enregistrements que rien ne tisse aujourd'hui

Ce qui reste devant ne se rattrape pas en écrivant du code : dix ans, un écosystème et des gens. Des dizaines de blueprints publiés par d'autres que l'équipe du générateur, une base d'utilisateurs qui remonte les défauts avant qu'ils n'atteignent tout le monde, et une réponse à des besoins que ce dépôt n'a pas encore rencontrés — cache distribué, messagerie, envoi d'e-mails, i18n de l'application engendrée, écran d'administration des comptes (non vérifié). Crabster n'a rien de tout cela, et il n'a surtout encore aucun utilisateur.

Ce que ni l'un ni l'autre n'a résolu🔗

Ce que les deux ont tranché différemment🔗

JHipsterCrabster
Base de développement ≠ productiondevDatabaseType h2Disk / prodDatabaseType postgresqlRefusé. Une base par projet
Sécurité du CRUDFermé par défaut, on ouvre ensuiteOuvert par défaut, on ferme en nommant l'extracteur
FrontendGénéré (Angular, React, Vue), dans le cœurPas d'application UI dans le cœur, par décision. Un client typé — TypeScript et Rust — généré depuis le modèle ; les applications UI en blueprints, et il en existe trois — React, Vue, Angular — écrites contre ce client et livrées dans le binaire. La différence n'est pas « avec ou sans interface » mais qui porte le coût d'un framework UI qui casse (ADR-0003)

Le premier mérite un mot, parce que c'est le seul où je pense que Crabster a raison contre l'usage établi : le schéma n'est pas le même d'une base à l'autre. Un Timestamp est un DATETIME sur MySQL et un timestamptz ailleurs ; un Decimal ne fait pas l'aller-retour sur SQLite. Tester sur H2 et déployer sur PostgreSQL, c'est faire passer ses tests contre un schéma que la production n'a jamais — et les tickets JHipster sur « ça marche en H2, ça casse après » ne sont pas rares. Le besoin derrière l'option est réel ; Crabster y répond par des testcontainers contre la base réellement visée.

jhipster upgrade : leur réponse au problème de la famille 4🔗

Elle est astucieuse et brutale. La commande :

  1. crée ou récupère une branche orpheline jhipster_upgrade ;
  2. y régénère l'application entière (--force --with-entities) avec la nouvelle version du générateur, et commite ;
  3. fusionne cette branche dans la vôtre par un git merge.

Les conflits, c'est git qui les pose et vous qui les résolvez. Sur une application peu personnalisée c'est indolore ; sur une application où vous avez beaucoup écrit, c'est une session de résolution réputée pénible.

crabster upgrade : la même intention, une autre mécanique🔗

Crabster fait la même chose, avec une information que JHipster n'a pas : il sait quels fichiers vous avez touchés, parce qu'il les a empreints en les écrivant. La commande régénère depuis .crabster/, puis tranche fichier par fichier — réécrit ce qui est resté tel qu'il l'avait écrit, laisse intact ce que vous avez édité et dépose sa version dans .crabster/incoming/, supprime ce qu'il ne produit plus s'il est intact.

Sur un fichier édité, la fusion est une vraie fusion à 3 voies, comme la leur. La différence est l'ancêtre commun : JHipster le fait produire par git à partir d'une branche orpheline régénérée ; Crabster le garde lui-même, dans .crabster/snapshot/, une copie de chaque fichier tel qu'il l'a écrit.

JHipsterCrabster
Ce qui arbitregit mergeune fusion 3-voies sur la copie que l'outil a gardée
Ce qui distingue votre travailrien : git compare deux textesl'empreinte enregistrée — l'outil sait qui a écrit chaque fichier
Exige gitoui, et une branche orphelinenon
En cas de conflitdes marqueurs dans vos fichiersvos fichiers intacts, la fusion marquée déposée à côté
Ce que ça coûte au dépôtrien de pluschaque fichier généré est committé deux fois

Les deux dernières lignes sont le vrai arbitrage. Crabster refuse de planter des marqueurs dans un fichier qu'il n'a pas écrit — un projet qui n'est pas dans git n'aurait alors aucun moyen de revenir en arrière — et il paie cette indépendance vis-à-vis de git par une copie committée du code généré.

C'est ADR-0002 appliquée telle qu'elle était écrite. Elle écarte explicitement la mécanique de JHipster :

Alternative écartée : régénération complète avec git diff manuel laissé à l'utilisateur — simple à implémenter, mais reporte tout le coût sur l'utilisateur et ne tient pas à l'échelle d'un projet réel.

Le point d'histoire vaut d'être gardé : cette section disait, il y a peu, que JHipster avait une réponse imparfaite depuis des années et Crabster aucune. Le raisonnement qui repoussait crabster upgrade était faux — il supposait qu'un instantané était un prérequis de la commande, alors que l'empreinte suffisait à répondre à la seule question qui la bloquait. L'instantané est venu ensuite, et il n'apporte que la fusion.

Là où Crabster est devant🔗

Quatre choses, et il faut les dire aussi honnêtement que le reste.

Il sait ce que vous avez touché. Chaque fichier généré porte une empreinte. JHipster régénère tout et laisse git trancher — il ne sait pas distinguer votre travail du sien. Crabster refuse d'écraser et nomme le fichier, aussi bien sur record que sur upgrade, et c'est aussi ce qui lui permet de ne fusionner que là où il y a quelque chose à fusionner.

Le contrat d'erreur. RFC 9457 pour toute défaillance, avec un type que le projet sert lui-même, et un document OpenAPI généré depuis les handlers — donc incapable de dériver du code. C'est plus serré que la moyenne du genre.

Le verrouillage optimiste. JHipster ne génère pas de @Version : les tickets le demandant sont ouverts depuis 2015, et c'était jusqu'à peu un manque partagé — les deux outils laissaient deux écritures concurrentes s'écraser en silence. @versioned le ferme : ETag en lecture, If-Match obligatoire en écriture, 412 sur une version dépassée, 428 sur une écriture qui ne dit rien, et la vérification dans la même instruction SQL que l'écriture.

Pas de bibliothèque de support. Le projet généré ne dépend d'aucun runtime Crabster : ni io.github.jhipster, ni rien. Arrêter d'utiliser l'outil ne coûte rien.

Ce que j'en reprendrais🔗

Cette liste était celle des choses à rattraper. Elle est vide, et c'est en soi un résultat à dater : les quatre entrées sont faites.

  1. Les objets liés dans les réponses — fait, ?expand=.
  2. Les opérateurs de filtre — faits, sauf in, qui attend quelqu'un qui en ait besoin plutôt qu'une ligne de plus dans un tableau.
  3. Une réponse de secours à la montée de version, même laide — sans objet : la vraie fusion 3-voies est arrivée avant que la version laide ne soit écrite.
  4. Le verrouillage optimiste — fait, depuis @versioned. C'était un endroit où prendre l'avance plutôt qu'à rattraper.

Ce qui reste à leur prendre n'est donc pas une fonctionnalité, c'est une mécanique d'écosystème : un blueprint capable de remplacer un sous-générateur entier, et pas seulement un jeu de gabarits. Tant que personne n'écrit de blueprint hors de ce dépôt, la question reste théorique — et c'est exactement ce que mesure la phase 18.

Et ce que je ne reprendrais pas : devDatabaseType.

Sources🔗