refactor(deployment): update SDK publishing workflow to align with dotCMS release strategy - #36633
Conversation
…w versioning strategy - Removed deprecated manual SDK release workflow and associated logic. - Introduced a new automated SDK release workflow that publishes SDK packages in lockstep with dotCMS releases. - Updated action inputs to require an exact version for publishing, eliminating the previous prerelease channel. - Simplified versioning logic by removing the VERSION file dependency and directly using the release tag for SDK versions. - Enhanced error handling and notifications for SDK publishing processes. This change streamlines the SDK release process, ensuring that all SDK packages are published at the same version as the corresponding dotCMS release.
|
Claude finished @KevinDavilaDotCMS's task in 6m 18s —— View job Claude Code ReviewRe-reviewed New Issues
Resolved
Notes (non-blocking)
• |
|
Went through all 3 findings from the Claude code review against the actual code at this commit — unlike the earlier deepseek-bot round on the fork PR, these were all real and confirmed by direct inspection. Fixed all three. Fixed
Same changes mirrored into the |
…error handling - Added idempotency check to skip publishing if the SDK version has already been published, preventing failures on re-runs. - Improved error handling during the publishing process to ensure that failures are reported correctly. - Updated comments for clarity on the new logic and its implications for the release process. These changes streamline the SDK deployment process, ensuring smoother operations during automated releases.
- Changed the default specification URL from the old production endpoint to the new production endpoint for the API documentation. - This update ensures that the SDK points to the correct location for the OpenAPI specification. This change is part of ongoing efforts to maintain accurate and up-to-date references in the SDK.
…-date-lockstep-versioning
sfreudenthaler
left a comment
There was a problem hiding this comment.
A few considerations and open questions, but no blocking commentary.
- Changed the setting from to in the SDK release workflow to prevent partial publishing of SDK packages during mid-loop cancellations. - Updated comments to clarify the reasoning behind this change, ensuring that a re-run is the only way to stop the loop partway. This adjustment enhances the reliability of the SDK publishing process, aligning it with similar practices in other workflows.
|
Tick the box to add this pull request to the merge queue (same as
|
…rsion + minimum supported SDK, SDKs warn on mismatch (dotCMS#36678) ## Videos Without missmatch: https://github.com/user-attachments/assets/14921ec1-3eea-4b12-a5b9-04cfe26f2109 With missmatch: https://github.com/user-attachments/assets/a71f6943-9b04-4bc7-87e5-24045d5ec6a9 ## Summary Implements the version handshake described in dotCMS#36609: the server advertises its own version and the oldest SDK version it still supports via two response headers; `@dotcms/client` reads them off responses it already makes and warns in the console when the installed SDK is outside the compatible range. Built directly on top of the date-lockstep versioning work in dotCMS#36587/dotCMS#36633 (ADR-0019) — this feature is only possible because the SDK's own version now reliably matches a real dotCMS release version. ## Problem this solves With Evergreen Tracks, a customer's environments (dev/staging/prod) run different dotCMS versions at different times, and SDKs are published in lockstep with the dotCMS version. Most releases don't change the SDK contract, so customers have no reason to update the SDK when nothing requires it — deployed SDKs drift behind the server over time, and nothing today tells a customer their SDK is no longer supported by the dotCMS version an environment was just updated to. ## What changed — Backend (Java) - **`MinSdkVersion.java`** (new, `com.dotcms.rest.config`) — a single manually-maintained constant: the oldest `@dotcms/*` SDK version this dotCMS instance still supports. Starts at baseline `"0.0.0"` (no breaking change has been introduced under this mechanism yet, so every previously published SDK version is still considered compatible). See "Decisions" below for the bump procedure. - **`SdkVersionWebInterceptor.java`** (new, `com.dotcms.filters.interceptor.meta`) — adds two static headers to every response: ``` X-DotCMS-Version: <ReleaseInfo.getVersion()> X-DotCMS-Min-SDK: <MinSdkVersion.VALUE> ``` Registered in `InterceptorFilter.addInterceptors()`, alongside the existing `ResponseMetaDataWebInterceptor` (the `x-dot-server` header) — same mechanism, same file, same pattern. - **`Access-Control-Expose-Headers`** — no change needed. `dotmarketing-config.properties` already defaults `api.cors.default.Access-Control-Expose-Headers` to `*`, so both new headers are already exposed cross-origin for browser-side SDK calls. ### Why a `WebInterceptor` and not a JAX-RS `ContainerResponseFilter` The first implementation used a JAX-RS `ContainerResponseFilter` (`SdkVersionHeaderFilter`, now removed). It worked for JAX-RS resources (confirmed via `curl` against `/api/v1/appconfiguration`) but **not** for GraphQL (`/api/v1/graphql`), because `DotGraphQLHttpServlet` is a plain `HttpServlet`, not a JAX-RS resource — it never enters the Jersey filter chain a `ContainerResponseFilter` hooks into. Since `@dotcms/client`'s page-loading flow goes through GraphQL by default, the JAX-RS-only filter silently missed the majority of real SDK traffic (confirmed by manual end-to-end testing against a local instance — see "Testing" below). `InterceptorFilter` is the first filter in the pipeline and maps every request regardless of how it's ultimately handled (JAX-RS or plain servlet) — the exact reason `ResponseMetaDataWebInterceptor` already uses this mechanism for `x-dot-server` instead of a JAX-RS filter. Switched to the same approach for full coverage. ## What changed — SDK (`@dotcms/client` only) Only `libs/sdk/client` needed changes. `react` and `angular` never make their own raw `fetch` calls — they route everything through `@dotcms/client`'s `FetchHttpClient`, so they inherit the check automatically, exactly as the issue describes. - **`sdk-compatibility.ts`** (new) — pure, dependency-free comparison logic: - Numeric calver comparison (`26.10.1 > 26.7.13`), not string comparison. - `checkSdkCompatibility(headers, ownVersion)`: reads `X-DotCMS-Version` / `X-DotCMS-Min-SDK` off a `Headers` object; logs `console.error` once per session if `ownVersion < minSdkVersion`; logs `console.warn` once per session if `ownVersion > serverVersion`. - Fails open by design: missing headers (older server), unparsable versions, or any unexpected exception → silently does nothing, never throws, never changes request/response behavior. - `sdk-compatibility.spec.ts` — 11 unit tests, 100% line coverage. - **`rollup.config.cjs`** — a small, dependency-free virtual-module Rollup plugin (`sdkVersionPlugin`) injects the package's own version (from its own `package.json`, already set to the exact dotCMS release version by the release pipeline before this build runs) as a build-time constant, importable as `virtual:sdk-version`. No new npm dependency added — verified `@nx/rollup`'s `withNx()` concatenates a `plugins` array passed in rather than replacing its own, so this doesn't interfere with Nx's generated plugins. - **`fetch-http-client.ts`** — one fire-and-forget call added right after `await fetch(url, options)`: `checkSdkCompatibility(response.headers, SDK_VERSION)`. - **`virtual-modules.d.ts`** (new) — ambient `declare module 'virtual:sdk-version'` so TypeScript accepts the import. - **`__mocks__/virtual-sdk-version.ts`** + **`jest.config.ts`** `moduleNameMapper` — Jest never runs the Rollup build, so the virtual module is mapped to a real stub file for unit tests. ## Decisions made along the way ### 1. Why `MIN_SDK_VERSION` (a floor) instead of a simple `ownVersion !== serverVersion` check Considered and rejected a simpler "warn on any mismatch" approach. The issue explicitly calls this out as the reason a floor is needed, not equality: > Most dotCMS releases do not change the SDK contract. Some do. With Tracks, a customer's SDK almost never exactly equals the current server version at any given moment — most releases don't require an SDK update at all. Warning on any mismatch would fire constantly for nearly every customer regardless of real incompatibility, training people to ignore it (alert fatigue) and defeating the point of the signal. `MIN_SDK_VERSION` only moves when a release actually breaks the contract, so the warning stays meaningful. ### 2. `MIN_SDK_VERSION` bump procedure — manual, approximate, decided as "Option A" `MIN_SDK_VERSION` is manually maintained, not computed — per the issue's own AC ("documented bump procedure"). The exact mechanics weren't specified in the issue, so we picked between: - **Option A (chosen):** when a PR breaks SDK compatibility, the developer sets `MinSdkVersion.VALUE` to whichever `@dotcms/client` version is already published as `latest` on npm at PR time. Doesn't need to be exact — it only needs to correctly gate SDKs older than what's needed. - **Option B (rejected):** try to set it to the exact future dotCMS release version this PR will ship in. Rejected because that version is a date assigned only when a release is actually cut (per ADR-0019's date-lockstep scheme) — it isn't known yet at PR-review time, so this isn't practically achievable. This is documented directly in `MinSdkVersion.java`'s Javadoc for future contributors. ### 3. "Once per session" is what the issue specifies, not an addition Both the description and both relevant AC bullets say "per session" explicitly: `logs a single console error per session` / `logs a single console warning per session`. Implemented via two module-level boolean flags. Note the practical implication: "per session" server-side means per Node.js process lifetime (a long-running production server might log this once across days/weeks), not per page reload — which matches the issue's own stated intent of this being a **developer-console early-warning system** (Tracks updates dev before prod, so the signal reaches a developer's local console before a production rollout), not a continuous ops-monitoring signal. ### 4. Baseline `MIN_SDK_VERSION` value: `"0.0.0"` No breaking change has been introduced under this mechanism yet, so the correct, honest starting value is "no SDK is too old" — `0.0.0` is lower than every real published version, so nothing is flagged as incompatible until the first real bump happens. ## Testing performed - **Unit tests:** `sdk-compatibility.spec.ts` — 11/11 passing, 100% line coverage. Full `sdk-client` suite (298 tests) still green after the change, including the pre-existing `fetch-http-client.spec.ts`. - **Lint:** clean (`nx lint sdk-client`). - **Real Rollup build:** ran `nx build sdk-client` and inspected the actual compiled `dist/libs/sdk/client/index.cjs.js` — confirmed `SDK_VERSION` is correctly inlined (matching `package.json`'s version exactly), no leftover unresolved `virtual:sdk-version` import, and the compatibility-check internals are not leaked into the public `.d.ts` API surface. - **Backend compile:** `./mvnw compile -pl :dotcms-core -DskipTests` → `BUILD SUCCESS`, zero warnings attributable to the new/changed files. - **Manual end-to-end test**, real local dotCMS instance (`just build && just dev-run`) + a real Next.js example app, packing `@dotcms/client` with `npm pack` and installing the `.tgz`: - Confirmed via `curl` that both headers appear on JAX-RS responses (`/api/v1/appconfiguration`). - **Found and fixed a real gap** this way: `curl` against `/api/v1/graphql` (the actual endpoint the Next.js example's page load hits) was missing both headers with the original JAX-RS-filter implementation — this is what led to discovering the GraphQL-is-a-plain-servlet issue and switching to `SdkVersionWebInterceptor` (see "Why a WebInterceptor" above). Re-verified via `curl` that the GraphQL endpoint returns both headers correctly after the fix. - Verified the outdated-SDK console error fires correctly and matches the expected message format, using a real packed/installed SDK build with a temporarily lowered `MIN_SDK_VERSION` floor to force the mismatch. - Confirmed the once-per-session throttle behaves as designed (logged once across two consecutive requests in the same server process). ## Out of scope / follow-ups - SDK documentation page update describing the two headers and compatibility behavior (issue AC item — not part of this PR, should be tracked as a fast-follow doc task). - `examples/*` pinning to exact versions instead of ranges — separate, pre-existing follow-up from ADR-0019 (Implementation Note 6), unrelated to this change. - LTS-specific `MIN_SDK_VERSION` policy — not addressed here; LTS SDK publishing itself is still an open follow-up from dotCMS#36587. ## Acceptance criteria - [x] Server: `MIN_SDK_VERSION` exists as release metadata with a documented bump procedure (documented in `MinSdkVersion.java`'s Javadoc) - [x] Server: all REST/GraphQL responses include `X-DotCMS-Version` and `X-DotCMS-Min-SDK` headers (verified via `curl` against both a JAX-RS resource and the GraphQL servlet) - [x] Server: both headers are exposed via `Access-Control-Expose-Headers` (already defaulted to `*`, verified in `dotmarketing-config.properties`) - [x] SDK: own version available at runtime via build-time injection, matching the published npm version (verified in the actual compiled bundle) - [x] SDK: console error once per session when `ownVersion < X-DotCMS-Min-SDK` - [x] SDK: console warning once per session when `ownVersion > X-DotCMS-Version` - [x] SDK: numeric calver comparison; fails open (no warning, no behavior change) when headers are absent or unparsable - [ ] Docs: compatibility behavior and the two headers documented on the SDK documentation page — **not done in this PR**, tracked as a follow-up This PR fixes: dotCMS#36609 --------- Co-authored-by: Kevin <kfariid@gmail.com>
This PR introduces the changes to align the SDK release with the dotCMS release process
Already have the same code in core-workflow-test and the tests passed
dotCMS#514
dotCMS#516
This PR fixes: #36587
This PR fixes: #36587