feat: JS server extension points, TypeScript content patches, and the JSServerExtensionInvoker SDK - #687
feat: JS server extension points, TypeScript content patches, and the JSServerExtensionInvoker SDK#687romain-pm wants to merge 21 commits into
Conversation
ADR-0001: per-type registrars over a shared base class (whiteboard-aligned) ADR-0002: first-class registry types (action, choicelist-initializer, node-validator) ADR-0003: typed TS registration wrappers with raw Java escape hatch ADR-0004: CSRF whitelisting stays the module author's responsibility ADR-0005: node validators bridged via a single Bean Validation bean on nt:base
…sion bridges - New AbstractServiceRegistrar<S>: per-bundle registry-entry discovery, bridge creation, OSGi service publication and tracked unregistration with per-entry error isolation (ADR-0001). - RenderFilterRegistrar refactored onto the base class. Two fixes folded in: the registry entry's priority is no longer overwritten to 0 after construction (parsed as float), and execute/prepare now null-guard both the registry entry (module stopped mid-flight) and null/undefined JS results. - mockito-core + graalvm js added at test scope.
- ChoiceListInitializerRegistrar bridges registry entries of type
'choicelist-initializer' to ModuleChoiceListInitializer OSGi services
(consumed by core, usable as choicelist[key] in CND definitions).
- New registerChoiceListInitializer() library API: typed callback receiving
{param, locale (BCP-47), values, node?, java escape hatch}; returns
{label, value, properties?} choices (properties variant supported).
- java-ts-bind: generate ExtendedPropertyDefinition (narrow whitelist,
flattened parent methods) and Locale.toLanguageTag.
- Test-module fixture + CND type, Cypress spec (forms GraphQL API), docs guide.
- ActionRegistrar bridges registry entries of type 'action' to
org.jahia.bin.Action OSGi services, invoked via <nodeUrl>.<name>.do.
- New registerAction() library API: typed declaration (requiredMethods,
requireAuthenticatedUser [Jahia default: true], requiredPermission,
requiredWorkspace) and handler receiving {parameters, renderContext,
resource, session, request, urlResolver}; result {statusCode, json,
redirect, absoluteRedirect}. The adapter pre-stringifies json to avoid
polyglot deep-conversion issues with nested structures.
- CSRF stays the module's responsibility (ADR-0004): documented recipe +
test-module .cfg exercising it end-to-end.
- java-ts-bind: generate URLResolver (narrow whitelist).
- Test-module fixtures (GET/POST/auth/redirect), Cypress spec incl. redeploy
resilience, docs guide.
- New registrars/validation package: a single JSNodeValidator Bean Validation
bean registered under the sentinel nt:base node type (exactly-once execution
per changed node per save; no clobbering of Java validators on real node
types), carrying four repeatable class-level @JSValidation constraints that
mirror Jahia's default/advanced/skip-on-import validation phases (ADR-0005).
- JSValidationConstraintValidator dispatches to JS validators via the
ref-counted NodeValidatorRegistrar (volatile snapshot gate before any
GraalVM entry, ownership-checked platform (un)registration, fail-closed on
throwing validators).
- Messages follow JahiaMessageInterpolator semantics: {resource.bundle.key}
messages are localized through module resource bundles, anything else is
verbatim; sub-2-character messages get a generic fallback (interpolator
crash guard).
- New registerNodeValidator() library API with locale (BCP-47) + raw Java
escape hatch.
- Bean Validation deps (api provided, hibernate-validator 6.2.0.Final at test
scope); full-chain unit tests against real HV; registrar state-machine tests.
- Test-module fixtures + CND type, Cypress spec (field/node-level, phases,
verbatim messages, redeploy resilience), reference documentation.
…nsion points - registerRenderFilter(): typed wrapper over the existing 'render-filter' registry shape (backward compatible), with applyOn* options accepting arrays and fractional priorities. - Hydrogen gains a self-contained ContactForm component demonstrating all three new extension points: a CSRF-whitelisted POST action receiving the form, a choicelist initializer for the form style, and a node validator on the notification email.
getName() is not part of the generated JahiaUser typing; the CI test-module build runs tsc --noEmit and failed on it.
- Choicelist initializers receive the CONTENT locale (language being edited), not the editor UI locale — docs, TSDoc and Cypress spec corrected (verified against a live instance via the Content Editor forms API). - Actions guide: the render servlet writes the JSON body only when the request sends Accept: application/json.
… ADR 0006 Completes the TS migration scripts feature (runtime + typed API landed in earlier commits on this branch): - persistent failure barrier: a recorded .failed migration holds back later-named pending migrations across restarts/redeploys - changeNodeType: rebind via getRealNode() (the Jahia wrapper throws UnsupportedRepositoryOperationException) and carry mapped properties around the retype (Jackrabbit validates existing properties against the new type) - JcrHelper.doExecuteAsSystem: include the exception class in the message (JCR exceptions often carry a null message across polyglot) - jahia-test-module: 9 migration fixtures covering the five operations plus skip/failure/halt semantics; Cypress spec asserting statuses in j:bundlesScripts and the transformed content - guide (docs/2-guides/6-migrations) and ADR 0006 All semantics verified live on jahia-discovery 8.2.3.
🦜 Chachalog
|
📝 Documentation GuidelinesThank you for contributing to our documentation! To ensure your contributions meet our standards, please review these resources:
This comment is posted automatically when changes are detected in the |
There was a problem hiding this comment.
Pull request overview
This PR extends the JavaScript Modules engine so JS modules can declare additional server-side extension points (choicelist initializers, node validators, actions, render filters) and introduces first-class JS migrations with typed APIs, engine-side registrars, documentation (ADRs/guides), and automated test coverage.
Changes:
- Added typed registration APIs in
@jahia/javascript-modules-libraryfor actions, choicelist initializers, node validators, render filters, and migrations. - Implemented/expanded engine-side registrars and bridges (including a shared
AbstractServiceRegistrar) plus migration execution and status recording. - Added Cypress e2e specs, Java unit tests, sample module updates, docs/ADRs, and updated Vite plugin defaults for server entry globs.
Reviewed changes
Copilot reviewed 61 out of 61 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| vite-plugin/src/index.ts | Expands default server entry glob to include .js/.ts variants. |
| vite-plugin/README.md | Documents the updated default inputGlob. |
| tests/cypress/e2e/ui/nodeValidatorTest.cy.ts | Cypress coverage for JS node validator behavior and redeploy resilience. |
| tests/cypress/e2e/ui/choicelistInitializerTest.cy.ts | Cypress coverage for JS choicelist initializer behavior (params, locale, properties). |
| tests/cypress/e2e/ui/actionTest.cy.ts | Cypress coverage for JS actions (methods/auth/redirect/CSRF whitelist, redeploy resilience). |
| tests/cypress/e2e/engine/migrationTest.cy.ts | Cypress coverage for JS migration ordering, statuses, and effects. |
| samples/hydrogen/src/components/ContactForm/extensions.server.tsx | Example JS module declaring action/choicelist initializer/validator. |
| samples/hydrogen/src/components/ContactForm/definition.cnd | Sample CND using a JS-declared choicelist initializer key. |
| samples/hydrogen/src/components/ContactForm/default.server.tsx | Sample component using the JS-declared action endpoint. |
| samples/hydrogen/settings/configurations/org.jahia.modules.jahiacsrfguard-hydrogen.cfg | Sample CSRF whitelist config for a POST action. |
| MIGRATIONS-DEMO.md | Developer-facing migration contract/demo document. |
| javascript-modules-library/src/index.ts | Exposes new registration APIs and migration types from the library entrypoint. |
| javascript-modules-library/src/framework/registerRenderFilter.ts | Typed render-filter registration wrapper writing to the server registry. |
| javascript-modules-library/src/framework/registerNodeValidator.ts | Typed node-validator registration wrapper and context adaptation. |
| javascript-modules-library/src/framework/registerChoiceListInitializer.ts | Typed choicelist initializer wrapper and Java→JS value adaptation. |
| javascript-modules-library/src/framework/registerAction.ts | Typed action wrapper with parameter conversion + JSON result shaping. |
| javascript-modules-library/src/framework/migrations/types.ts | Public types for migration declaration/context/operations. |
| javascript-modules-library/src/framework/migrations/registerMigration.ts | Migration registration wrapper with sync-only enforcement and dedupe guard. |
| javascript-modules-library/src/framework/migrations/operations.ts | Implementation of guard-railed migration helpers (remove/set/convert/retype). |
| javascript-modules-library/src/framework/migrations/jcr.ts | Migration JCR batching engine + system-session helper. |
| javascript-modules-engine-java/src/test/java/.../validation/NodeValidatorRegistrarTest.java | Unit tests for validator registrar lifecycle and violation shape handling. |
| javascript-modules-engine-java/src/test/java/.../validation/JSNodeValidatorBeanValidationTest.java | Full Bean Validation chain tests for the JS validator bridge. |
| javascript-modules-engine-java/src/test/java/.../migrations/MigrationRegistrarTest.java | Unit tests for migration ordering, terminal status semantics, halting behavior. |
| javascript-modules-engine-java/src/test/java/.../ChoiceListInitializerRegistrarTest.java | Unit tests for JS choicelist value conversion. |
| javascript-modules-engine-java/src/test/java/.../ActionRegistrarTest.java | Unit tests for action mapping + result conversion. |
| javascript-modules-engine-java/src/test/java/.../AbstractServiceRegistrarTest.java | Unit tests for the shared registrar base class behavior. |
| javascript-modules-engine-java/src/main/java/.../validation/NodeValidatorRegistrar.java | Engine registrar bridging JS validators to Jahia’s validation pipeline. |
| javascript-modules-engine-java/src/main/java/.../validation/JSViolation.java | Value object for JS validator violations. |
| javascript-modules-engine-java/src/main/java/.../validation/JSValidationConstraintValidator.java | Bean Validation constraint validator dispatching to JS validators. |
| javascript-modules-engine-java/src/main/java/.../validation/JSValidation.java | Repeatable constraint annotation representing validation phases. |
| javascript-modules-engine-java/src/main/java/.../validation/JSNodeValidator.java | Sentinel Bean Validation bean bridging all JS node validators. |
| javascript-modules-engine-java/src/main/java/.../RenderFilterRegistrar.java | Refactors render-filter registrar onto shared base + honors priority. |
| javascript-modules-engine-java/src/main/java/.../migrations/MigrationSupport.java | Java support object passed into migrations (logger/dry-run/definition ops). |
| javascript-modules-engine-java/src/main/java/.../migrations/MigrationRegistrar.java | Runs pending JS migrations and records terminal statuses in the shared store. |
| javascript-modules-engine-java/src/main/java/.../ChoiceListInitializerRegistrar.java | Publishes JS choicelist initializers as OSGi services with collision warnings. |
| javascript-modules-engine-java/src/main/java/.../ActionRegistrar.java | Publishes JS actions as OSGi services with collision warnings. |
| javascript-modules-engine-java/src/main/java/.../AbstractServiceRegistrar.java | New shared registrar base for “registry entry → OSGi service” bridging. |
| javascript-modules-engine-java/src/main/java/.../js/server/JcrHelper.java | Adds doExecuteAsSystem helper for migration/system-session execution. |
| javascript-modules-engine-java/pom.xml | Adds provided/test dependencies for JSON + Bean Validation + GraalJS test runtime. |
| javascript-modules-engine-java/.java-ts-bind/package.json | Expands bindings/whitelist for new Java types/methods used by typed APIs. |
| javascript-create-module/templates/module/vite.config.mjs | Updates template comment for the new default server glob. |
| jahia-test-module/src/react/server/extensions/validators.ts | JS fixtures for node validator behaviors covered by Cypress specs. |
| jahia-test-module/src/react/server/extensions/migrations.ts | JS fixtures for migration behaviors covered by Cypress specs. |
| jahia-test-module/src/react/server/extensions/choicelists.ts | JS fixtures for choicelist initializer behaviors covered by Cypress specs. |
| jahia-test-module/src/react/server/extensions/actions.ts | JS fixtures for action behaviors covered by Cypress specs. |
| jahia-test-module/settings/definitions.cnd | Adds fixture node types/properties used by new server-extension tests. |
| jahia-test-module/settings/configurations/org.jahia.modules.jahiacsrfguard-jsmtest.cfg | CSRF whitelist for POST action test fixture. |
| docs/adr/README.md | Adds ADR index and references for the new architecture decisions. |
| docs/adr/0001-javascript-server-extension-points.md | ADR: per-type registrars over shared base class. |
| docs/adr/0002-first-class-registry-types.md | ADR: first-class registry types per extension point. |
| docs/adr/0003-typed-registration-wrappers.md | ADR: typed TS-first wrappers with Java escape hatch. |
| docs/adr/0004-csrf-whitelisting-for-js-actions.md | ADR: CSRF whitelist remains module-author responsibility. |
| docs/adr/0005-js-node-validators-single-bean-validation-bridge.md | ADR: single sentinel Bean Validation bridge for validators. |
| docs/adr/0006-javascript-migrations.md | ADR: run migrations via registrar + shared patch status store. |
| docs/3-reference/3-node-validators/README.md | Public documentation for JS node validators. |
| docs/3-reference/1-cnd-format/README.md | Mentions JS-declared choicelist initializers as a CND option. |
| docs/2-guides/6-migrations/README.md | Public documentation for writing JS migrations. |
| docs/2-guides/5-choicelist-initializers/README.md | Public documentation for JS choicelist initializers. |
| docs/2-guides/4-actions/README.md | Public documentation for JS actions + CSRF whitelist recipe. |
| .chachalog/js-server-extension-points.md | Release note describing new server extension points and render-filter priority change. |
| * supported.](https://www.npmjs.com/package/@rollup/plugin-multi-entry#supported-input-types) | ||
| * | ||
| * @default "**/*.server.{jsx.tsx}" | ||
| * @default "**/*.server.{js,jsx,ts,tsx}" |
| /** Converts the Java List of org.jahia...ChoiceListValue accumulated so far into plain JS objects. */ | ||
| const toJsChoiceListValues = (values: List<unknown>): ChoiceListValue[] => { | ||
| const result: ChoiceListValue[] = []; | ||
| if (values) { | ||
| for (let i = 0; i < values.size(); i++) { | ||
| // Jahia's ChoiceListValue: getDisplayName(), getValue() (JCR Value), getProperties() | ||
| const value = values.get(i) as { | ||
| getDisplayName(): string; | ||
| getValue(): { getString(): string }; | ||
| getProperties(): JavaMap<string, unknown> | null; | ||
| }; | ||
| result.push({ label: value.getDisplayName(), value: value.getValue().getString() }); | ||
| } | ||
| } | ||
| return result; | ||
| }; |
| JavaScript modules can now declare choicelist initializers, server-side node validators and actions — extension points that previously required a Java module. Use the new `registerChoiceListInitializer`, `registerNodeValidator`, `registerAction` and `registerRenderFilter` functions from `@jahia/javascript-modules-library`. | ||
|
|
||
| Note for existing modules using `server.registry.add("render-filter", …)`: a declared `priority` is now honored (it was previously ignored and forced to 0), which may reorder such filters in the render chain. |
| if (!registry.hasNodeType(name)) { | ||
| getLogger(bundle.getSymbolicName()).info( | ||
| "Node type {} is not registered on this instance, nothing to unregister", name); | ||
| return; | ||
| } |
| if (dryRun) { | ||
| getLogger(bundle.getSymbolicName()).info("[dry-run] would unregister node type {}", name); | ||
| return; | ||
| } |
| // Snapshot the identifiers first: mutating nodes while paging through a live query result | ||
| // (retyping, removing…) would make the pagination skip or repeat nodes. | ||
| const identifiers = withSystemSession({ workspace }, (session) => { | ||
| const result: string[] = []; | ||
| for (let offset = 0; ; offset += SNAPSHOT_PAGE_SIZE) { | ||
| const sql2Query = session | ||
| .getWorkspace() | ||
| .getQueryManager() | ||
| .createQuery(query, "JCR-SQL2"); | ||
| sql2Query.setLimit(SNAPSHOT_PAGE_SIZE); | ||
| sql2Query.setOffset(offset); | ||
| const iterator = sql2Query.execute().getNodes(); | ||
| let pageCount = 0; | ||
| while (iterator.hasNext()) { | ||
| result.push((iterator.nextNode() as JCRNodeWrapper).getIdentifier()); | ||
| pageCount++; | ||
| } | ||
| if (pageCount < SNAPSHOT_PAGE_SIZE) break; | ||
| } | ||
| return result; |
Renames the run-once content-scripting feature from "migration" to "content patch" — "migration" collided with Jahia core's platform Migrator and over-narrowed a mechanism whose real job is arbitrary run-once content/definition fixes (repairs, backfills, retypes), not only schema migrations. - registerMigration -> registerContentPatch; context.migrate -> .patch - MigrationRegistrar/Support -> ContentPatchRegistrar/Support (registrars.contentpatches package) - registry type "migration" -> "content-patch" - status path prefix /javascript/migrations/ -> /javascript/content-patches/ - config PID + logger namespace ...engine.migrations -> ...engine.contentpatches - library dir framework/migrations -> framework/contentPatches; all exported types Migration* -> ContentPatch* - test-module fixtures, Cypress spec, guide (docs/2-guides/6-content-patches), ADR 0006, and design docs (CONTENT-PATCHES-PLAN/DEMO) renamed; Groovy / Sanity / Contentful prior-art references keep their own "migration" term 58 unit tests green; all content-patch semantics (five operations + skip/failure/persistent-barrier + new status-path prefix) re-verified live on jahia-discovery 8.2.3.
…nsions Adds a public, polyglot-free SDK so other OSGi bundles can consume JS-declared server extensions (server.registry.add(type,...)) and invoke their callbacks: - JSServerExtensionInvoker (interface) + Impl (@component) in a new exported package org.jahia.modules.javascript.modules.engine.sdk - forEach(type, handler) runs within one pooled GraalVM context; Invoker.call executes a JS callable and converts the result to plain Java (no GraalVM types leak to consumers, so callers need no polyglot dependency) - engine bundle now Export-Package's only the sdk package Enables Formidable to run JS-authored form-field validators server-side (issue Jahia/formidable#158) without embedding GraalVM or engine internals.
Frees the plain 'action' name for the upcoming client-callable actions (#588) and makes the org.jahia.bin.Action lineage explicit. Registry type becomes 'node-legacy-action'; Java registrar, types, fixtures, hydrogen sample and the guide (now 'Legacy Node Actions') renamed accordingly. Pre-release rename, no compatibility shim.
- .action.ts files are compiled twice by @jahia/vite-plugin: the server bundle registers every export as a callable action; the client bundle replaces the module with generated fetch stubs (devalue-serialized args and results), so islands call server code as plain async functions. - action(schema, fn) safe wrapper: input validation via any Standard Schema compatible library (interface vendored, no dependency added); validation issues travel back to the client error. - Engine: single jsAction dispatch endpoint riding the render servlet (visitor session, guests allowed), mandatory X-JS-Action header as CSRF defense, engine-owned csrfguard whitelist entry; JSPromise settles async handlers through GraalJS microtask draining (unit-tested against real GraalJS, incl. never-settling detection). - Docs: new Actions guide + when-to-use-what vs legacy node actions; ADR-0007 (naming) and ADR-0008 (design). - Fixtures: vite-plugin snapshot fixture, test-module .action.ts + island, Cypress spec covering wire protocol, devalue round-trip, validation, header requirement and the end-to-end island path.
40 of the branch's files did not conform to the repo prettier config; formatted wholesale (includes regenerated vite-plugin fixture snapshots).
Applied findings from a fresh-eyes review of the branch's new code:
- RenderFilterRegistrar: class javadoc added; execute/prepare guards made
symmetric — a missing optional callback is a silent no-op, only a vanished
registry entry warns.
- JSPromise.Settled renamed Outcome with isSettled() ('a Settled that is not
done' contradicted itself); GenericActionEndpoint uses SC_OK constants and
an error(String) overload.
- NodeValidatorProps renamed NodeValidatorDeclaration (aligns with the
sibling *Declaration interfaces); stale 'Registered action' log message
disambiguated after the legacy rename.
- Bridge constructors take registryEntry (was: value, colliding mentally
with polyglot Value); dead getRegistryType() removed.
- vite-plugin actions: export-discovery regex tightened to the documented
forms (const/function/async function), extension set aligned with the
default glob, module-name fallback now warns loudly, stub header value
commented; choicelist accumulated-values mapping documents why properties
are not surfaced; messageOf() helper extracted in the action adapter.
- registerActionsModule renamed __registerActionsModule and marked @internal: it is a vite-plugin-generated-code contract, not a developer API. It stays in the main entry point because the engine resolves the library as a single shared module at runtime (a subpath entry would not resolve there) — recorded in ADR-0008. - server.registry documentation now steers extension-point registration to the typed register* functions; the raw entry shapes are internal contracts.
Implements #688 and #689. - JSPromise moved from ..engine.actions to ..engine.jsengine (general GraalJS facility, not an actions concern) and gains settleOrThrow(): fulfilled value returned, rejection converted to GraalVMException (same semantics as a synchronous throw), never-settling promise fails with an explicit message. - All four bridges (choicelists, node validators, legacy node actions, render filters) settle JS results through it, so async callbacks now work everywhere (microtask-only, like actions). Validators keep their fail-closed policy: a rejection lands in the existing catch and blocks the save. - TS signatures accept Promise returns; the legacy-action adapter chains the conversion on the handler promise. Docs and ADR-0003 updated; test fixtures made async on purpose so the existing Cypress specs exercise the async path end-to-end. - registrars/package-info.java documents the package layout rule.
Content patches are an independent feature (ContentPatchRegistrar implements the engine Registrar SPI, no dependency on the extension-points mechanism). Moving them to #697 shrinks this PR by ~2300 LOC and separates two review audiences. This also neutralizes the mislabeled commit 3b6c507: the content-patch files it swept in are no longer part of this branch's net diff. Removed here: registerContentPatch + jcr/operations/types, ContentPatchRegistrar (+support, tests), the content-patch guide/ADR-0006/plan docs, the Cypress spec, and the patchTest* test-module fixtures. Left in place intentionally (not content-patch-exclusive, referenced by other verticals or generally useful): the org.json/test dependencies in the engine-java pom, the JCR mutation-method entries in .java-ts-bind, and JcrHelper.doExecuteAsSystem.
The generic-actions Cypress spec (genericActionTest.cy.ts) uses devalue, added
to tests/package.json, but tests/yarn.lock was not regenerated. CI's immutable
install in the standalone tests/ project rejected it ('the lockfile would have
been modified'), failing on-code-change / static-analysis. This was the only
failing check (build + sonar pass).
|
Heads-up: this branch's integration suite is red for a packaging reason, and the fix is one line.
The suite's first spec ( Fix (adding |
Scope
Four features, delivered on this branch as stacked work (recommended merge: squash, see "Review notes"):
registerChoiceListInitializer), server-side node validators (registerNodeValidator), legacy node actions (registerNodeLegacyAction, theorg.jahia.bin.Actionbridge) and typed render filters (registerRenderFilter), over a sharedAbstractServiceRegistrarmechanism.registerContentPatch: versioned, run-once content transformations backed by core's patch status store..action.tsfiles compiled twice (server implementation + client fetch stubs), devalue wire format, optional Standard Schema validation (action(schema, fn)), singlejsActiondispatch endpoint.How to review (suggested order)
docs/adr/0001–0008): every structural decision, alternative and trade-off is recorded there — the code is meant to read as their implementation.registrars/AbstractServiceRegistrar.java+RenderFilterRegistrar.java(the refactored template). Key invariant: bridges re-resolve JS functions from the pooled-context registry on every call.registrars/validation/): the one deliberate deviation — single Bean Validation bean on sentinelnt:base(ADR-0005 explains why per-nodeType registration is incorrect).vite-plugin/src/actions.ts,framework/actions/,actions/GenericActionEndpoint.java+JSPromise.java): the dual compilation and the microtask-settling contract (ADR-0008).Verification
.action.tsfixturejavax.validation/org.jsonfrom the platform (not embedded)Review notes / known caveats
3b6c507is mislabeled: it carries an early snapshot of the content-patches work swept in from a shared working tree, under a one-line-fix title. Squash-merging makes this moot; reviewing file-by-file (not commit-by-commit) is recommended for the content-patches part.render-filterregistry entries'prioritywas previously ignored (forced to 0) and is now honored..action.ts).How to review
Content patches have been split out into #697, so this PR is now the server extension points + actions + SDK (~2,300 LOC smaller).
Review by the Files-changed tab (squash-merge is intended — the intermediate commits are throwaway). Suggested attention budget:
javascript-modules-engine-java/.../registrars/**and.../actions/**; the TS wrappers injavascript-modules-library/src/framework/**;vite-plugin/src/actions.ts. Read the ADRs (docs/adr/) first — the code is their implementation.jahia-test-module/**fixtures,samples/hydrogen/**, the Cypress specs intests/**, the guides indocs/2-guides/**.javascript-modules-engine-java/target/**, thevite-plugin/fixtures/expected/**snapshots, and the pure-formatting commit5008239(reviewable with GitHub's Hide whitespace).Once you've reviewed the shared mechanism + one vertical end-to-end, the other verticals are variations on the same bridge pattern.
Tracking
Part of EPIC #554.
Closes #588, closes #691, closes #692, closes #693, closes #694, closes #696, closes #688, closes #689.
Content patches were split into their own PR #697 (closes #695).
Follow-up (stays open): #690 — actions endpoint v2 (dedicated servlet transport).