Skip to content

CaaS auto-publish: extract payload builder into leaf module + standalone bundle#6227

Open
shkhan91 wants to merge 5 commits into
adobecom:stagefrom
shkhan91:da-caas-autopublish
Open

CaaS auto-publish: extract payload builder into leaf module + standalone bundle#6227
shkhan91 wants to merge 5 commits into
adobecom:stagefrom
shkhan91:da-caas-autopublish

Conversation

@shkhan91

@shkhan91 shkhan91 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What & why

Milo is static-only (AEM Edge Delivery) and cannot host a backend endpoint, but the CaaS card→XDM business logic must be reusable from a backend service (milo-caas) that auto-publishes pages to CaaS on a schedule. This PR extracts that logic into a self-contained leaf module and produces a small standalone bundle that milo-caas fetches (pinned by SHA-256) and runs in a vm sandbox — keeping a single source of truth here in Milo.

This PR is library/tooling only — it does not change any page rendering and is behavior-preserving for the existing browser "Send to CaaS" / bulk-publish paths.

  • Add buildCaasXdmPayload() (plus hasCardMetadata / isDisabledOnPage) in a new self-contained leaf tools/send-to-caas/caas-payload-core.js that does not import libs/utils/utils.js or libs/blocks/caas/utils.js (those transitively drag in Milo's bootstrap, a 706KB tag taxonomy, and lazy chunks — which makes the module impossible to bundle standalone). The only libs/ import is the leaf-safe getUuid.js.
  • send-utils.js now re-exports the payload surface from the leaf and keeps only the browser-only pieces (postDataToCaaS, isPagePublished). It injects the 706KB caas-tags fallback lazily via setConfig({ getCaasTagsFallback }), so the leaf stays free of it and the browser never eagerly downloads it when the network tag fetch succeeds.
  • Refactor caasAutoPublish() to call buildCaasXdmPayload() (removes the duplicated inline setConfig → loadCaasTags → getCardMetadata → getCaasProps sequence). No behavior change.
  • Add build/caas-payload-builder.js entry + build:caas-payload-builder npm script (microbundle UMD, --external none, mirroring the existing build:gnav-profile pattern) producing a single ~20KB committed bundle at tools/send-to-caas/caas-payload-builder.js, served from milo.adobe.com.

Companion PR

This is Part 1 of 2. The backend service that consumes the bundle lives in milo-caas:

Together: milo-caas polls the DA/Franklin admin log per tenant → fetches the page HTML → runs this bundle to build the XDM payload → posts to Chimera (preview → CaaS stage, publish → CaaS prod).

Design notes for reviewers

  • Why a leaf + bundle instead of importing send-utils.js directly? A standalone import of send-utils.js pulls in utils.js (2850 lines, top-level window/document side effects) and its lazy graph (e.g. preflight-notification ~170KB). The leaf isolates the pure payload logic so the bundle is ~20KB and vm-safe.
  • Why is the caas-tags fallback injected rather than imported? microbundle UMD cannot code-split, so a dynamic import breaks the single-file build, while a static import would bloat the bundle to ~404KB and eager-load 706KB in the browser. Injecting it keeps the bundle tiny and the browser lazy. The leaf reaches the fallback only when the network tag fetch fails.
  • Card identity: contentId/entityId/url derive from the prodUrl string — unchanged here, but this is why the consuming service applies per-tenant .html rules.

Testing

  • tools/send-to-caas + blocks/caas unit tests: 271 passing (auto-publish, bulk-publish-to-caas, send-to-caas, caas/utils). No regressions.
  • Bundle verified as a single 20KB UMD file with no code-split chunks; smoke-tested end-to-end from the milo-caas vm+linkedom loader against the real bundle (tags resolve, caasProps correct, .html drives contentId/url).

Resolves: MWPW-117684

Test URLs:

Shahbaz Khan and others added 3 commits June 24, 2026 14:36
Introduces tools/send-to-caas/auto-publish.js, a new library that hosts
(da-live editor, milo bulk-publish-v2) call after a successful Helix
preview/publish to push the page into CaaS via the existing milo-caas
gateway. Replaces an earlier approach that polled Helix logs externally
and managed its own auth — that design was operationally fragile.
Hooking next to the publish primitive keeps the user's IMS token in
scope, removes a separate auth path, and surfaces failures immediately
in the host's UI.

Design

  - Data-driven config at /.milo/caas/config.json per site, fetched via
    the existing getCustomConfig helper. Mirrors the URL-pattern shape
    of /.milo/publish-permissions-config.json: '**' suffix matches
    descendants, exact match otherwise; longest pattern wins.

  - Reuses send-utils.js unchanged for HTML -> XDM transformation. No
    changes to the existing CaaS publishing logic.

  - Per-page controls: pages without a .card-metadata block are skipped
    silently; an explicit "auto-publish | false" row in card-metadata
    opts a single page out without touching site config.

  - Default routing: preview -> [prod+draft, stage+live]; publish ->
    [prod+live]. A rule's `targets` array overrides defaults.

  - Never throws. Returns { skipped, reason } or { skipped:false,
    results } or { skipped:false, error }. CaaS publishing is best-
    effort and must not block the underlying Helix publish.

  - Per-target failures are isolated; one CaaS env failing does not
    affect the others.

Scope

Pure additive change — no existing files modified. Integration into
bulk-publish-v2/services.js and da-live's saveToAem() will land in
follow-up PRs once this library is reviewed.

Testing

35 tests in test/tools/send-to-caas/auto-publish.test.js cover:
  - matchesUrl, resolveRule, resolveTargets, isDisabledOnPage as
    pure functions
  - every gating skip reason (unsupported action, missing args,
    no config, no matching rule, rule disabled, page fetch error,
    no card-metadata, page-level disable override)
  - posting paths: 1 call on publish, 2 parallel calls on preview,
    rule.targets overriding defaults, per-target failure isolation
  - never-throws contract: getCustomConfig rejection, getAuthToken
    throwing, both surface as structured errors

All 45 tests in test/tools/send-to-caas/ pass; 25/25 bulk-publish-v2
tests pass; no regressions.
Wires the new caasAutoPublish library into bulk-publish-v2 so that bulk
preview/publish jobs automatically index successful URLs into CaaS.
Mirrors how the same library will be invoked from da-live's single-page
publish path: hook next to the existing Helix admin call, reuse the
user's IMS token, never block the underlying publish.

What was added

  - libs/blocks/bulk-publish-v2/auto-publish-hook.js
    A small orchestrator (runAutoPublishForJob) that consumes a Helix
    job result, filters to successful resources on supported topics
    (preview/publish only), checks site opt-in, fetches the IMS token
    once, then invokes caasAutoPublish for each path in parallel. All
    side-effects are isolated through dependency injection (publish,
    getToken, optedIn) which makes the hook fully unit-testable
    without touching the real IMS or network.

  - libs/blocks/bulk-publish-v2/components/job-process.js
    A 10-line addition in updated(): once per job (guarded by a
    caasAutoPublishFired boolean against Lit re-renders), fire the
    hook and swallow any error. The bulk-publish UI never waits on,
    observes, or surfaces CaaS results — failure cannot affect
    existing functionality.

Failure isolation (three layers)

  - Per-target inside caasAutoPublish (postDataToCaaS rejection
    becomes ok: false on that target, others still post).
  - Per-resource inside runAutoPublishForJob (one path throwing is
    captured as an entry in the result array).
  - Top-level .catch(() => {}) in job-process.js (any unexpected
    error is silently dropped).

Lazy IMS load

The hook short-circuits before fetching IMS by checking
/.milo/caas/config.json via the existing cached getCustomConfig
helper. Sites that have not opted in never load the IMS library;
this also keeps existing bulk-publish-v2 tests free of the
"external script in tests" warning.

Mock fetch hardening (independent improvement)

test/blocks/bulk-publish-v2/mocks/fetch.js previously threw a
TypeError when a request URL was not in the fixture list (it
destructured an undefined response). Replaced with a graceful 404
response. Necessary for the new background fetches the auto-publish
hook makes, and a defensive improvement on its own — additions to
existing components no longer require updating the mock fixture
just to keep tests passing.

Scope discipline

  - send-utils.js intentionally untouched. The library imports
    setConfig/getCardMetadata/getCaasProps/postDataToCaaS/
    loadCaasTags as-is; modifying that file would be churn.
  - bulk-publish-v2/services.js intentionally untouched. The hook
    lives one level up, in the consumer (job-process.js), so the
    services layer keeps a single responsibility.
  - Only preview/publish topics fire the hook. unpublish/delete
    CaaS removal is a future PR.

Testing

13 new tests in test/blocks/bulk-publish-v2/auto-publish-hook.test.js
cover: collectSuccessfulPaths variants (missing/malformed jobStatus,
mixed status codes, webPath/path fallback), all gating skip paths
(missing origin/topic, unsupported topic, no successful paths,
not opted in, missing token, throwing token), happy paths
(per-resource fanout with correct args for preview and publish),
per-resource error isolation, defensive parsing of malformed
origins, and verification that getToken is not invoked when the
site is not opted in.

All 85 tests pass: 45 in test/tools/send-to-caas/ (existing 10 plus
35 from the auto-publish library), and 38 in test/blocks/
bulk-publish-v2/ (existing 25 plus 13 from the new hook). No
regression in existing bulk-publish-v2 tests.
- Add buildCaasXdmPayload() + hasCardMetadata/isDisabledOnPage in a new
  self-contained leaf (tools/send-to-caas/caas-payload-core.js) that does
  not import utils.js/caas-utils.js, so it can be bundled standalone.
- send-utils.js re-exports the payload surface and keeps the browser-only
  postDataToCaaS/isPagePublished; injects the lazy 706KB caas-tags fallback
  so the leaf stays free of it.
- Refactor caasAutoPublish() to use buildCaasXdmPayload().
- Add build/caas-payload-builder.js entry + build:caas-payload-builder
  microbundle script producing a single ~20KB UMD bundle for milo-caas to
  fetch and run.

271 CaaS unit tests pass.
@shkhan91 shkhan91 requested a review from a team as a code owner June 25, 2026 01:32
The bundle is a minified build artifact (like libs/deps/*); add it to
eslint ignorePatterns so lint-changed-files does not flag it.
@shkhan91 shkhan91 added the CaaS CaaS related code label Jun 25, 2026
- Only pages carrying a caas:content-type tag auto-publish (matches the
  original AEM behavior); core pages without one are a clean skip, not an
  error. Adds hasContentTypeTag() and applies it in caasAutoPublish().
- Add getCaasIds(prodUrl, floodgatecolor) to derive a card's entityId/
  contentId from the prodUrl alone (no DOM) for milo-caas removal.
- Export both from the leaf, send-utils, and the bundle entry; rebuild the
  standalone bundle.

Milo CaaS unit tests: 272 passing (adds a no-content-type-tag gate test).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CaaS CaaS related code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant