Repository navigation
Conversation
This was referenced Oct 15, 2026
Merged
tlgimenes
marked this pull request as ready for review
October 18, 2026 13:20
tlgimenes
added a commit
that referenced
this pull request
Oct 18, 2026
… stops at the first match (#693) ## What this PR does Three product-owner decisions (2026-10-14/15) in `@decocms/blocks`. The rest of the stack (#694 and up) merges this branch. ### 1. `createCMS` reads `NODE_ENV` again to decide dev mode (d03448c) - With no `dev` option, a `createCMS` with `site` stays on local files when `process.env.NODE_ENV === "development"`. Hosted releases are never swapped in, and drafts still load. - The read is guarded (`typeof process !== "undefined" && process.env != null && …`), so a Worker or browser without `process` counts as production. It is spelled `process.env.NODE_ENV` so a bundler's `define` can replace it. - The explicit `dev` option, which shipped in 8.0–8.2, stays as an override in both directions (`dev: false` forces swaps even when `NODE_ENV=development`). - This is the SDK's only environment read. `noEnv.test.ts` allows exactly that one line. `remoteLoader` itself still reads no environment. - Conformance: four new H-5 tests (default, override, production or unset, no `process`). Each was mutation-checked. - Removing `isNextDev` from `@decocms/nextjs`, the migrator, the skill and the smoke apps happens in #697 and #700, not here. ### 2. `deco schema --check` (fa83481) - For CI: generates the schema in memory, writes nothing, and exits 1 when `.deco/schema.gen.json` is missing or differs from what `deco schema` would write. The message says what moved: - blocks that are new in code or gone, - the number of changed definitions, - the generating version, - or formatting only. It ends with: Run `deco schema` and commit .deco/schema.gen.json. - Schema errors exit 1 with the errors. `--check` and `--watch` can't be combined, and there is no dev watch. - Usage line: `deco schema [--root <dir>] [--watch | --check]`. Seven tests in `cli/run.test.ts`, including one through the bin under plain Node. `dd-06` follows the new usage line. - The migrator adds it to each site's CI workflow in #700. ### 3. `multivariate` stops at the first matching rule, as v7 did (ac23a70) - Before this change, the resolver resolved every variant's rule before the built-in `multivariate` ran. A matcher after the winner still ran, so a random rule there drew a number and recorded a cohort. - Now the built-in `multivariate` (marked the way `lazy` is) gets its rules resolved in order, one at a time, up to the first `true` one. Later variants are left out and their matchers are never called. - A `multivariate` that the site declares in its block map replaces the built-in and still gets every rule resolved, like any other block. - Legacy multivariate types (`website/flags/multivariate.ts`, …) go through the same path. - `builtin-03/mv-02` now claims first-match order. That test changed because the docs claim changes. New tests cover in-order evaluation, a random rule after the winner never drawing, legacy types, and the block-map override. Mutation-checked: 4 fail without the change. - CLAUDE.md now says that `lazy` and the built-in `multivariate` are the resolver's special cases. ### Docs site follow-up (deco-sites/docs-tanstack, `/next/*`) - **api-reference / releases-and-drafts**: `dev` defaults to `NODE_ENV === "development"` (guarded; `false` without `process`). Pass `dev` to override it, e.g. `dev: import.meta.env.DEV` on a Worker. Remove "the SDK reads no environment variable" wherever it refers to `dev`. - **cli / checking**: `deco schema --check` (usage line, exit codes, the message) and a CI snippet that runs `deco schema --check`. - **matchers-and-variants** line 75: replace "Rules aren't lazy: they're cheap booleans and always run." with "Rules run in order and stop at the first `true` one, as in v7: a later rule's matcher never runs, so a random rule after the winner draws nothing and sets no cookie. A `multivariate` you declare in your block map gets every rule." - **how-resolution-works**: `lazy` is no longer the only special case. Add the built-in `multivariate`'s in-order rules. ### Gates - `bun run check` passes. - `bun run build` passes. - `skills:check` and `skills:readme:check` pass. - A plain-Node import of every built export works (22), and so does `deco --help`. - `bun run test` (2 GB): 2092 pass, 3 fail, 95 skipped. - DS-2 is a known failure on `main` since #689. - Two `schema.test.ts` tests hit the 5 s timeout under full-suite load. They pass alone (116/116 with noEnv, guides.cli and telemetry). ## Draft shape and block JSON: unchanged (2026-10-16) Two who-edited-what experiments came and went on this branch, and the net change is none: - A top-level `edits` field on the draft (`00125500`), reverted in `a886d720`. - A reserved `__meta` key on saved blocks that the resolver dropped (`b1cd9a13`, `36fb75f1`), reverted in the commit on top. Both reverts are merged up the stack. PO: "only have metadata and changed by on v8 hosted". Studio keeps who and when in the hosted draft object's storage metadata, so the draft stays exactly `{ set, delete }` and blocks has no reserved metadata key. The tree at M2 equals its state before these commits. https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu ### Main refresh (2026-10-17, `63b9e6d2`) - Merged main `8bd3d88c` (#709) into this branch (normal merge, no conflicts), then merged up the stack to M2 #707. - Gates at the top of the stack (M2 `5131a3c1`): `bun run check`, `bun run build`, `skills:check`, `skills:readme:check` pass. `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 #709); no timeouts this run. - CI on this PR's new head: all checks pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
tlgimenes
added a commit
that referenced
this pull request
Oct 18, 2026
…image providers in their packages (#694) Stacked on #693 (P2 of the v8 framework-packages plan). Reworked after the product owner's review: `@decocms/blocks` keeps framework code only, and nothing that knows VTEX, Shopify or another commerce platform. ## What **`@decocms/blocks/image`** (platform-neutral): pure functions, no React, no module state. - `decoCdn(options)`: the Deco image CDN (`https://<domain>/image?fit=&width=…&src=`). Options: `domain`, `quality` (tier), `fit` (default `cover`, as v7's `getSrcSet`/`Image`), `legacyHosts` (default `["decoims.com"]` on the Cloudflare CDNs), `sources` (`"deco"` default, `"all"` for v7's "everything else goes through the CDN", or a predicate), `stripAssetHosts` (`"leading"`, the default since the PO's 2026-10-15 decision: v7 from 7.71.4; or `"anywhere"`: v7 up to 7.71.3 and two storefronts' vendored helpers), `layout` (`"src-last"` or `"quality-last"`). - `createImageUrl({ providers })`: one `ImageUrl` per site, with `isOptimizable` and `hosts()`. Providers are tried in order; a provider's `format` may return `null` to leave a source to the next one. - `srcSet`, `parseSrcSet`, `preloadLink`, and the generic types (`ImageProvider`, `ImageUrl`, `ResizeOptions`, `Quality`, `Fit`, …). `srcSet` without a width writes v7's two `width=NaN … NaNw` candidates, as v7's `getSrcSet` did for an `Image` or `Source` without a width (a storefront's pages have 12 such `<source>` tags; before this, they lost their srcset). **`@decocms/apps-vtex/image`**: VTEX's URL formats as providers. They are pure helpers over the blocks types: no fetch, no React, no env reads, no runtime imports. - `vtexCatalog()`: v7's `Image` rule. VTEX hosts, `{id}-{w}-{h ?? w}`, URL re-serialised. - `vtexCatalogFrame({ frame? })`: any host, a fixed frame or the call's box, rounded. - `vtexCatalogWebp({ quality? })`: the next/image loaders' rule. `{id}-{w}-auto/{file}.webp?…&quality=` on VTEX's 1-10 scale. - `vtexFileManager({ via: "query" | "thumbor", quality? })`. **`@decocms/apps-shopify/image`**: `shopifyCdn()`, v7's rule (`width`, `height` when given, `crop=center`). A site lists the platform providers before `decoCdn`. The bindings never depend on an `apps-*` package; sites wire the helper in. v7's `registerImageCdnDomain`/`registerImageQuality` do not come back: they were module state. Their values are `decoCdn`'s `domain` and `quality`. ## Golden URLs: only the approved changes The fixtures were regenerated by running each v8 storefront's own helpers, **unchanged**: - the three vendored v7 `getOptimizedMediaUrl`/`getSrcSet` variants - two next/image loaders - a fixed-frame catalog loader The corpus grew to cover edge cases: unencoded characters, an asset host inside another URL, already-resized CDN URLs, uppercase hosts, odd id suffixes, store-domain catalog URLs, and fractional widths. The fixtures are split per package: | Package | Cases | Sources | |---|---|---| | `@decocms/blocks` | 1,508 | Deco hosts, other hosts, paths, `data:`, `""` | | `@decocms/apps-vtex` | 2,625 | catalog + file manager, composed with `decoCdn` | | `@decocms/apps-shopify` | 144 | Shopify CDN, composed with `decoCdn` | Every case reproduces today's URL byte for byte, except 192 approved `@decocms/blocks` cases: - 160 for approved change 2 below (the two already-resized CDN sources, in every config); - 32 for approved change 3 (the source `https://images.example.com/https://assets.decocache.com/…`, on the two configs that keep `decoCdn`'s default with `sources: "all"`). Each of those carries `before` (today's URL) and `reason`. The test asserts that no other case changed, and that each change-3 `before` is exactly what `stripAssetHosts: "anywhere"` still writes. The `apps-vtex` and `apps-shopify` fixtures have no changed case and still assert that none carries a `before`. Where a helper throws today, the case is recorded as `<throws>`, and the provider still throws (v7's VTEX rule on a URL that only mentions a VTEX host in its query). The fixtures use no site names. The generator stays out of the repo because it reads the sites' paths. ## Approved changes (PO, 2026-10-14 and 2026-10-15) The product owner approved 1 and 2 on 2026-10-14 ("the image fixes are good") and 3 on 2026-10-15: 1. **File-manager images on the next/image loader that still uses VTEX's `?width=&aspect=true` resizer.** That resizer has answered 500 for raster files since 2026-10-07. That store switches to `vtexFileManager({ via: "thumbor" })`, which the other loader already uses. This is a site change only; no package changes. 2. **An already-resized Deco CDN URL pasted into content** (`https://assets.decocms.com/image?…`, or the `decoims.com`, `deco-assets.edgedeco.com` and `deco-assets.decoazn.com` endpoints). Today it becomes `src=image`, or the whole `/image` URL without its query, and the CDN cannot serve either one. `decoCdn` now re-resizes it from its `src` by default (c80dd33, narrowed in 6b7e700). That covers an encoded `src`, a nested CDN URL, `http` and any host case. Resizing a result again gives the same URL. It applies only to the URLs `decoCdn` already resized (its `domain` and legacy hosts with the default `sources: "deco"`; every endpoint with `sources: "all"` or a predicate that takes it): a Deco CDN URL it passed through before, such as a `deco-assets.edgedeco.com` one under `sources: "deco"`, still passes through unchanged, because it was served fine. Other URLs are unaffected, including a non-Deco `/image` path and an `/image` URL with no `src`. A differential fuzz against the previous commit (9 configs, 10 hosts, 6 path shapes) finds 108 changed URLs, every one of them `src=image` or `src=<cdn>/image` before. 3. **`stripAssetHosts` defaults to `"leading"`** (afe430c). An asset host (`assets.decocache.com/`, the S3 bucket) is removed only where the source starts with it, as v7's `Image` does since 7.71.4. Two storefronts' vendored helpers (and v7 up to 7.71.3) removed it anywhere, so a source that only contains an asset URL (`https://other.host/https://assets.decocache.com/x.png`) used to lose that part and now keeps it. Sources that start with an asset host, which is every one the storefronts' content has in the corpus, give the same URL either way. `"anywhere"` stays an option. Follow-up in the migrator (#700): stop emitting `"anywhere"` and count and report the URLs this changes per site. ## Proposals for the PO (not decided, not in the code) 4. **A mobile app's VTEX helper copies disagree with each other.** One drops the source's query; the other leaves a pre-sized `-W-auto` id at its baked size. The app does not use these packages, so it is out of the golden suite. Proposal: point it at one helper. Nuvemshop's image helper stays in the store's own code (PO, 2026-10-14). There is no `apps-nuvemshop` package. ## Checks Decision 3 (afe430c, on top of #693's decisions 1–3 merged in d654d9a): `bun run check`, `bun run build`, `skills:check` and `skills:readme:check` pass, and plain Node imports all 24 built exports (`deco --help` runs). `bun run test` (2 GB): 2150 passed, 3 failed: DS-2 (fails on main too; fixed separately) and two `schema.test.ts` timeouts under full-suite load (58/58 alone). Image tests 48/48 across the three packages; reverting the default fails three of them. Adversarial pass (6b7e700): c80dd33 had also widened `decoCdn`'s default match to every Deco CDN endpoint, which changed 168 URLs that used to pass through and were served fine; the match is back to the pre-fix sources, and a new unit test pins those pass-throughs. `bun run check` and `bun run build` pass; image tests 27/27; the golden fixtures are unchanged (160 approved cases). Full gates at the top of the stack (#700). Fix 2 (c80dd33): `bun run check`, `bun run build`, `skills:check` and `skills:readme:check` pass. `bun run test`: 2133 passed, 2 failed: DS-2 (the known flake) and a `schema.test.ts` timeout under full-suite load, which passes 58/58 alone. The image tests pass 26/26, with 6 new fix-2 unit tests. Review pass (2026-10-14): the `srcSet` fix above (ea0a6bf). Its golden markup cases (v7's own components, no width) are in #700's `tests/tanstack-image-markup.golden.test.ts`. Full gates at the top of the stack (#700). First round (before the PO review): - `bun run check` passes. `bun run build` passes and emits `dist/v8/image.*`, `apps-vtex/dist/image.*` and `apps-shopify/dist/image.*`. Plain Node imports the built `@decocms/apps-vtex/image`. - `bun run test`: 2124 passed, 4 failed: - DS-2 in `delivery.cli.test.ts`, the known flake. - Three CLI conformance timeouts under full-suite load (`cli-02`, `qs-01/in-03`, `saved-03/saved-11`). Run alone, those files pass 179/179. - `bun run skills:check` and `bun run skills:readme:check` pass. - Bundle tests: - `@decocms/blocks/image` bundles for a browser and for workerd with no Node built-ins and no React (esbuild). - New `tests/upstream-helpers.guardrail.test.ts`: every `apps-*` helper subpath gets the same bundle check, plus a purity check (no network, React, env reads or runtime imports). Its comment records that the PO asked for helper subpaths in `apps-*`. - `CLAUDE.md` now says where platform helpers live. **For the PRs above (#696, #697, #700):** the API changed: - `vtexCatalog`/`vtexFileManager`/`shopify` are no longer in `@decocms/blocks/image`. - `parse`/`ParsedImage` and `proxyOtherHosts` are gone (use `sources: "all"`). - `@decocms/tanstack`'s default `ImageUrl` cannot include VTEX/Shopify, because it must not depend on `apps-*`. ### Main refresh (2026-10-17, `0103f85f`) - Merged P1 `63b9e6d2` into this branch (normal merge, no conflicts), then merged up the stack to M2 #707. - Gates at the top of the stack (M2 `5131a3c1`): `bun run check`, `bun run build`, `skills:check`, `skills:readme:check` pass. `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 #709); no timeouts this run. - CI on this PR's new head: all checks pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…, the page setup, and stubs for v7 runtime (#707) Rebased onto main as one commit per PR (PO, 2026-10-18); the previous history is kept on backup/migrator-invoke-scaffold-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/migrator-compat-imports
branch
from
October 18, 2026 18:55
ceba138 to
a99a734
Compare
tlgimenes
force-pushed
the
feat/migrator-invoke-scaffold
branch
from
October 18, 2026 18:56
5131a3c to
948edba
Compare
The bindings and @decocms/compat-v7 use blocks APIs that 8.3.0 doesn't have (/preview and the rest of this stack), so every package that builds on blocks now takes ^8.4.0, and compat-v7 takes the bindings at ^8.4.0 as well. The release test syncs at 8.4.0 and 8.5.1 and refuses 8.3.x; the migrator writes ^8.4.0 and the docs name 8.4 as the floor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZsmzpZ2zahRBoATBb2tBu
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.
Stacked on #706 (
feat/migrator-compat-imports). The migration script now writes what the M1 report still left as "call X here":/deco/invokeas server functions, v7's app blocks as client code, the page setup, and stubs for the v7 runtime that has no port.What the script does now
/deco/invoke→ one server function per key (scripts/invoke.ts)(props, request), as v7's endpoint did, and sends{}when there are no props.src/server/invoke.functions.ts. Loaders areGETwithmarkPrivate(), so the worker never shares them; actions arePOST.src/server/invoke.actions.ts, a"use server"file.invoke.site.loaders.spin(props)becomes$siteLoadersSpin({ data: props }), or$siteLoadersSpin(props)on Next.js.["can-spin"]), flat keys (invoke["vtex/loaders/x.ts"]),invoke(key, props)andRuntime.invoke.site/<path>resolves tosrc/<path>. An app key vendors the app's module when that module's default export is the handler.src/runtime.ts) is deleted once nothing imports it.any, as v7's invoke was. The report asks for a zod validator and a typed result for each function.VTEX invoke keys from the VTEX app's own invoke map:
--vtex-invoke keep|v8(scripts/vtexInvoke.ts, PO decision 7, 2026-10-15)setupAppsregistered every named export of the VTEX app's modules under<module>/<export>, soinvoke.vtex.actions.checkout.addItemsToCart(props)reachedaddItemsToCartinvtex/actions/checkout, which has no handler file of its own. Each migration chooses what those keys become, with an explicit flag (main.ts --vtex-invoke,migrate({ vtexInvoke })), never env. There is no default (PO: "each migration could choose this"): when the site calls such keys and no choice was given, the migrator stops before writing anything, lists the keys and both choices, and asks to run it again with one (VtexInvokeChoiceNeeded; the command line prints that message without the usage). A site that calls none needs no flag.keep: the module is vendored and the server function calls that export (import { addItemsToCart as handler0 } from "../vendor/vtex/actions/checkout"), so the call works as in v7.v8: no v7 code. The keys the v8 client has an operation for (getOrCreateCart,addItemsToCart,updateCartItems,addCouponToCart,updateOrderFormAttachment,simulateCart,getSellersByRegion) become functions over the site'ssrc/clients/vtex.tsinsrc/server/vtex-invoke.server.ts. They send the shopper's cookies and forward VTEX'sSet-Cookieas v7 did: Intelligent Search's session cookies stay on the server, and the Domain becomes the site's. The report lists each one's differences from v7's handler (noallowedOutdatedData/noSplitItem/RnbBehavior, SKU ids sent as numbers, no http→https image rewrite). Every other key becomes theunportedVtexInvokestub, which rejects with the key, plus a report line.cookies(), which percent-encodescheckout.vtex.com=__ofid=…. The report says to use a route handler that appends the client'ssetCookies.Stubs (
scripts/stubs.tswritessrc/migration-stubs.ts; every stub is@deprecated)unportedInvoke(key, props)rejects. It replacesctx.invokein a v7 loader and any key that no handler file answers. For asite/key, v7 failed with "handler not found" too.appState(name)throws. It replacesctx.<app>andctx.getAppState(…).ctxalready does in v8, so the section keeps its saved props.ctxis rewritten: the second parameter must be the request. React Query'sonError(err, vars, ctx)is not touched; a dry run caught that case.useSectionandusePartialSectionreturn v7 7.x's own placeholder URL, so pages render as they did in v7.hx-*attributes are reported. They did nothing in v7 on TanStack Start or Next.js, and they do nothing now.App blocks → client code (
scripts/apps.ts)deco-vtex,deco-shopify,deco-wakeanddeco-resendbecomesrc/clients/<app>.ts, using v7'sconfigure()rules:"1".VTEX_APP_KEY,SHOPIFY_STOREFRONT_TOKEN,WAKE_TOKEN,RESEND_API_KEY). Their values are never written.Page setup (
scripts/scaffold.ts), following/next/tanstack-start-descriptorsand/next/nextjssrc/cms.ts:createCMSwith the binding'scontentClient, so drafts work.src/open-page.server.ts: resolves every section, then runsloadSectionsat the page's URL. On TanStack Start it callsmatchPage(url)first andcommitSegmentCookie()last, and setsX-Deco-Degradedwhen a section loader fails. It addsrequestDevicewhen the site callsuseDevice.src/page.functions.tsis the page's server function. It takes the page's path, so a client-side navigation loads the page, not/_serverFn/….src/page-view.tsxrenders the page throughsrc/views.ts.src/routes/$.tsxis the catch-all route; v7's home route is rewritten the same way.[[...slug]]/page.tsxbinds the draft (bindDraft) and renders<SegmentCookie />. When the site already has a catch-all, it is rewritten in place, keeping its parameter name and route group.cmsRouteConfig,createDecoPage) are replaced, v7's admin routes are deleted, and any other existing file is kept and reported.Also
BlockDescriptor.propsis nowRecord<string, any>, so TanStack Start can serialize a page's loader data.SKILL.md(10,233 bytes, under the 10 KB limit),reference/{script-steps,manual-steps,server-functions,import-map}.mdandCLAUDE.md.Tests
invoke.test.ts(new, 9 tests): every invoke call shape; Next.js actions; app-key vendoring; the stub for a missing key and the report for a computed key; a site's own invoke tree left alone; the ctx stubs, including React Query'sonError;useSection; the app clients; scaffold keep/replace; a Next.js catch-all in a route group.bindings.test.ts: the TanStack Start and Next.js fixtures now include invoke calls,ctx.invoke, a VTEX app block, v7's page and admin routes, anduseSection. Everything generated type-checks against this repo's packages.invoke.test.ts,--vtex-invoke(3 tests):keepvendors and imports by export.v8on TanStack writes the module, stubs the unmapped key, and type-checks the generated files against@decocms/apps-vtex; the cart call then runs over the real v8 client with a fake fetch, and VTEX's cookies are forwarded (IS cookie kept back, the site's Domain).v8on Next.js maps only the cookie-less calls. Four mutations fail them: the mode switch, the IS filter, the Domain rewrite and the Next.js exclusion.cms.ts,open-page.server.ts,page.functions.ts,page-view.tsx,invoke.functions.ts,clients/*.tsandviews.tstype-check against this branch's packages.Gates
bun run checkbun run buildskills:checkandskills:readme:checkdeco --helplists--checkbun run test(2 GB cap)The failures are DS-2, which also fails on main (its fix is in its own PR), and a
schema.test.tstimeout under full-suite load. Run alone, those two files pass 68 of 69, and the one failure is DS-2.Not in this PR
unportedInvokeand reported./nextpages still tell sites to copy these helpers; a docs PR should follow.Validation on two v7 sites (re-run at f2e03a6)
The migrator ran on fresh v7 trees of
deco-sites/storefront-tanstack(eef304e) anddeco-sites/demo-storefront(928488a), with the packages packed from this branch. The re-run includes PO decisions 1 to 9; both sites are Shopify, so they call no VTEX invoke-map keys and need no--vtex-invoke. The new report lines show up there: the two section loaders v7 never ran (CategoryBanner and Wishlist on each site; the demo's hand migration already ran them), thewithABTestinghint, and the CIdeco schema --checkstep (manual on both: no workflow on one, no one-line install step on the other). The manual work was then ported from each hand migration, and the result was compared with that hand migration.That comparison found v7 behaviour the generated page setup had dropped. Fixed here:
<section id data-manifest-key>around v7'sSectionErrorBoundary, in a newsrc/section-error-boundary.tsx.cmsRouteConfig, plusvalidateSearch/loaderDeps, the route cache times and v7's full head (robots, Open Graph, Twitter Card, canonical, JSON-LD).siteblock's SEO defaults and templates.QueryClientmoves intogetRouter.seois typed as v7'sPageSeo, so an SEO block fits it.deco check,tsc<head>vs handog:urlstyleattribute, which the hand migrations droppedLine counts ignore whitespace and exclude content, lockfiles, generated files, docs and the parity harness.
Review round (2026-10-15)
325e19e9: the--vtex-invoke v8stubs are run, and must reject naming the key (unportedVtexInvokeandunportedInvoke);--vtex-invoke v7must exit 1 with the flag's message. A stub that resolved, or a dropped flag check, used to pass.325e19e9:bun run check,bun run build, skills checks, a plain-Node import of all 64 built exports anddeco --helppass.bun run test: 2,720 pass, 95 skipped; DS-2 (fixed on main by test(conformance): DS-2 allows the discard's unstage, still bans commits and remote git #709) fails, plus 5 s timeouts under full-suite load whose files pass alone (213 of 214, the one failure DS-2).--vtex-invokehas no default (2026-10-15,c92d94f5,3aae719e)migrate()reads the site's invoke calls first (vtexInvokeMapKeysUsed, read only). Keys of v7's VTEX invoke map and no--vtex-invoke: it throwsVtexInvokeChoiceNeededbefore any step writes, with the keys and both choices (keep: copy v7's handlers so each call works as it did;v8: the v8 client where it has the operation, else a stub that rejects, in the report).main.tsprints it without the usage and exits 1. A site that calls none, or runs on no binding the script knows (its calls are left as they are), needs no flag.rewriteInvokesno longer assumeskeepeither.invoke.test.ts+3):migrate()andmain.tson a site with three keys stop with the list and every file stays byte-identical; a site without such keys needs no flag; thekeeptest passeskeepexplicitly. Dropping the check fails two of them (the run then writes files and fails later). Skill: 177 tests. Docs:SKILL.mdflag row (10,232 bytes),script-steps.md,server-functions.md.68167723(preload hints only on the app's pages) through C47fad06ae(A/B legacy page carries none) and M187740538.3aae719e:bun run check,bun run build,skills:check,skills:readme:checkpass.bun run test: 2,718 pass, 16 fail, 95 skipped, on a machine at load 27–33 with 13 of 15 GB used by other work: all timeouts plus DS-2. The 9 failing files alone:run,serve,cliconformance,observability,syntax.cli, compatsectionsandtests/releasepass in full;schema.testpasses with the one slow test rerun alone;delivery.clifails only DS-2 (fixed on main by test(conformance): DS-2 allows the discard's unstage, still bans commits and remote git #709).Main refresh (2026-10-17,
5131a3c1)ceba1386into 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→v8 migration script now writes what the M1 report left as "call X here":
/deco/invokecalls become server functions, v7's app blocks become client code, the page is scaffolded for the binding, and stubs cover the v7 runtime v8 lacks. A--vtex-invokeflag decides how the VTEX app's own invoke keys are migrated.Invoke, app blocks, stubs and page setup
invokekey becomes one server function (src/server/invoke.functions.tson TanStack Start with GET loaders marked private and POST actions;src/server/invoke.actions.tson Next.js), imported at each call site.src/clients/<app>.tswith v7's defaults; credentials come from the env vars v7 fell back to, never their values.src/migration-stubs.tsprovides@deprecatedstand-ins (unportedInvoke,appState,useSection) so remaining v7 uses compile and stay findable.src/cms.ts,src/open-page.server.ts, the page route) replaces v7's page routes and renders sections, route options, page SEO and the router'sQueryClientas v7 did, including v7's section wrapper and error boundary on TanStack.BlockDescriptor.propsis nowRecord<string, any>so TanStack Start can serialize a page's loader data, and the final scripts step addsdeco schema --checkto the site's CI.@decocms/blockspeer floor rises to^8.4.0: the bindings and@decocms/compat-v7(which also takes both bindings at that floor) use blocks APIs 8.3.0 lacks, and the migrator writes the new range.VTEX invoke keys
--vtex-invokehas no default: when the site calls keys of v7's VTEX invoke map and no flag is given, migration stops before writing anything and prints the keys with both choices. A site with no such keys needs no flag.--vtex-invoke keepvendors v7's handler so calls likeinvoke.vtex.actions.checkout.addItemsToCartwork as they did.--vtex-invoke v8maps the keys the v8 VTEX client has operations for to functions insrc/server/vtex-invoke.server.tsthat forward VTEX's cookies; other keys become a rejectingunportedVtexInvokestub naming the key, plus a report line. Next.js cart calls stay stubs.Written for commit 6195fe5. Summary will update on new commits.