v0.9.7 — JHipster 9.0.0 upgrade + stability & DX release
Release Notes - v0.9.7
Overview
JHipster Rust Blueprint v0.9.7 is the largest release since v0.9.0. It upgrades the blueprint to JHipster 9.0.0 (from 8.11.0), which brings a significant number of upstream breaking changes — new sub-generator namespaces, a reorganised support module layout, a workspace-oriented client layout (client/ subfolder), the Angular 17+ Vite-based dev server, and renamed Angular component files. On top of the upgrade work, the release hardens entity generation across all four supported databases (SQLite, PostgreSQL, MySQL, MongoDB), fixes a long list of bugs that surfaced when generating sample projects with non-trivial relationships, restores the standard npm start / docker build / cargo build && cargo run workflows end-to-end, and lands 44 new regression tests so the same issues do not come back.
Every generator in the blueprint (rust-server, client, common, server, entity, docker, languages, cypress, ci-cd, kubernetes, kubernetes:helm, and the new base-application:bootstrap) was touched during the upgrade; every test snapshot was regenerated; and both existing and new sample projects (SQLite/Angular, MySQL/React, PostgreSQL/Angular, MongoDB/Angular) were regression-tested end to end — generation, compilation, migrations, server startup, and the Angular UI.
What's New in v0.9.7
Upgrade to JHipster 9.0.0
JHipster 9 is a major upstream release with a number of breaking changes that required updates across every generator in this blueprint. If you are maintaining a private fork of the blueprint or you built tooling against the v0.8.x / v0.9.x APIs, the items below are the ones you will care about.
Dependency and engine bumps
| Field | v0.9.6 | v0.9.7 |
|---|---|---|
generator-jhipster |
8.11.0 |
^9.0.0 |
engines.generator-jhipster |
8.11.0 |
9.0.0 |
engines.node |
^18.19.0 || >= 20.6.1 |
^22.18.0 || >= 24.11.0 |
keywords |
jhipster-8 |
jhipster-9 |
.yo-rc.json jhipsterVersion |
8.11.0 |
9.0.0 |
Node 18 and Node 20 are no longer supported — Node 22.18+ or Node 24.11+ is required. This matches the baseline JHipster 9 supports.
Sub-generator namespace restructuring
JHipster 9 renamed several sub-generators and introduced the <parent>:<child> namespace convention. The blueprint's sub-generator directories were reorganised to match:
| v0.9.6 | v0.9.7 |
|---|---|
generators/bootstrap-application/ |
generators/base-application/generators/bootstrap/ |
generators/kubernetes-helm/ |
generators/kubernetes/generators/helm/ |
dependsOnJHipster('bootstrap-application') |
dependsOnBootstrapApplication() (shorthand) or dependsOnBootstrap('server') |
generator-jhipster/generators/base/support |
generator-jhipster/generators/base-core/support |
CLI: jhipster-rust kubernetes-helm |
CLI: jhipster-rust kubernetes:helm |
The .yo-rc.json generators block and subGenerators list were updated accordingly, and the docs/KUBERNETES.md instructions now reference the new jhipster-rust kubernetes:helm command form.
New base-application:bootstrap sub-generator
JHipster 9 requires blueprints to contribute a dedicated base-application:bootstrap generator instead of stashing initialisation logic in the server entry point. The blueprint now ships one at generators/base-application/generators/bootstrap/generator.js that runs with sbsBlueprint: true and sets:
jhipsterConfig.backendType = 'Rust'(so the base JHipster generators know this isn't a Java/Spring Boot project)jhipsterConfig.withAdminUi = falseapplication.clientRootDir = 'client/',application.clientSrcDir = 'client/src/',application.clientTestDir = 'client/test/'(the JHipster 9 workspace model — see below)application.rustServerRootDir = 'server/'- For Angular specifically, forces Authority
skipClient = falseforbuiltInauthority entity so user-management UI renders correctly
Workspace-oriented client layout (client/ subfolder)
JHipster 9 formalises the "client as a separate npm workspace" layout. In this blueprint:
- The Angular / React / Vue client lives in
client/(not at the project root as in v0.9.6) - Root
package.jsondeclares"workspaces": ["client"] docker/,migrations/,server/, andCargo.tomllive at the project root next to theclient/workspace- Diesel schema lives at
server/src/db/schema.rs
This is a breaking change for projects regenerated from v0.9.6 — source file locations under client/src/** have moved and the webapp build output is now at server/dist/static/ instead of target/static/.
Angular component file renames
JHipster 9 drops the .component.ts / .component.html / .component.scss / .component.spec.ts file suffixes for Angular components. All generated Angular files now use the shorter forms: activate.ts / activate.html / activate.spec.ts instead of activate.component.{ts,html,spec.ts}. Every snapshot test was regenerated to match.
Angular dev server and build pipeline
- Dev server:
ng servein Angular 17+ uses Vite under the hood. A newclient/proxy.config.mjsproxies/api/*,/management/*, and/v3/api-docsto the Rust backend on port 8080. - Build plugins: A new
client/build-plugins/directory ships two esbuild plugins (define-esbuild.ts,i18n-esbuild.ts) that were previously Webpack loaders in v0.9.6. - Lint config format:
eslint.config.mjs→eslint.config.ts(TypeScript flat config). - Test runner: Jest is gone (
jest.conf.jsremoved); the blueprint now uses Vitest everywhere, consistent with JHipster 9's Angular defaults. - Docker build: The
client-builderDockerfile stage usesnpm install --force(instead of--legacy-peer-deps) and invokesnpm run webapp:prod(instead ofng build --configuration=production). A small inline Node script stripsESLintWebpackPluginfrom any legacy webpack config that might still be around — linting runs during development, not during the container build.
Kubernetes workspace-model workaround
JHipster 9's kubernetes generator expects a workspace deployment model where multiple apps live under <workspaceRoot>/<appName>/. For single-app deployments the blueprint now sets jhipsterConfig.directoryPath = '.' in both kubernetes/generator.js and kubernetes/generators/helm/generator.js beforeQueue hooks, so the generated manifests resolve paths relative to the current project instead of a non-existent parent workspace.
backendType: 'Rust' auto-inference
Base JHipster 9 expects projects to declare their backendType so framework-specific generators (spring-boot, data-relational, etc.) can opt out. The base-application:bootstrap generator now sets backendType = 'Rust' during the configuring phase, preventing JHipster from trying to compose the Spring Boot backend generator when our blueprint is active.
databaseType auto-derivation
JHipster 9's server bootstrap generates built-in entities (User, Authority) only when databaseType is set. In v0.9.6 only devDatabaseType was captured from the prompt, which meant the built-in entities were skipped for React/Angular clients that needed them for user-management. The rust-server configuring task now derives databaseType from devDatabaseType:
if (!this.jhipsterConfig.databaseType && devDb) {
if (['postgresql', 'mysql', 'mariadb', 'mssql', 'oracle', 'sqlite'].includes(devDb)) {
this.jhipsterConfig.databaseType = 'sql';
} else if (devDb === 'mongodb') {
this.jhipsterConfig.databaseType = 'mongodb';
}
}Rust-side improvements riding along with the upgrade
While migrating to JHipster 9, a handful of Rust-side quality fixes were folded in:
.cargo/config.tomlnow forcesRUST_TEST_THREADS=1for PostgreSQL and MySQL projects. Integration tests share a single database and are not isolated from each other, so they must run sequentially to avoid flakes on shared fixtures.test_utils.rswas refactored to useOnceLock<DbPool>instead ofOnce. The oldOnce-based pool initialisation would poison on the first failure (e.g. DB not reachable) and cascade the failure into every subsequent test. WithOnceLock::get_or_init, each test retries independently.- Angular
@popperjs/coredependency is now explicitly listed inclient/package.jsonfor Angular projects. It's a peer dependency of@ng-bootstrap/ng-bootstrapandbootstrap, andnpm install --legacy-peer-deps(used in the Dockerfile) wouldn't resolve it without an explicit entry. - PostgreSQL and MySQL integration tests consistent with the docker-compose credentials (
<baseName>user with trust auth, not hardcodedpostgres:postgres).
Multi-Database Entity Generation
The blueprint now correctly generates compilable, runnable Rust code for monolith projects on SQLite, PostgreSQL, MySQL, and MongoDB. Previous versions had a number of latent issues that only manifested with non-default databases or non-trivial relationship topologies.
rust-server now depends on the JHipster server bootstrap
- Root cause:
rust-serveronly calleddependsOnBootstrapApplication(), which left the JHipster server bootstrap unrun. Server bootstrap is what populatesentity.persistClass,entity.entityClass,relationship.otherEntityTableName,relationship.joinTable, etc. Without it, every entity template that referenced one of those properties exploded. - Fix: Added
await this.dependsOnBootstrap('server')inrust-server/generator.js'sbeforeQueue. This ensures every code path that produces an entity (jhipster-rust,jhipster-rust:rust-server,jhipster-rust:entity) gets a fully populated entity object regardless of how it was invoked.
Relationship property backfill (backfillRelationshipForRust)
- Root cause: Even when server bootstrap runs, JHipster's
prepareRelationshipForDatabaseonly setsjoinTablefor SQL many-to-many relationships (databaseTypeSql && relationshipManyToMany && ownerSide). For MongoDB,rel.joinTablewas undefined and the Rust MongoDB service template (which uses it as a link-collection name) crashed at<%= rel.joinTable.name %>. Similarly,relationship.otherEntityTableNamewas sometimes unset and produced broken SQL like_id INTEGER NOT NULL REFERENCES (id) ON DELETE CASCADE. - Fix: Introduced a shared
backfillRelationshipForRust(entity, relationship)helper ingenerator-rust-constants.js. It mirrorsrelationship.otherEntity.entityTableNameintorelationship.otherEntityTableName, synthesises ajoinTable.namefor owner-side many-to-many, and normalises the join-table name to a single-underscore separator. Wired intoPREPARING_EACH_ENTITY_RELATIONSHIPfor bothrust-serverand theentitysub-generator. Applies uniformly to SQL and MongoDB.
Rust file naming no longer depends on a client property
- Root cause:
rust-server/files.jsandpostWritingEntitiesTemplateTaskderivedsnake_casefile names fromentity.entityFileName, which is set by JHipster's client generator (client/entity.js:36). WithclientFramework: 'no'orskipClient: true, that property was undefined andtoSnakeCase(undefined)crashed. - Fix: Added a shared
rustEntityFileName(entity)helper that falls back toentityNameKebabCase(always populated by base entity preparation) →entityFileName→entityInstance→name. Wired into bothfiles.jsandgenerator.js's POST_WRITING_ENTITIES task.
SQL Migration Generation
Many-to-many join tables now live in their own migration files
- Root cause: Join-table
CREATE TABLEstatements used to be inlined into the per-entity migration template (migrations/entity/up.sql.ejs). For aStore ↔ Productmany-to-many, the Store migration tried to createrel_store_productswith FKs tostore(id)ANDproduct(id). Because Store'schangelogDateis earlier than Product's, the FK toproduct(id)was evaluated before theproducttable existed:- PostgreSQL / MySQL: silently refused the FK constraint, the join table was never created, and
cargo buildthen failed because the model importscrate::db::schema::rel_store_productsandprint-schemaskipped the missing table - SQLite: tolerated it because FK enforcement is off by default — the bug was masked
- PostgreSQL / MySQL: silently refused the FK constraint, the join table was never created, and
- Fix: New
migrations/join_table/up.sql.ejsanddown.sql.ejstemplates. Each owner-side many-to-many emits its own migration directory with a deterministic timestamp computed asbumpMigrationTimestamp(maxEntityChangelogDate, n)— strictly greater than every entity's changelogDate, so the join-table migration always runs after both endpoint tables exist on every database. The entity templates no longer contain inline join-table SQL.
ALTER TABLE migrations for new foreign-key columns on existing entities
- Root cause: When a new entity was added that introduced a back-reference to an existing entity (e.g. adding
Orderwith a one-to-many toProduct, which givesProducta many-to-one back-reference and a neworder_idcolumn), the entity sub-generator regenerated the existing entity's model file with the new field but did NOT generate a migration to add the column to the existing table. The nextcargo buildfailed withcannot find type 'order_id' in module 'crate::db::schema::product'. - Fix: The entity sub-generator's
WRITING_ENTITIEStask now scans existing migrations for FK columns of every many-to-one / owner-side one-to-one relationship. For each missing column it emits anALTER TABLE … ADD COLUMN … REFERENCES …migration via the newmigrations/alter_add_column/up.sql.ejstemplate, with a unique timestamp.
diesel migration run now runs automatically in the END phase
- Root cause: After writing entity migrations, the user had to manually run
diesel migration runto updateserver/src/db/schema.rsbeforecargo buildwould succeed. Only the entity sub-generator did this — the rust-server full-generation flow left it as a manual step. - Fix: Extracted the migration runner into a shared
runDieselMigrations(generator)helper and added an END task torust-server/generator.jsthat invokes it. Mongo backends are automatically skipped. Fresh project generation now produces a project that compiles with a singlecargo build(assuming a reachable database for PG/MySQL).
Diesel & DTO Improvements
Diesel queries now use Selectable::as_select() for column-name matching
- Root cause: The Diesel service template emitted
.load::<Entity>(conn)and.first::<Entity>(conn)which useQueryable's positional column matching. After anALTER TABLE ADD COLUMNappended a new FK column to the end of an existing table, the model and schema fell out of position-order alignment and every query failed to compile withthe trait bound (...): CompatibleType<...>errors. - Fix: All Diesel query sites in
services/_entityFileName_service.rs.ejsnow emit.select(Entity::as_select()).load(conn)/.select(Entity::as_select()).first(conn). TheSelectablederive matches columns by name instead of position, so column ordering between the model struct and the actual table no longer matters. MongoDB service template is unaffected (it doesn't use Diesel).
DTO fields with snake_case JHipster names now serialise correctly
- Root cause: Every generated DTO struct had
#[serde(rename_all = "camelCase")], which converts a Rust field name likecustomer_nameinto the JSON keycustomerName. But when a user named a JHipster fieldcustomer_name(which JHipster preserves as-is), the React/Angular form sendscustomer_namein the request body and the Rust deserializer rejected it withmissing field 'customerName'. - Fix: All three DTO structs (
<Entity>Dto,Create<Entity>Dto,Update<Entity>Dto) now emit an explicit#[serde(rename = "<jhipster_fieldName>")]for fields whose JHipster name doesn't match whatrename_all = "camelCase"would produce. CamelCase fields are unchanged (no redundant rename).
RelationshipId deserializer now accepts string-encoded integer IDs
- Root cause: React multi-selects emit
{id: "1"}because HTML form values are always strings. The RustRelationshipIddeserializer was an untagged enum with onlyId(i32)andObject { id: i32 }variants, so the string-typedidfield was rejected withdata did not match any variant of untagged enum IdOrObject at line 1 column 69. This affected all React-generated UIs trying to set many-to-many relationships. - Fix: Rewrote
RelationshipId::deserializeanddeserialize_optional_relationshipindto/common.rs.ejsto use aparse_i32_valuehelper that accepts both numeric (1) and string-encoded ("1") integers viaserde_json::Value. Works for React, Angular, and Vue clients without any frontend changes.
Client / Developer Experience
Workspace-root npm start / build / test / lint scripts
- Root cause: The blueprint puts the client in a
client/workspace subfolder, but JHipster's base layout assumes the client lives at the project root. Users runningnpm startfrom the project root gotnpm error Missing script: "start"and had tocd client && npm startinstead. - Fix: A new
addRootClientWorkspaceScriptstask inclient/generator.js's POST_WRITING phase forwardsstart,build,test, andlintfrom the workspace root to theclient/workspace vianpm run -w client/ <script>. Existing root scripts are not clobbered.
React workspace overrides for react-redux-loading-bar
- Root cause: Base JHipster's React generator wrote
"overrides": { "react-redux-loading-bar": { "react": "$react", "react-dom": "$react-dom" } }to the rootpackage.json. The$reactself-reference only resolves when the samepackage.jsondeclaresreactas a dependency, but in our workspace layoutreactlives inclient/package.json— so npm install printed a long wall ofERESOLVE overriding peer dependencywarnings about every package that pulled in React. - Fix: New
fixWorkspaceOverridestask inclient/generator.js'sPOST_WRITING_ENTITIESphase reads the actual react / react-dom version fromclient/package.jsonand rewrites the root override entries with explicit version strings viaStorage.merge(). Runs after the React generator'sclientBundlertask but before mem-fs commits to disk, so no overwrite-conflict prompt is triggered.
Swagger UI dev-mode assets (Angular only)
- Root cause: The Vite-based Angular dev server (Angular 17+) does not honor
angular.jsonassetsglob entries that source files fromnode_modules/. So when runningnpm start, the swagger UI iframe's required runtime files (swagger-ui-bundle.js,swagger-ui-standalone-preset.js,swagger-ui.css,axios.min.js) all returned 404 and the iframe at/admin/docswas blank around the Angular page chrome. Production builds were unaffected because the build pipeline copies the assets correctly. - Fix: A new
fixSwaggerUiDevAssetstask generatesclient/scripts/copy-swagger-ui-assets.cjs— a small helper that resolvesswagger-ui-dist/andaxios/viarequire.resolve('<pkg>/package.json')(works around the modernexportsfield that blocks direct./dist/subpath resolves) and copies the runtime files intoclient/src/swagger-ui/where the dev server happily serves them as plain static assets. The task addsprestartandpostinstalllifecycle hooks toclient/package.jsonto invoke the helper, plus aclient/src/swagger-ui/.gitignoreentry so the auto-copied files don't pollutegit status. The fix is gated onclientFrameworkAngular— React/Vue projects are unaffected.
Dockerfile fix for the swagger-ui postinstall hook
- Root cause: The
client-builderDockerfile stage copies onlyclient/package.jsonfirst (for layer caching), then runsnpm install --force, then copies the rest ofclient/. The new postinstall hook referencedscripts/copy-swagger-ui-assets.cjswhich didn't exist in the build context yet, abortingdocker buildwithCannot find module '/app/client/scripts/copy-swagger-ui-assets.cjs'. - Fix: The
Dockerfile.ejstemplate now copiesclient/scripts/into the build context beforenpm install, gated onclientFrameworkAngular. React/Vue projects don't get the extra COPY layer.
Generator Hygiene
"Could not retrieve version of blueprint" warnings silenced
- Root cause: Yeoman sets
_meta.packagePathto the package directory, but JHipster'sstoreBlueprintVersionfeature doesreadFileSync(this._meta.packagePath, 'utf8')and expects a file path topackage.json. The feature is enabled by default inBaseApplicationGeneratorso every blueprint generator emittedCould not retrieve version of blueprint 'jhipster-rust:<name>'at startup. - Fix:
fixBlueprintPackagePath(generator)helper ingenerator-rust-constants.jsregisters abefore:queueOwnTaskslistener (viaprependOnceListenerso it runs before JHipster's own handler) that appends/package.jsonto_meta.packagePathwhen it's a directory. Wired into the constructor of every generator in the blueprint.
list → select prompt deprecation
- Root cause: Inquirer's
listprompt type is deprecated in favor ofselect, producing console noise like`list` prompt is deprecated. Use `select` prompt instead. - Fix: Updated all 11 prompt definitions across
rust-server/command.js,kubernetes/generator.js,kubernetes/generators/helm/generator.js, and.blueprint/generate-sample/command.mjs.
Test Coverage
44 new regression tests added across three new spec files (389 → 433 total):
| Test File | New Tests | Coverage |
|---|---|---|
generators/rust-server/entity-generation.spec.js |
31 | Cross-database entity generation matrix (sqlite, postgresql, mysql, mongodb): models, DTOs, migrations, join-table ordering, RelationshipId, Selectable, snake_case rename, MongoDB-specific paths |
generators/client/dev-experience.spec.js |
8 | Angular & React workspace scripts, swagger-ui dev helper, .gitignore, Dockerfile COPY client/scripts ordering, React workspace overrides, non-Angular gating |
generators/entity/regression.spec.js |
5 | jhipster-rust:entity sub-generator paths: relationship backfill, join-table migration timestamps, snake_case DTO rename across all three DTO structs |
Each test maps directly to a session bug and includes an inline comment describing the original symptom — so a future regression of the same shape fails the build with a clear breadcrumb instead of producing broken sample projects.
Upgrade Notes
Breaking changes at a glance
This is a non-backward-compatible release due to the JHipster 9 upstream upgrade. The blueprint itself cannot be consumed from a JHipster 8 host; existing projects generated by v0.9.6 or earlier will need to be regenerated against JHipster 9.
Before you run jhipster-rust against a v0.9.6 project, verify your host environment:
node --version # must be >= 22.18.0 or >= 24.11.0
jhipster --version # must report 9.0.0If you were running the blueprint in a CI environment pinned to Node 18/20, you'll need to bump your CI image to Node 22+ as well.
Migrating an existing v0.9.6 project
Because of the client-layout move (root → client/ workspace) and the Angular component file renames, the safest path is a clean regeneration rather than an in-place upgrade:
# From your project root
rm -rf client/ server/src/app/ target/static/ client-src/
jhipster-rust --forceJHipster 9 writes generated files to the new workspace layout automatically, but stale files from the old layout will linger if you don't clean them up. Your .yo-rc.json will be updated in place; your .jhipster/*.json entity descriptors are preserved.
If you were using jhipster-rust kubernetes-helm, update any automation to the new command name:
- jhipster-rust kubernetes-helm
+ jhipster-rust kubernetes:helmSample project regeneration
Existing projects generated by v0.9.6 should be regenerated with jhipster-rust --force to pick up both the JHipster 9 layout changes and the stability fixes landed in v0.9.7 (SQL migration restructuring, Selectable query rewrite, DTO snake_case rename behavior, etc.). After regeneration:
# SQLite — fully self-contained
cargo build && cargo run
# PostgreSQL / MySQL — start the bundled docker compose first
docker compose -f docker/postgresql.yml up -d # or mysql.yml
cargo build && cargo run
# MongoDB — same idea
docker compose -f docker/mongodb.yml up -d
cargo build && cargo rundiesel migration run is now invoked automatically in the END phase for SQL backends, so re-running jhipster-rust --force against an existing project will refresh server/src/db/schema.rs for you.
Existing many-to-many entities
If your project already had many-to-many relationships generated with v0.9.6, the join-table SQL was inlined in the entity migration. After upgrading to v0.9.7 the entity migration template no longer emits inline join-tables, so you may want to:
- Drop the existing project's database (or
diesel migration revert --all) - Regenerate with
jhipster-rust --force - Run
diesel migration run(or simply re-runjhipster-rust --forceand let the END phase do it)
For PostgreSQL / MySQL projects this also fixes the latent FK constraint that was silently dropped on the old layout.
Running the dev server (Angular)
The npm start command now works from the project root (it forwards to the client/ workspace). After npm install from the root, in two terminals:
# Terminal 1
cargo run
# Terminal 2
npm startThe Angular dev server proxies /api/*, /management/*, and /v3/api-docs to the Rust backend on port 8080, and the swagger UI at /admin/docs now renders correctly thanks to the postinstall asset copy.
Running the Docker container
The container is built and run in production mode by default (APP_ENV=production). In production, the dev ribbon and the API menu item are intentionally hidden — both are gated on the dev / api-docs JHipster profiles for security reasons (matching base JHipster Spring Boot behavior).
To run the container with the dev ribbon and API menu item visible:
docker run -e APP_ENV=development -p 8181:8080 <image>:latest
# or
docker run -e APP_PROFILE=dev -p 8181:8080 <image>:latestNo rebuild is needed — APP_ENV / APP_PROFILE are read at runtime by /management/info.