Repository navigation
Conversation
tlgimenes
marked this pull request as ready for review
October 18, 2026 13:20
… loaders, views and image URLs wired (#706) Rebased onto main as one commit per PR (PO, 2026-10-18); the previous history is kept on backup/migrator-compat-imports-2026-10-18. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu
tlgimenes
force-pushed
the
feat/compat-v7-commerce-bindings
branch
from
October 18, 2026 18:55
18e9a8b to
6d85e67
Compare
tlgimenes
force-pushed
the
feat/migrator-compat-imports
branch
from
October 18, 2026 18:55
ceba138 to
a99a734
Compare
Contributor
Author
|
Superseded by #722 so the stack lands as one commit and one 8.4.0 release (PO, 2026-10-18). |
tlgimenes
added a commit
that referenced
this pull request
Oct 18, 2026
…cocms/compat-v7, /preview, lockstep release and the migrator (#722) This PR replaces the 11 stacked PRs #695 … #707 so the rest of the v8 framework packages land on `main` as **one commit and one release, 8.4.0**, publishing all 11 packages together: `@decocms/blocks`, `@decocms/tanstack`, `@decocms/nextjs`, `@decocms/compat-v7` and the seven `@decocms/apps-*`. Its branch is #707's (`feat/migrator-invoke-scaffold`), already rebased onto `main` with one commit per former PR and their trees unchanged, plus one commit that raises every `@decocms/blocks` peer floor to `^8.4.0`. #693 and #694 are already on `main` and shipped in 8.3.0 (`NODE_ENV` dev default, `deco schema --check`, first-match multivariate, the platform-neutral `/image` with `@decocms/apps-vtex/image` and `@decocms/apps-shopify/image`). ## What lands ### #695 — `@decocms/blocks/preview`: the draft badge and the Studio bridge - `DraftBadge({ pointer, exitHref? })`: v7's "Preview mode" pill, unchanged on screen. Renders nothing without a pointer, on the server and inside a frame. The inlined Deco mark is now a 36px PNG (~2KB, v7 inlined ~22KB). - `StudioBridge({ site, trustedOrigins, editShortcut?, nonce? })`: v7's `LiveControls` as one static inline script, so it runs before hydration and a Server Component can render it. `trustedOrigins` is required; it answers only the parent frame from those origins. - `@decocms/blocks` exports no Studio origins and no draft names: the bindings own them. - PO decision 4: the "." / Ctrl+Shift+E edit shortcut is on by default, as in v7; `editShortcut={false}` turns it off. ### #696 — `@decocms/tanstack` for v8 - For TanStack Start on Cloudflare Workers, depending only on `@decocms/blocks`. Subpaths: root (draft names, `draftResponseHeaders`, `STUDIO_ORIGINS`), `/worker` (`createDecoWorker`), `/server`, `/start`, `/router`, `/react` (`DecoDocument`, `NavigationProgress`, `StableOutlet`), `/vite` (`decoTanstack`), `/scope`. - `createDecoWorker`: Studio framing, v7's draft host allowlist (D1), CMS redirects, the edge cache (build + revision + device + segment key, SWR/SIE, never storing private, signed-in or degraded responses), the `__cseg` server-function cache check, Set-Cookie dedup, immutable assets. `onCache` is the only metrics hook (D3); build hash from CI's sha (D4). - Owns the draft names, headers and Studio origins that #693 had put in blocks (PO review of #693); values unchanged. ### #697 — `@decocms/nextjs` for v8 - Helpers for any Next.js App Router app, no hosting assumptions, depending only on `@decocms/blocks`: `/proxy` (`decideDraft`, `draftProxy`, …), `/server` (`bindDraft`, `contentClient`), root (`DraftPreviewIndicator`, `DraftBadge`, `StudioBridge` with Studio's origins), `/config` (`withDeco`), `/image-loader` (`createDecoImageLoader`, platform-neutral). - PO decision 1: `isNextDev` is gone; `createCMS` reads `NODE_ENV`. PO decision 4: the edit shortcut is on by default. Its own copy of the draft names and Studio origins (PO review of #693). Image golden test: 1,212 cases, only the 60 of PO-approved fix 2 (#694) change. ### #699 — lockstep release - `.releaserc.json` publishes every package in `packages/` at one version on `latest` from `main`, blocks first, then the bindings. - Every package that builds on blocks takes it as a caret **peer** (now `^8.4.0`) with `workspace:*` only in `devDependencies`, so a site installs one blocks copy. - `scripts/sync-versions.mjs` writes the version and refuses a plain in-repo dependency or a peer floor above the release. `tests/release.test.ts` checks the allowlist, the ranges, every export's shape and what each tarball ships; CI runs it on the built dist. ### #700 — the migrator and skill use the bindings - The v7-to-v8 script and the `deco-v7-to-v8-migration` skill move sites onto `@decocms/tanstack` / `@decocms/nextjs` instead of copying v7's worker, draft, bridge and image code. - `@decocms/tanstack/react` gets v7's `Image`, `Picture`, `Source` (same props and markup, URLs from `ImageUrlProvider`); 160 golden markup cases, zero changed. - PO decisions 1, 2 and 5: no dev flag; the script adds `deco schema --check` to the site's CI; `decoCdn` strips only a leading asset host and the report lists every URL that changes. ### #702 — `@decocms/compat-v7`: types, device, scripts, clx - The PO-approved home (2026-10-14) for v7's **pure** helpers on v8, so migrated sites stop keeping copies. No v7 runtime (`ctx`, invoke, request scope), no env reads, nothing added to blocks. - `/types` (`SectionProps` = the loader's awaited return, per the PO), `/device`, `/script`, `/clx`. v7's own files are hash-pinned fixtures and every helper is compared against them over seeded inputs. ### #703 — `@decocms/compat-v7/matchers` - v7's website matchers with v7's results; each takes the request as its second argument. `random` is latest 7.x's sticky random on the `deco_segment` cookie (PO decision), keyed by the saved block's name (`cohort`). ### #704 — `@decocms/compat-v7/sections` - `runSectionLoaders` / `runSectionLoader`: v7's `(props, req)` section loaders after a page resolves, with v7's nested walk (deep, or `nested: "direct"` for 7.77.12 and earlier), `__pageUrl`/`__pagePath`, and the `degraded` list. v7's mixins. Reading `ctx` throws with what to use instead. ### #705 — `@decocms/compat-v7` `/commerce`, `/tanstack`, `/nextjs` - `/commerce/*`: v7's `@decocms/apps-commerce` pure files under the same layout (AST-equal to v7, 5,000 generated inputs each). - `/tanstack` and `/nextjs`: `matcherContext`, `commitSegmentCookie`, `cohortSegment`, `requestDevice`, `DeviceProvider`. - `/tanstack/worker` (PO decision 6): v7's `withABTesting` legacy-origin split as a `createDecoWorker` `routes` handler, matched to v7 over 3,000 seeded requests. PO decision 3 checked against the compat random. ### #706 — the migrator imports `@decocms/compat-v7` - Rewrites v7 helper and `@decocms/apps-commerce` imports onto compat-v7, rewrites `ctx.device`, turns `registerSectionLoaders` into `sectionLoaders` and wires `loadSections`, registers the matchers the content uses and writes the image URLs module. - PO decision 8: every section's own `loader` runs, and each one v7 never ran is reported. PO decision 9: blocks are registered under their v7 names only. ### #707 — the migrator writes invoke, app clients, the page setup and stubs - Each `/deco/invoke` key becomes one server function (TanStack Start `GET` loaders marked private and `POST` actions; Next.js `"use server"`), imported at each call site. - PO decision 7: VTEX invoke-map keys need an explicit `--vtex-invoke keep|v8` choice, no default. - `deco-vtex`/`-shopify`/`-wake`/`-resend` app blocks become `src/clients/<app>.ts`; `src/migration-stubs.ts` holds deprecated stubs for v7 runtime with no port; the page setup (`src/cms.ts`, `open-page`, the catch-all route) is written per binding. ### This PR's own commit — peer floors `^8.4.0` The bindings and compat-v7 use blocks APIs that 8.3.0 doesn't have (`@decocms/blocks/preview` and the rest of this stack), so every `@decocms/blocks` peer (and compat-v7's optional binding peers) is `^8.4.0`. `tests/release.test.ts` syncs at 8.4.0 and 8.5.1 and refuses 8.3.x; the migrator writes `^8.4.0`; CLAUDE.md, the README and the skill name 8.4 as the floor. ## Checks - **Release dry run** in a throwaway clone of a mirror with the tags (deleted afterwards; nothing pushed to GitHub or published): `main` + this branch squashed into one commit with this PR's title. `semantic-release --dry-run`: "Found git tag blocks-v8.3.0 … The next release version is 8.4.0". The prepare step (`VERSION=8.4.0 node scripts/sync-versions.mjs && bun run build`): 11 manifests at 8.4.0, every peer `^8.4.0`, all builds exit 0. `npm publish --dry-run --tag latest` for all 11: `@decocms/blocks@8.4.0` … `@decocms/apps-wake@8.4.0`, each "with tag latest". The 11 tarballs installed together: `npm ls @decocms/blocks` shows one `8.4.0`, every other entry deduped; no required peer is unmet. - **Gates at the top:** `bun run check`, `bun run build`, `skills:check`, `skills:readme:check`, `bun test tests/release.test.ts` (42 pass), the compat-v7 tests (13 files, 153 pass), and `bun run test` under a 2 GB cap: 122 files pass, 1 skipped; 2,746 tests pass, 95 skipped, 0 fail. ## Landing 1. Approve, then **squash** with this PR's `feat:` title. The squash message is the PR body (repo setting), so this body is what commit-analyzer reads: one `feat:`, a minor release. 2. The release publishes **8.4.0 on `latest`** for all 11 packages. 3. **Before landing**, check that npm Trusted Publishing for `@decocms/tanstack`, `@decocms/nextjs` and `@decocms/compat-v7` accepts `main`'s `.github/workflows/release.yml`. The bindings were last published from the 7.x branch: npm's trusted publisher names the repo and workflow file, not a branch, so `main` should be accepted, but if either is tied to a GitHub environment, add it for `main` first. compat-v7's trust config already exists and its `0.0.0` placeholder is published. Once this lands, the 11 stacked PRs can be closed. ## npm commands for the PO after the release (not run) ```bash # 1. Remove the stale `next` dist-tag (8.1.0) from blocks and every apps-* that has it (all seven do today). npm dist-tag rm @decocms/blocks next npm dist-tag rm @decocms/apps-algolia next npm dist-tag rm @decocms/apps-magento next npm dist-tag rm @decocms/apps-resend next npm dist-tag rm @decocms/apps-sfmc-personalization next npm dist-tag rm @decocms/apps-shopify next npm dist-tag rm @decocms/apps-vtex next npm dist-tag rm @decocms/apps-wake next # 2. Rewrite the 8.0.0 deprecation (it says "use @latest (7.x)", wrong once latest is 8.4.0). npm deprecate @decocms/tanstack@8.0.0 "Published by mistake. Use ^8.4.0 for v8, or 7.x for v7." npm deprecate @decocms/nextjs@8.0.0 "Published by mistake. Use ^8.4.0 for v8, or 7.x for v7." # 3. Deprecate the two prereleases (not deprecated today). npm deprecate @decocms/tanstack@"8.0.0-next.0 || 8.1.0-next.0" "Prerelease. Use ^8.4.0 for v8, or 7.x for v7." npm deprecate @decocms/nextjs@"8.0.0-next.0 || 8.1.0-next.0" "Prerelease. Use ^8.4.0 for v8, or 7.x for v7." ``` No `latest-7` command is needed: #698 already moved `latest-7` to 7.80.2 for both bindings. `latest` moves to 8.4.0 by itself when the release publishes. Checks afterwards: ```bash npm view @decocms/blocks dist-tags # { latest: '8.4.0', 'latest-7': '7.80.2' }, no next npm view @decocms/tanstack dist-tags # { latest: '8.4.0', 'latest-7': '7.80.2' } npm view @decocms/nextjs dist-tags # { latest: '8.4.0', 'latest-7': '7.80.2' } npm view @decocms/compat-v7 dist-tags # { latest: '8.4.0' } for p in algolia magento resend sfmc-personalization shopify vtex wake; do npm view @decocms/apps-$p dist-tags; done # latest: '8.4.0', no next npm view @decocms/tanstack@8.0.0 deprecated npm view @decocms/nextjs@8.1.0-next.0 deprecated npm view @decocms/tanstack@8.4.0 peerDependencies # @decocms/blocks: '^8.4.0' ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Replaces PRs #695–#707 so the v8 framework packages land as one commit and one release: 8.4.0 publishes all 11 packages together (`@decocms/blocks`, `@decocms/tanstack`, `@decocms/nextjs`, `@decocms/compat-v7`, and the seven `@decocms/apps-*`). The v7-to-v8 migrator and its skill now move sites onto these bindings instead of copying v7's worker, draft, bridge, and image code into the site. **What ships** - `@decocms/blocks/preview`: the draft badge and Studio bridge, as one static inline script that runs before hydration. - `@decocms/tanstack`: the TanStack Start binding on Cloudflare Workers — drafts, edge cache, CMS redirects, draft names and Studio origins. - `@decocms/nextjs`: the Next.js App Router binding — draft proxy, content client, image loader, Studio bridge, without hosting assumptions. - `@decocms/compat-v7`: v7's pure helpers (types, device, scripts, clx, matchers, section loaders, commerce files) with v7's output, so migrated sites stop keeping their own copies. - The migrator rewrites `/deco/invoke` calls into server functions, writes the page setup per binding, registers v7 matchers and section loaders, and reports everything left for a person. **Release and migration** - All packages release in lockstep at one version on `latest`; every package that builds on blocks takes it as a `^8.4.0` caret peer, so a site installs one blocks copy. - Existing v7 sites stay on `latest-7`; `next` dist-tags and misplaced `8.0.0`/prerelease deprecations must be cleaned up after release (commands in the PR body). <sup>Written for commit 6195fe5. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/decocms/blocks/pull/722?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="View guided diff" src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a> <a href="https://www.cubic.dev/action/auto-fix/pr/decocms/blocks/722?returnTo=https%3A%2F%2Fgithub.com%2Fdecocms%2Fblocks%2Fpull%2F722&source=description" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img alt="Turn on auto-fix" src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Landed in #722 (squashed as one commit and one 8.4.0 release). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The v7-to-v8 migration script now uses
@decocms/compat-v7. Before this change, its report told sites to copy v7's helpers into their own code; now the script rewrites the imports and generates the wiring. It's stacked on #705.What the script does now
Imports onto
@decocms/compat-v7(scripts/compat.ts, merged intoMOVES):@decocms/apps-commerce/<subpath>becomes@decocms/compat-v7/commerce/<subpath>: the same files, in any import form, including inside vendored loaders. A stray 6.x@decocms/apps/commerce/<subpath>gets the same rewrite. v7's app runtime (app-types,registry,resolve,manifest-utils) is reported, not moved.clx,useScript/useScriptAsDataURI/inlineScript,detectDeviceand its siblings,SectionProps/Resolved/LoadingFallbackProps/Flag, the widget types, and the mixins (compose,withDevice,withMobile,withSearchParam,withSectionLoader) each move to their compat subpath. A declaration that mixes moved and unmoved names is split.useDevice,DeviceProviderandDeviceContextmove to/tanstackor/nextjs, by binding. The report says how to seed the provider withrequestDevice().AppContext,useSection,registerLayoutSections, …) are reported with what to do instead.ctx.device: in a v7 loader(props, req, ctx)it becomesdetectDevice(req.headers.get("user-agent") ?? ""). That is what v7'sbuildSectionLoaderContextcomputed. Any otherctx.*read is reported: it throws in v8, and its section keeps its saved props.Section loaders (
scripts/sectionLoaders.ts):registerSectionLoaders({ … })becomesexport const sectionLoaders, andregisterNonCriticalSections([…])becomesexport const nonCriticalSections..deco/index.tsexportsloadSections(sections, request, url?)overrunSectionLoaders. It usesnested: "direct"for sites on 7.77.12 or earlier, and the default deep walk otherwise.loader(PO decision 8, 2026-10-15): the registered map wins where it lists a section,loadSectionsexists even when the site registered nothing, and each loader v7 never ran is reported by name as an approved difference to list in the PR. A loader typed with v7'sctxis wrapped, since the runner calls(props, request).Matchers (
scripts/matchers.ts):@decocms/compat-v7/matchers: device, random, cookie, host, pathname, queryString, location and multi.matcherContext(). On TanStack, the generatedmatchPage(href)tells the matchers which page is opening during a client-side navigation.environmentis reported.cohort(the key v7's sticky random used indeco_segment), only on sites where the matcher is registered, sodeco checkstays green elsewhere.commitSegmentCookie()and passcohortSegmentto the worker on TanStack, orpageUrlRequestHeadersand<SegmentCookie />on Next.js.Block names: each block is registered under its v7 names only, with no short name, so the site editor's pickers list each block once (PO decision 9).
Views: a descriptor's
componentis now the section's v7 type name (site/sections/….tsx), the key v7's loader map uses.src/views.tsmaps each name to its module. An existingsrc/views.tsis kept.Images: on TanStack, the script writes
src/imageUrl.tswith v7's URLs. These come from the release's rules, or from the site's own copy of v7's image helper, plus the domain and quality it registers. The script also putsImageUrlProviderintocreateDecoRouter'sWrap. It reports the cases it can't handle: a non-literal registered value, an unreadable copy, aWrapit can't read, or a.tsrouter.src/imageUrl.tsnever setsstripAssetHosts(decoCdn's"leading"default, PO decision 5); for a site whose v7Imagestripped the asset hosts anywhere, the file's comment names the approved difference and the report's "image URLs" line counts the URLs it changes.A/B split: a v7
withABTestingimport is reported withabTestingRoutesfrom@decocms/compat-v7/tanstack/worker. It isn't rewritten, because a wrapper becomes a routes handler.From the lower branches:
deco schema --checkin the site's CI, and nodev/isNextDev(decisions 1 and 2, from #700).Docs:
SKILL.md,import-map.md,script-steps.md,manual-steps.md,framework-code.mdandCLAUDE.mddescribe the new behaviour.knip.jsonlets the skill's tests import compat-v7, as it already does for the other packages.Tests
compat.test.ts(new): 21 tests, at least one per codemod.exports.ctx.device: the rewrite, aliasing, what is left alone and what is reported. A check runs the generated expression againstdetectDevice, as v7 did.loadSectionswith no registration.cohort,views.ts, andWrap(inline, added, unreadable,.ts).bindings.test.ts: migrates a v7 TanStack Start fixture and a v7 Next.js one (an A/B test, a device rule, every helper, a registeredwithSectionLoader). Then it type-checks everysrc/file plus.deco/index.tsandsrc/views.tsagainst the repo's packages.migrate.test.ts: imports the migrated block map and runsloadSectionson the migrated page. The result: v7'scompose(withDevice(), withSearchParam())output, and actx.deviceloader throughwithSectionLoader.imageCopy.test.ts: for each 7.xImageexcerpt,releaseRules(version)gives the samedecoCdnoptions as the excerpt's own code, under five registered domain/quality pairs.src/imageUrl.tsis checked from a copy and from a release.src/imageUrl.tsequals the hand-migrated site's.Gates
bun run checkbun run buildbun run test(2 GB cap, at #707's head)schema.test.tstimeout passes alone (68/69 with DS-2)skills:checkandskills:readme:checkdeco --helplists--checkNot in this PR
matchPage,loadSections,commitSegmentCookie): the report says what to call where.onBeforeResolvePropssection convention./nextpages still say to copy these helpers into the site. A docs PR should follow.Main refresh (2026-10-17,
ceba1386)18e9a8bbinto this branch (normal merge, no conflicts), then merged up the stack to M2 feat(migrator): invoke as server functions, app blocks as client code, the page setup, and stubs for v7 runtime #707.5131a3c1):bun run check,bun run build,skills:check,skills:readme:checkpass.bun run test(2 GB memory cap): 122 files pass, 1 skipped; 2,745 tests pass, 95 skipped, 0 fail. DS-2 now passes (fixed on main by test(conformance): DS-2 allows the discard's unstage, still bans commits and remote git #709); no timeouts this run.🤖 Generated with Claude Code
https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu
Summary by cubic
The v7-to-v8 migration script now rewrites v7 helpers, fixtures, and wiring onto
@decocms/compat-v7instead of telling sites to copy them in.clx,useScript,detectDevice, widget types, section-loader mixins) to@decocms/compat-v7; names with no port are reported instead.ctx.devicetodetectDevice(req.headers.get("user-agent") ?? ""); any otherctx.*read is reported since it throws in v8.registerSectionLoadersandregisterNonCriticalSectionsinto exports that.deco/index.tsruns vialoadSections, along with every section's ownloader(v7 only ran registered ones). The registered map wins where it lists a section; each loader v7 never ran is reported as an approved difference.cohortsodeco_segmentgroups carry over.src/views.tsmaps them (an existing one is kept).src/imageUrl.tswith v7's URLs and putsImageUrlProviderintocreateDecoRouter'sWrap; unwritable cases are reported.src/imageUrl.tsnever setsstripAssetHosts; sites whose v7Imagestripped asset hosts get a comment naming the difference and a report line counting the URLs changed.seoas the block map'sPageSeo, v7's shape, so existing SEO blocks register and passdeco check; the SEO hint references that same type.withABTestingimport withabTestingRoutes(@decocms/compat-v7/tanstack/worker) instead of rewriting it, since a wrapper becomes a routes handler.onBeforeResolvePropsconvention are still left to the report.New
compat.test.tscovers each codemod, with the entry list pinned to compat-v7'sexports.bindings.test.tsmigrates and type-checks both a TanStack and a Next.js fixture;migrate.test.tsruns the migratedloadSectionsend to end. The image release rules forsrc/imageUrl.tsare checked against each 7.xImageexcerpt inimageCopy.test.ts.Written for commit a99a734. Summary will update on new commits.