Skip to content

refactor(deployment): update SDK publishing workflow to align with dotCMS release strategy - #36633

Merged
KevinDavilaDotCMS merged 5 commits into
mainfrom
36587-align-sdk-publish-pipeline-with-dotcms-date-lockstep-versioning
Jul 21, 2026
Merged

refactor(deployment): update SDK publishing workflow to align with dotCMS release strategy#36633
KevinDavilaDotCMS merged 5 commits into
mainfrom
36587-align-sdk-publish-pipeline-with-dotcms-date-lockstep-versioning

Conversation

@KevinDavilaDotCMS

@KevinDavilaDotCMS KevinDavilaDotCMS commented Jul 20, 2026

Copy link
Copy Markdown
Member

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

…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.
@KevinDavilaDotCMS
KevinDavilaDotCMS requested a review from a team as a code owner July 20, 2026 05:50
@KevinDavilaDotCMS KevinDavilaDotCMS linked an issue Jul 20, 2026 that may be closed by this pull request
11 tasks
@github-actions github-actions Bot added Area : CI/CD PR changes GitHub Actions/workflows Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries labels Jul 20, 2026
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @KevinDavilaDotCMS's task in 6m 18s —— View job


Claude Code Review

Re-reviewed origin/main...HEAD. The three prior findings are all resolved and the reviewer concerns (cancel-in-progress) are already addressed in the current code. One non-blocking robustness note on the new idempotency guard.

New Issues

  • 🟡 Medium: .github/actions/core-cicd/deployment/deploy-javascript-sdk/action.yml:172 — the idempotency guard if npm view "@dotcms/${sdk}@${RELEASE_VERSION}" version >/dev/null 2>&1; then ... skip relies on npm view exit code to decide "already published". npm view on a scoped package returns a non-zero E404 only when the version is missing on some npm versions, but on others it prints nothing and exits 0 for a not-yet-published version of an existing package. If the latter behavior applies to the runner's npm, every already-existing package (i.e. all of them after the first release) would be misdetected as "already published" and silently skipped, so a real release would publish nothing while reporting success.
    • Assumption: the loop's correctness depends on npm view <scoped>@<missing-version> exiting non-zero.
    • What to verify: run npm view "@dotcms/client@99.99.99-nope" version; echo $? on the pinned Node/npm (core-web/.nvmrc). If it exits 0, gate on non-empty output instead of exit code, e.g. if [ -n "$(npm view "@dotcms/${sdk}@${RELEASE_VERSION}" version 2>/dev/null)" ]; then. That form is correct regardless of npm version. Fix this →

Resolved

  • .github/workflows/cicd_release-sdk.yml:121notify-precheck-failure now checks out (actions/checkout@v4) before referencing the local notify-slack action; the Slack notice is no longer a silent no-op.
  • .github/workflows/cicd_release-sdk.yml:88-94IS_LTS is now derived from RELEASE_VERSION on workflow_dispatch (and TAG_NAME on release), so an LTS-shaped manual dispatch is correctly skipped instead of publishing.
  • .github/actions/core-cicd/deployment/deploy-javascript-sdk/action.yml:172 — publish loop is now re-runnable: an already-published version is treated as success, so a resumed run can finish the remaining packages (modulo the exit-code caveat above).
  • .github/workflows/cicd_release-sdk.yml:42-50cancel-in-progress: false matches cicd_6-release.yml, addressing @dcolina's half-published concern.

Notes (non-blocking)

  • Removals are consistent: publish-npm-sdk-libs was dropped from both the caller (cicd_3-trunk.yml) and the definition (cicd_comp_deployment-phase.yml), and set to false in cicd_6-release.yml — no dangling reference. The deleted core-web/libs/sdk/VERSION has no remaining references. The sdk_libs filter in .github/filters.yaml is still used for PR area-labeling, so keeping it is correct.
  • @sfreudenthaler's question (why keep the deprecated cicd_manual-release-sdks.yml rather than delete): the file's own header answers it — kept disabled as a clean revert path per ADR-0019's "confirm before deleting" guidance. That's an author decision, not a code issue.

36587-align-sdk-publish-pipeline-with-dotcms-date-lockstep-versioning

@KevinDavilaDotCMS KevinDavilaDotCMS changed the title refactor(deployment): update SDK publishing workflow to align with ne… refactor(deployment): update SDK publishing workflow to align with dotCMS release strategy Jul 20, 2026
@KevinDavilaDotCMS

Copy link
Copy Markdown
Member Author

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

  • Missing checkout in notify-precheck-failure (🟠 High) — confirmed real, and on us: we added actions/checkout to the publish job when fork testing caught this exact issue there, but never propagated the same fix to notify-precheck-failure (added earlier, in the previous review round, to make a pre-check failure non-silent). It went untested because our fork tests never exercised a precheck failure path (only success, LTS-skip, and dry-run). Added the missing checkout step — same reasoning as the publish job's comment: a local (./) action can't be resolved on an empty runner workspace, so the Slack notification was a silent no-op.
  • IS_LTS never computed for workflow_dispatch (🟡 Medium) — confirmed real. The regex validating manual-dispatch versions explicitly allows the _lts_v## shape, but IS_LTS was only ever derived inside the release-event branch, defaulting to false for manual runs. A manual dispatch with an LTS-shaped version would pass validation and then actually publish, contradicting the documented skip-LTS policy. Fixed by deriving IS_LTS from whichever value carries the marker for that event type (tag_name for release, RELEASE_VERSION for workflow_dispatch) instead of only the former.
  • Publish loop not retriable on partial failure (🟡 Medium) — confirmed real, and a legitimate operational trap under the new model: the version is fixed to the dotCMS release tag and can never be bumped (unlike the old @next scheme, which side-stepped this with a fresh run-number suffix each retry). If package N of 10 failed mid-loop, packages 1..N-1 were already published immutably, and a re-run would hit npm's hard rejection on republishing an existing version — permanently stalling that release's SDK publish with no clean recovery. Made the loop idempotent: each package now checks npm view @dotcms/$sdk@$RELEASE_VERSION first and treats an already-published version as success, so a re-run (or a run resumed after cancel-in-progress killed a prior one) can finish whatever's left.

Same changes mirrored into the core-workflow-test fork branches, ready to be re-verified there before this goes back for another look.

…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.
@mbiuki
mbiuki self-requested a review July 21, 2026 00:29

@sfreudenthaler sfreudenthaler left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few considerations and open questions, but no blocking commentary.

Comment thread .github/workflows/cicd_manual-release-sdks.yml
Comment thread .github/workflows/cicd_release-sdk.yml
Comment thread .github/workflows/cicd_release-sdk.yml Outdated
- 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.
@mergify

mergify Bot commented Jul 21, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

Merged via the queue into main with commit 740b002 Jul 21, 2026
87 checks passed
@KevinDavilaDotCMS
KevinDavilaDotCMS deleted the 36587-align-sdk-publish-pipeline-with-dotcms-date-lockstep-versioning branch July 21, 2026 16:58
hassandotcms pushed a commit to hassandotcms/core that referenced this pull request Jul 29, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : CI/CD PR changes GitHub Actions/workflows Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

Align SDK publish pipeline with dotCMS date-lockstep versioning

5 participants