Conversation
…o + ship @decocms/next Squashed history of PR #307 (originally merged to a branch named 7.x, then migrated to v7 — see below). Three phases plus release automation: Phase 1 — Monorepo split ========================= Splits the single `@decocms/start` package into a 5-package Bun workspace with a one-way dependency graph: packages/live → @decocms/live (CMS core: cms/, matchers/, sdk/, hooks/, types/, admin schema composition — named "live" not "runtime": @decocms/runtime already exists on npm as a real, unrelated, actively-published package at v2.3.1) packages/admin → @decocms/admin (admin protocol handlers, depends on live) packages/cli → @decocms/cli (codegen scripts + Fresh→ TanStack migration tooling, depends on live) packages/tanstack → @decocms/tanstack (TanStack Start + Cloudflare Workers binding: workerEntry, cmsRoute, vite plugin, daemon; depends on live + admin) packages/next → @decocms/next (new) Root-caused and fixed the real bug motivating the split: dist/tsup bundling caused module-state duplication across package boundaries, which is why an earlier attempt at this shipped as v5.2.2 and had to be reverted. This split ships plain .ts source exports with no bundling step. Phase 2 — @decocms/next (Next.js App Router binding) ====================================================== New package mirroring @decocms/tanstack's capabilities for Next.js: SectionRenderer, DeferredSectionBoundary (RSC Suspense streaming), client-only-forced section rendering, DecoPageRenderer, createDecoPage, DecoRootLayout, and Route Handler re-exports of the admin protocol. Validated against tanstack-smoke and next-smoke fixtures. Follow-up cleanup ================== Refreshed README.md, CLAUDE.md, and deep-dive docs for the new architecture. Replaced GAP_ANALYSIS.md + GAP_ANALYSIS_V2.md with docs/known-gaps.md. Audited all ~30 .cursor/skills/ directories: deleted 8 stale ones, merged 2 near-duplicate pairs, path-fixed the rest. Forward-ported 3 fixes that landed on main while this branch was in flight (x-deco-matchers-override, @title/@ignore JSDoc on app loaders, a vite dev-watcher delta-apply fix for a real production incident). Release automation (v7 branch, ci/release.yml + .releaserc.v7.json) ====================================================================== main keeps releasing the legacy @decocms/start (v*.*.* tags, unchanged config). v7 releases the 5 new @decocms/* packages independently, under a separate tag namespace (live-v*.*.*) via a dedicated semantic-release config — deliberately NOT sharing main's `branches` array, since main's @decocms/start and the 5 new packages are unrelated npm packages with no real version relationship. Two hard-won, non-obvious fixes baked into this config, both costly to rediscover: 1. The branch cannot be named like a semver range ("7.x"). Semantic- release auto-classifies any `N.x`-shaped branch NAME as a "maintenance" branch — regardless of how it's written in `branches` config, since classification runs on the resolved branch name, not the config-side glob. Maintenance branches are structurally required to have a LOWER version than a companion "release"-type branch, which is backwards from what we want (v7 should be newer than legacy main). No in-branches-array trick escapes this (confirmed empirically against real semantic-release source: lib/branches/index.js's per-type branchesValidator). Renamed the branch 7.x -> v7 to sidestep it entirely — "v7" doesn't match the range-shape pattern, so it's just a normal branch with a seeded starting tag (live-v7.0.0, on a commit unique to this branch's history, NOT shared with main — sharing it caused semantic-release to compute an impossible `>=7.0.0 <7.0.0`-shaped range once, since both branches would "see" the same tag as their own last release). v7 still needs a companion branch in `branches` (semantic-release requires ≥1 non-maintenance-shaped branch even for a single-branch config) — used a genuine orphan placeholder branch (decocms-live-legacy-anchor, pinned at a fixed v0.0.1 seed tag) rather than main, since main's own fluctuating computed version was ALSO capping v7's valid range in confusing ways. 2. semantic-release's `--dry-run` only skips its OWN native git-tag creation step. It calls `plugins.prepare()` and `plugins.publish()` UNCONDITIONALLY either way, and @semantic-release/exec's prepare/publish hooks don't check dry-run themselves. A naive exec-based publishCmd would attempt a REAL `npm publish` during a "dry run". .releaserc.v7.json's publishCmd checks a `$DRY_RUN` env var (set by the workflow from the workflow_dispatch dry_run input) before calling npm — verified this is a real, not theoretical, risk: an early version of this config without the guard reached a real `npm publish --access public` for @decocms/admin during testing (caught immediately; failed on 404 only because publish permissions aren't provisioned yet, not because of the dry-run guard — that guard didn't exist at the time). Added a workflow_dispatch trigger with a dry_run input (default true) specifically so the version computation and publish plan can be inspected in real CI logs before trusting an automatic push-triggered release — this is genuinely a one-way door (5 packages that have never been published before) and deserves that scrutiny. First verified computation: 7.0.1. Known follow-up, not yet resolved: whatever npm credentials are currently configured for this repo's Actions (NODE_AUTH_TOKEN via actions/setup-node) return 404 (not authorized to CREATE new packages) for @decocms/admin and @decocms/cli — confirmed via the real publish attempt above. The actual first release will need proper npm token provisioning with create-package rights on the @decocms org before it can succeed. Verified: all 5 packages typecheck clean and pass their full test suites via vitest (live 455, admin 15, next 10, cli 369, tanstack 59 — 908 tests total). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…decision)
Same rationale/mechanics as the earlier @decocms/runtime -> @decocms/live
rename: renamed packages/live/ -> packages/blocks/ (git mv, preserving
file history) and every @decocms/live import + bare packages/live path
reference (102 files) to @decocms/blocks.
Also renamed the release-line naming to match: tagFormat
live-v${version} -> blocks-v${version} in .releaserc.v7.json, and
the placeholder companion branch decocms-live-legacy-anchor ->
decocms-blocks-legacy-anchor (new orphan branch + blocks-v0.0.1 seed
tag pushed; old decocms-live-legacy-anchor branch and live-v* tags
left in place for now, not yet deleted).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…decocms/cli -> @decocms/blocks-cli Disambiguates both packages as part of the @decocms/blocks family — 'admin' and 'cli' alone are generic enough to be confusing on their own, unlike @decocms/tanstack and @decocms/next which are already self-explanatory as framework bindings (left unchanged). Renamed packages/admin/ -> packages/blocks-admin/ and packages/cli/ -> packages/blocks-cli/ (git mv, preserving file history), plus every @decocms/admin and @decocms/cli import + bare packages/admin and packages/cli path reference (65 files total) to the new names. Cross-package workspace dependencies (packages/next and packages/tanstack both depend on @decocms/admin; packages/tanstack also depends on @decocms/cli) updated in the same pass. bin script entries in blocks-cli/package.json use relative paths, unaffected by the rename. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…in/blocks-cli siblings vitest run --root ../.. packages/blocks was matching as a path PREFIX, not an exact directory — since packages/blocks-admin and packages/blocks-cli both start with the substring 'packages/blocks', running @decocms/blocks's own test script incorrectly pulled in both siblings' test files too (35 -> 58 files, 455 -> 839 tests). A trailing slash forces an exact directory boundary. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e provisioned 3+ push-triggered runs during development created real git tags before failing on npm 404 (CI's NODE_AUTH_TOKEN lacks create-package rights on @decocms org), each requiring manual tag cleanup. Not removing the capability, just the automatic push trigger — re-add 'v7' to the push branches list once a proper npm Automation token is configured, then use workflow_dispatch for the actual first release rather than relying on the next push to catch it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…citness Renamed packages/next/ -> packages/nextjs/ and examples/next-smoke/ -> examples/nextjs-smoke/ (git mv, preserving file history), plus every @decocms/next import + bare packages/next and examples/next-smoke path reference to the new names. Also proactively added trailing slashes to every package's 'vitest run --root ../.. packages/X' test script (not just the ones with a live collision) — packages/blocks's own test script silently picked up packages/blocks-admin and packages/blocks-cli's tests too in the previous rename (path-prefix match, not exact directory), and nextjs' name is now a substring-prefix risk for any future packages/nextjs-* addition. Trailing slash forces an exact directory boundary in all cases. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
NODE_AUTH_TOKEN is now a granular access token with 2FA bypass and read/write access to the decocms org + all packages. All 5 v7 packages were bootstrapped to 7.0.0 via a manual local publish (confirmed live on npm) using the same token; future commits release through this trigger normally. Also added .npmrc to .gitignore (holds npm auth tokens during local publish sessions) and fixed a stale examples/next-smoke comment missed in the nextjs rename. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…red token Removes the NODE_AUTH_TOKEN dependency entirely — each of the 5 v7 packages now has this repo's release.yml configured as its trusted publisher on npmjs.com. Adds an explicit npm upgrade step since Trusted Publishing needs npm >=11.5.1 and Node 22's bundled npm is older. Also deleted the now-unused NODE_AUTH_TOKEN repo secret. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ication npm Trusted Publishing (OIDC) auto-generates sigstore provenance attestations and cross-checks package.json's repository.url against the actual GitHub repo the OIDC token came from — publish fails with E422 if repository is missing. Adds repository.url + directory to all 5 v7 packages, pointing at https://github.com/decocms/blocks (this repo's current name). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.0.1 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
The declared ^1.28.0 floor is too loose: otel.ts uses ATTR_DEPLOYMENT_ENVIRONMENT_NAME, which doesn't exist until 1.41.1 (verified directly against package tarballs — absent in 1.40.0's stable_attributes.js, present in 1.41.1's). deco-start's own tree happens to resolve a high-enough version so this never surfaced locally, but a consumer (faststore-fila) with a different dependency tree resolved 1.40.0 and hit a hard TS2724 type error on this package's own otel code. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.0.2 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
…mports The full @decocms/blocks/cms barrel (index.ts) re-exports loader.ts and resolve.ts, which transitively import node:async_hooks and node:fs/promises. Bundling ANY export from that barrel for a browser target — even one with zero Node dependencies itself, like getResolvedComponent — drags the whole module graph in, since ES imports are evaluated per-file, not per-export. Turbopack rejects this outright in production builds; webpack has historically let it through uncaught. Found via faststore-fila's migration to this package: a Client Component importing only getResolvedComponent failed Turbopack's production build. New @decocms/blocks/cms/client entry point exports just the verified-safe subset: registry.ts (component lookups — imports only React types), sectionMixins.ts (goes through useDevice.ts -> requestContext.ts, whose only node:async_hooks dependency is already swapped for a browser stub via this package's existing "browser" export condition), and schema.ts (zero imports). Deliberately excludes loader.ts, resolve.ts, sectionLoaders.ts, loadDecofileDirectory.ts, blockSource.ts, and applySectionConventions.ts — resolver/storage concerns that only make sense server-side. Verified with a real esbuild browser-target bundle, not just tsc: client.ts bundles clean with zero node: references; index.ts genuinely fails the same bundle with real node:async_hooks resolution errors, proving both that the original bug is real and that the split fixes it. Kept as an automated regression test (client.browserBundle.test.ts) — shells out to the esbuild CLI binary rather than using its JS API, since the JS API's internal TextEncoder/Uint8Array invariant check fails across Vitest's module-isolation VM boundary (confirmed the invariant holds under plain `bun -e`; it's a realm mismatch, not a real environment problem). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.1.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
…arrel split DecoPageRenderer.tsx (packages/tanstack/src/hooks/) is client-bundled (useState/useEffect/Suspense/lazy) and only used runtime imports that are all in the new cms/client subset (getResolvedComponent, getSectionOptions, getSectionRegistry, getSyncComponent, preloadSectionModule, setResolvedComponent, SectionOptions) — a real consumer of the split published as @decocms/blocks@7.1.0. Its DeferredSection/ResolvedSection type-only imports stay on the full @decocms/blocks/cms barrel, since type imports are erased at compile time regardless of source path, and neither type is in cms/client anyway. Swept the docs/skills that describe this import path for staleness: - CLAUDE.md: added a cms/client row to the package-exports table with guidance on when to use it vs the full barrel; also fixed the Package column still saying "runtime"/"next" from before the @decocms/runtime->live->blocks and @decocms/next->nextjs renames. - import-mapping.md (deco-next-package-migration skill): getResolvedComponent's row still described the dev-diagnostic page's view component as "a client component" — no longer true, it was converted to a Server Component in faststore-fila specifically because of this bug. Added the concrete fix-by-case breakdown and pointed future readers at cms/client for the common case. - docs/hydration-and-ssr-migration.md: the DecoPageRenderer syncThenable fix sketch didn't show an import statement; added a note that getResolvedComponent there needs cms/client, not the full barrel, since it's the exact component this session found and fixed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.1.1 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
…ability.ts sdk/observability.ts re-exports instrumentWorker from ./otel, but otel.ts imported configureMeter/configureTracer/getActiveSpan back from ./observability — a real cycle (observability.ts -> otel.ts -> observability.ts). Confirmed via a real Rollup warning surfaced while building casaevideo-tanstack: "will likely lead to broken execution order" when the two modules land in different chunks. Fix: otel.ts now imports those three functions directly from ../middleware/observability (where they're actually defined — sdk/observability.ts just re-exports them), the same module it already imported METRIC_METADATA from. Breaks the cycle without changing any public API. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.1.2 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
Captures the 2026-07-07 design session: split-by-platform package taxonomy (blocks-* for framework, apps-* for integrations, nextjs/tanstack for framework bindings), the matcher/flags/UI-component duplication found and how to resolve it, lockstep release integration, and what happens to the old apps-start repo. Design only — no implementation yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed with the user: blocks/hooks (existing subpath, already houses similar UI primitives like RenderSection/LazySection) over a new blocks/commerce subpath, to minimize new export-surface additions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…onorepo 15 tasks: shared migration tooling, apps-commerce (foundational), UI component relocation to blocks/hooks, matcher/flags cleanup in blocks, apps-website (reduced scope), then apps-vtex/shopify/magento/algolia/ salesforce/resend/blog, end-to-end verification against faststore-fila, and a deprecation notice on the old apps-start repo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…admin/apps/autoconfig mapping Verified in Task 1 Step 2 by comparing apps-start's registry.ts doc comment (references autoconfigApps()) against packages/blocks-admin/src/apps/autoconfig.ts, which exports an equivalent autoconfigApps(blocks, registry) and documents the old call site directly. Matches the mapping already in this repo's CLAUDE.md.
Moves apps-start's commerce/ (types, app-types, resolve, manifest-utils, utils/*, sdk/*) and root registry.ts into a new @decocms/apps-commerce package, excluding commerce/components/ (Task 3 moves those UI components elsewhere). Rewrites @decocms/start/* imports via scripts/migrate-apps-import.mjs; the registry.ts doc comment's remaining @decocms/start references are updated by hand to the confirmed @decocms/blocks-admin/apps/autoconfig mapping. Test files that lived in __tests__/ subdirs in apps-start are flattened to sibling *.test.ts files to match this repo's co-located-test convention; their relative imports were fixed accordingly (../foo -> ./foo). Known gap, not resolved here: registry.ts's APP_REGISTRY entries dynamically import platform mod modules (./shopify/mod, ./vtex/mod, ./resend/mod, ./blog/mod) that lived alongside commerce/ in apps-start's single package. Now that each platform is a separate @decocms/apps-<platform> package depending one-way on @decocms/apps-commerce, resolving those imports would require the reverse edge (apps-commerce -> apps-shopify -> apps-commerce), violating the monorepo's one-way dependency rule. Left as @ts-expect-error stopgaps with an explanatory comment pending a plan-level decision (e.g. a site-level registry aggregator, or non-statically-typed string entries) rather than guessing a fix. Verified: bun install clean; packages/apps-commerce typecheck + test clean (5 files, 50 tests); whole-workspace typecheck and test clean across all 6 packages (blocks, apps-commerce, blocks-admin, blocks-cli, nextjs, tanstack). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…m entries Task 2's implementation surfaced a real conflict: apps-start's registry.ts was a single static array whose entries relatively-imported every platform's mod.ts, only possible because everything lived in one package. Split by platform, no apps-* package can hold that array without depending on every other apps-* package, violating the one-way rule. User decision: apps-commerce/registry keeps only the shared types; each platform package with a registrable app (vtex, shopify, resend, blog) exports its own single-entry registry from its own package, and sites compose their own array explicitly. Updates Tasks 7/8/12/13's scope accordingly (documented here; each task's own section gets the concrete addition when reached). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…stry types Resolves the circular-dependency gap flagged in the previous commit: registry.ts no longer holds a static APP_REGISTRY array whose entries dynamically imported sibling platform mods (./shopify/mod, ./vtex/mod, ./resend/mod, ./blog/mod) that can't resolve from this package without creating a reverse apps-commerce -> apps-<platform> -> apps-commerce dependency edge. Per decision: each platform package with a registrable app now exports its own single-entry registry from its own ./registry subpath (Tasks 7, 8, 12, 13 — not this task). apps-commerce/registry.ts keeps only the shared AppRegistryEntry / AppRegistry types those per-platform registries will import. The @decocms/apps-commerce package.json's "./registry" export subpath is unchanged. Verified: packages/apps-commerce typecheck + test clean (5 files, 50 tests); whole-workspace typecheck clean across all 6 packages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t commerce/components) Moves Image, Picture, and the JsonLd SEO components from apps-start's commerce/components/ into @decocms/blocks/hooks (existing subpath, per the design's resolved decision to reuse it rather than add blocks/commerce). Image and Picture were already self-contained (no commerce-type imports) and moved unchanged; ported Image's Vitest suite alongside it. JsonLd.tsx imported Product/ProductListingPage/BreadcrumbList/etc. from apps-start's commerce types module (now @decocms/apps-commerce/types), which @decocms/blocks cannot depend on (one-way rule: apps-* depends on blocks, never the reverse). Each JsonLd function only reads a small, flat subset of those schema.org types (name/url/sku/image[].url/ offers.price/aggregateRating.ratingValue, etc.) — the original code already didn't fully trust the nominal Product.offers type either (it cast to `Offer[] | AggregateOffer`). Replaced the import with local, minimal structural types (JsonLdProduct, JsonLdProductListingPage, JsonLdBreadcrumbList) that describe just what's read; any real apps-commerce Product/ProductListingPage/BreadcrumbList value is a structural superset and can be passed in without a cast. No `JsonLd` symbol exists in the source file (only ProductJsonLd, PLPJsonLd, BreadcrumbJsonLd, seoMetaTags), and Picture.tsx has no default export (named Picture/Source) — the barrel exports the components' actual names rather than the brief's placeholder `default as X` shape. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
When a widget alias like Color is imported from a module ts-morph can't
resolve (remote/CDN import), the type comes through as any and
typeToJsonSchema returns {}. applyWidgetFormat matched the alias name
correctly but every application branch required a pre-existing type, so
the format was dropped silently -- the field emitted with neither type
nor format, and the admin fell back to a plain text input instead of
the color picker.
Add a final branch to applyWidgetFormat: when the alias matches a known
widget but the schema has no type/$ref (unresolved any), recover it as
{ type: "string", format } -- every widget alias is string-based.
Also makes isMainModule robust to symlinked entrypoints (compares
realpathSync of both sides instead of raw file:// URL strings -- tsx
symlink traversal was silently skipping generation and writing no
meta.gen.json).
Ported from main (#322, commit 73d9345).
Original-Author: guitavano <tavano62@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Align telemetry with the deco-cx/deco framework so both land on the same series/labels in ClickHouse (no collisions): - Durations normalized to SECONDS at the source (was ms). http.server / http.client / loader durations all divide by 1000 at record time. - HTTP server attributes -> OTel semconv: method -> http.request.method, route_pattern -> http.route, status -> http.response.status_code. Proprietary extras moved under deco.* (deco.http.status_class / .outcome / .region, deco.cache.decision / .layer). - Cache: the two hits/misses counters collapse into a single deco.cache.requests counter dimensioned by deco.cache.status (follows the OTel semconv pattern, e.g. nfs.server.repcache.requests + .status). - Loader labels namespaced under deco.* (deco.loader.name, deco.cache.result). - deco.cms.resolve.duration drops its path label entirely: the raw request path was unbounded cardinality (one histogram series per product/search/facet URL) and was the single biggest series in ClickHouse (~56% of otel_metrics_histogram). Per-route detail still lives on the resolve span's sampled deco.route attribute -- the correct home for high-cardinality per-request dimensions. - normalizePath exported so every route-labeled metric shares the exact same cardinality-bounding normalization. Caller API (RequestMetricLabels field names) is unchanged -- only emitted keys change. Ported from main (decocms/blocks, commits 6655cf3 + be66958 + 0f6ce27 + 11c23df, squashed here since all four are sequential fixes to the same few functions in this one file -- splitting them into 4 separate commits here would be artificial). resolve.ts's corresponding one-line change (drop the RESOLVE_DURATION metric's path label, keep the span attribute) is bundled into the sticky-flags commit that follows, since that commit touches the same function for an unrelated reason. Original-Author: Nicacio Oliveira <nicacio@deco.cx> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two independent upstream fixes combined here because both touch sdk/workerEntry.ts's request/response pipeline: 1. Outbound User-Agent + x-powered-by parity (#319): Cloudflare Workers fetch sends no User-Agent at all, unlike the old Deno runtime where every outbound request implicitly carried Deno/x.y.z. UA-less requests trip Cloudflare managed WAF rules / Bot Fight Mode on partner origins, blocking loader calls. New sdk/outboundHeaders.ts: installDefaultUserAgent() patches globalThis.fetch once (idempotent) to set Deco/<version> (+https://deco.cx) when the caller didn't set a UA; app-specific UAs always win. createDecoWorkerEntry installs it by default; new outboundUserAgent option overrides the value or disables with false. Responses now carry x-powered-by: deco@<version> (set-if-absent). 2. Sticky, cache-correct A/B multivariate flags (#323): website/matchers/random.ts was a stateless Math.random() < traffic, so a visitor's variant re-rolled every request and -- because the HTML is edge-cached and the cache key never carried the variant -- the first roll got frozen and served to everyone in the device/geo bucket. Analytics never saw the active flag either. New packages/blocks/src/sdk/flags.ts (deco_segment cookie codec, shared by both packages/blocks and packages/tanstack -- resolve.ts records sticky decisions, cmsRoute.ts persists them to the cookie, workerEntry.ts folds the cohort into the cache key so each variant keeps its own cached HTML). resolve.ts's RESOLVE_DURATION metric also drops its path label here (bundled from the observability commit just before this one, since this is the same function). Ported from main (commits 1f6da52 + 109913e). flags.ts lives in packages/blocks (not packages/tanstack) because resolve.ts, which records the sticky decisions, is a packages/blocks file and needs a same-package import; workerEntry.ts and cmsRoute.ts (packages/tanstack) consume it via the new @decocms/blocks/sdk/flags export instead of the relative "./flags" import main used before the monorepo split. tanstack/tsconfig.json's "include" gained "package.json" (matching packages/blocks's own tsconfig) since outboundHeaders.ts reads its own package version from it -- without this tsc can't resolve the JSON import even with resolveJsonModule enabled. Original-Authors: hugo-ccabral <hugo@deco.cx>, guitavano <tavano62@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.3.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
…function'
Next.js App Router route handlers evaluate their module graph against
React's react-server build (Next's vendored rsc/react.js) and IGNORE
"use client" directives — there is no client graph to move a module
into. That build exports forwardRef/useMemo/createElement but NOT
createContext, so any module-scope createContext call reachable from a
route.ts crashes the whole route at import time:
TypeError: {imported module .../vendored/rsc/react.js}.createContext
is not a function
All six admin handlers (metaGET, decofileGET/POST, invokePOST,
renderGET/POST) crashed this way, each bisected individually against a
real Next 16.2.6 + Turbopack consumer (faststore-fila's /.decofile).
Two independent import paths reached a module-scope createContext:
1. blocks-admin -> @decocms/blocks/cms barrel -> sectionMixins.ts
(detectDevice) and sdk/requestContext.ts (isMobileUA) -> useDevice.ts,
whose back-compat re-export drags in useDeviceContext.tsx ("use
client" + top-level createContext). Fix: split the pure UA parsing
(Device, MOBILE_RE, TABLET_RE, isMobileUA, detectDevice) into a new
react-free leaf sdk/detectDevice.ts; sectionMixins and requestContext
import the leaf; useDevice.ts re-exports it so its public API is
unchanged. New ./sdk/detectDevice subpath export.
2. @decocms/nextjs root barrel also exports the render components,
whose graph reaches @decocms/blocks/hooks -> Picture.tsx — a
top-level createContext with no "use client" at all (a latent bug for
ANY react-server graph, Server Component pages included; it only
survived because every current consumer happens to import the hooks
barrel behind an existing client boundary). Fix: create the preload
context lazily on first render — renders only ever happen under a
full React build.
Even with both fixed, mixing render components and route handlers in
one barrel leaves a landmine (e.g. SectionErrorFallback's "use client"
+ class extends Component still crashes route graphs, since directives
are ignored there and react-server has no Component). So route files
now get a dedicated component-free subpath:
export { decofileGET as GET } from "@decocms/nextjs/routeHandlers"
Verified against faststore-fila under real npm-resolved node_modules
(files copied over the installed 7.3.0 packages, no symlinks):
/.decofile 200 with the full 2.5MB decofile JSON, storefront routes
unaffected, zero createContext errors. Not a 7.3.0 regression — the
crash reproduces identically on 7.2.3; this is latent since the
package split.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.3.1 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
generate-schema.ts's findTsxFiles and generate-sections.ts's walkDir both scanned every .ts/.tsx under the sections dir, so a co-located test file (e.g. sections.test.ts) could be emitted as a bogus section block — this happened for real in fila's meta.gen.json. Both generators now route their directory walks through a shared isExcludedCodegenFile predicate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…le:// paths meta.gen.json definition keys were derived from ts-morph's absolute file:/// paths, so the same repo produced different schema IDs (and /live/_meta ETags) on every machine. Relativize path-derived IDs to the site root via definitionIdForPath(); CMS-key-derived IDs were already stable and are untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… can compile `import.meta.env?.DEV` in decofile.ts's dev-bypass check is a syntax error when a CJS consumer (ts-jest, as in fila's Next.js jest suite) compiles this raw-TS package — `import.meta` has no CJS representation at all, so the file fails to parse before any test even runs. Replace it with a NODE_ENV check (valid in both ESM and CJS) and rename isViteDev -> isDevRuntime since it's no longer Vite-specific. This intentionally widens the dev-bypass to `next dev` (previously always false there, since Next never defines import.meta.env) and narrows it slightly under vitest (NODE_ENV=test, not "development"), so decofile.test.ts now forces NODE_ENV=development in beforeAll/afterAll to preserve its original no-auth-bypass coverage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
env.ts was the one signal left after 97695d5 removed import.meta from decofile.ts. It's importable via the @decocms/blocks/sdk barrel, so any CJS consumer (ts-jest) compiling this raw-TS package would hit an ESM-only syntax error. Replace the import.meta.env.DEV read with a try/catch'd process.env.NODE_ENV read (readNodeEnv()) that bundlers can still statically define at build time, preserving the NODE_ENV and DECO_PREVIEW fallback signals unchanged. Adds env.test.ts covering isDevMode() across production/development/DECO_PREVIEW. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mport map for non-Vite bundlers createSiteSetup's `sections` option is normally satisfied by Vite's import.meta.glob, which Next has no equivalent of. Behind an opt-in --registry flag, generate-sections now walks the same section files it already scans and emits `sectionImports`, a glob-style-keyed Record<string, () => Promise<any>> that drops straight into SiteSetupOptions.sections (and createNextSetup's sectionGlob later). Built from every scanned file, not just ones carrying convention exports, so files like a bare Promo.tsx are still registered. Output without the flag is untouched, keeping Vite sites' regenerated sections.gen.ts byte-stable in CI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Composes createSiteSetup, applySectionConventions, loadBlocks (all route-handler-safe) with loadDecofileDirectory for filesystem decofile loading, and lazily imports @decocms/blocks-admin for meta/renderShell/ previewWrapper so the eager import graph never touches module-scope client-React. Returns a memoized ensureSetup() for Task 6's dispatcher and fila's Task 10 to call before every admin/route-handler request. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
setupPromise ??= (...) permanently cached a rejected first attempt, so one transient failure (fs blip during loadDecofileDirectory, flaky meta() fetch) would poison the warm serverless instance forever. Now the memo is cleared on rejection so the next ensureSetup() call retries, while the triggering call still rejects with the original error. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds createDecoRouteHandlers() to routeHandlers.ts: a single GET/POST dispatcher for app/deco/[[...deco]]/route.ts that routes /deco/decofile, /deco/meta, /deco/render, /deco/invoke/*, and /deco/previews/* (rebuilt to the /live/previews/* prefix handleRender parses) to the existing blocks-admin handlers, running an optional setup() before each request and 404-ing unknown paths. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oke branch The catch-all dispatcher wired the same handler to GET and POST with no method check on invoke/*. handleInvoke has no auth and falls back to a ?props=<json> query string for GET, so an unauthenticated <img> tag on a third-party page could trigger mutating VTEX actions cross-site (GET is a CORS simple request, no preflight). The per-URL invokePOST export this dispatcher replaces was POST-only for exactly this reason — restore it, and 405 non-GET on meta (read-only schema endpoint) for symmetry. Also pins the previews-rebuild's `new Request(rebuilt, request)` body- forwarding semantics with a test, and documents why a plain-object init would throw "duplex option is required" on a future refactor. That test needs Node's native Request (jsdom's fetch polyfill drops the body on Request-as-init), so routeHandlers.test.ts now runs under @vitest-environment node, matching this package's setup.test.ts precedent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lePackages) Adds packages/nextjs/src/config.cjs (CJS, requireable from a CJS next.config.js despite the package being "type": "module") exporting withDeco() and DECO_REWRITES, plus a src/config.d.cts declaration and a "./config" exports map entry. withDeco() prepends the three Studio rewrites (/.decofile, /live/_meta, /live/previews/:path*) that Task 6's dispatcher expects, ahead of any user rewrites in array or object form, and dedupes transpilePackages with the @decocms packages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s/createNextSetup + README recipe Migrates examples/nextjs-smoke off its ad-hoc rewrites/route files onto the three new @decocms/nextjs surfaces built in Tasks 5-7 (withDeco, createDecoRouteHandlers, createNextSetup), and adds packages/nextjs/README.md as the copy-paste recipe a new Next.js site follows. While validating end-to-end against a real `next build`/`next dev`, found and fixed two real bugs surfaced by the migration (not just fixture wiring): - routeHandlers.ts's dispatch derived the requested action solely from a `/deco/`-prefixed pathname, on the assumption that a next.config rewrite hands route handlers the destination path. Verified empirically (dev and `next start`) that Next.js instead hands `request.url`/`nextUrl.pathname` as the ORIGINAL, pre-rewrite path — so every rewritten protocol URL (`/.decofile`, `/live/_meta`, `/live/previews/*`) 404'd. `resolveAction` now accepts both forms; added regression tests hitting the rewrite-source paths directly (the prior tests only ever constructed already-/deco/* Requests, so they'd have kept passing against the broken dispatcher). - Picture.tsx (reachable from DecoRootLayout/SectionRenderer via the hooks/index.ts barrel) imports createContext/useContext without "use client", which crashes Next's react-server module-graph check for any page that imports the root barrel. Added "use client", same precedent as SectionErrorFallback.tsx's existing fix for the same class of issue. Also dropped a stray console.log left in blocks-admin's handleRender. Verification: `examples/nextjs-smoke` builds clean; dev server curls of /.decofile, /live/_meta, and / (CMS-resolved Hero content) all 200; repo-wide `bun run typecheck && bun run test` green across all packages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…he block comment Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…enance path npm 12.0.0 (published 2026-07-08T21:06Z, ~1h before our release run) fails every 'npm publish' with "Cannot find module 'sigstore'" from libnpmpublish's provenance.js. Two release runs died on it; the morning's 7.3.1 release on the same runner image succeeded because @latest still resolved to 11.18.0 then. Pin the major; revisit after npm 12 stabilizes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.4.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tifacts live in the framework's folder)
Flips the default output path of four blocks-cli generators from
src/server/{cms,admin}/ to .deco/, so generated framework artifacts
live alongside .deco/blocks instead of scattered under app source:
- generate-blocks -> .deco/blocks.gen.ts (+ sibling .deco/blocks.gen.json,
derived from --out-file by extension swap)
- generate-loaders -> .deco/loaders.gen.ts
- generate-sections -> .deco/sections.gen.ts
- generate-schema -> .deco/meta.gen.json
generate-invoke is the one exception and keeps its default at
src/server/invoke.gen.ts: unlike the other four (inert data/metadata
snapshots the framework loads at runtime), it emits actual app
server-function code that TanStack Start's build-time compiler
transforms (createServerFn().handler() -> RPC stubs), so it belongs
in src/ with the rest of the compiled app code. Documented inline.
Legacy guard: when no explicit --out/--out-file flag is passed AND
the OLD default file still exists on disk, each generator prints a
one-line stderr warning (lib/legacyArtifact.ts) naming both paths and
telling the caller to move the file and update its importers, then
writes to the NEW default anyway. An explicit flag skips the guard
entirely (deliberate choice, no warning). Every flipped generator
mkdir -p's the new output directory before writing.
Updated in the same commit (ships in the same lockstep release):
- packages/tanstack/src/vite/plugin.js dev-mode watcher, which called
generateBlocks()/generate-schema.ts relying on the old defaults
- packages/blocks-cli/scripts/migrate.ts's scaffolding templates
(setup.ts, commerce-loaders.ts, knip-config.ts) that imported from
or referenced the old default paths
- packages/nextjs README + setup.ts JSDoc: scripts no longer need
--out/--out-file, and the recipe now imports generated artifacts
through a `deco/*` tsconfig path alias -> .deco/* instead of a
relative import, since src/deco/setup.ts isn't adjacent to the
site-root .deco/ directory
- packages/blocks-admin's "unknown handler" error message and
packages/blocks/src/setup.ts JSDoc, which pointed at the old path
examples/nextjs-smoke and examples/tanstack-smoke construct their
setup inline and don't invoke any of these generators, so they needed
no changes.
Tests: extended generate-sections.test.ts and generate-schema.test.ts
with subprocess fixtures covering the new default path, the legacy
warning (both paths + guidance text on stderr, new default still
written), and the explicit-flag no-warning case.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
generate-blocks/loaders/sections/schema now default their output to
.deco/ instead of src/server/{cms,admin}/ (commit 36ebf2a). Update the
two migration-skill docs that hardcoded the old paths in troubleshooting
commands and the setup.ts template's generated-artifact imports.
generate-invoke's src/server/invoke.gen.ts default is unchanged and was
left as-is everywhere it's referenced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.5.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
The spawnSync-tsx fixture tests take ~6s under full-suite parallel load, tripping vitest's 5s default — 2 flaky failures on whole-monorepo runs that never reproduced in isolated package runs. test: type on purpose — no release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… inside Vite Root cause of the long-standing non-fatal "[deco] blocks bootstrap failed: TypeError: generateBlocks is not a function" cold-start warning on lebiscuit-tanstack: tsx 4.22.0-4.22.4 has a loader-hook state bug (fixed upstream in 4.22.5, "isolate hook state per async module.register() registration"). Inside a Vite dev-server process, tsImport(@decocms/blocks-cli/generate-blocks) resolves the module correctly but returns an EMPTY namespace — no rejection, which is why every out-of-band reproduction (plain node, same parent URL, same tsx copy) passed while the in-process call failed. Sites whose lockfiles froze 4.21.0 (baggagio, casaevideo, fila) never saw it; lebiscuit's lockfile froze 4.22.0. Verified in both directions on lebiscuit: tsx 4.21.0 -> bootstrap succeeds; 4.22.0 -> empty namespace; 4.23.0 -> succeeds. blocks-cli's tsx range was ^4.19.0, so any fresh install could land in the broken window — floor it at ^4.22.5. The tanstack vite plugin's loadGenModule also now fails with an actionable message (naming the tsx window and the upgrade command) when it sees an empty namespace, instead of the bare "generateBlocks is not a function" that a site's own hoisted-broken-tsx lockfile pin would still surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🎉 This PR is included in version 7.5.1 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
…src/ Smoke-tested moving it to .deco/ on a real site: TanStack Start's client stub transform handles the new path fine, but the server half can't resolve the split module back to an executable handler — every /_serverFn call 500s (unhandled, no stack) while the storefront and typecheck stay green. Reverted; same probe back under src/ returns 200 with a real VTEX orderForm. docs-only commit, no release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Long-term tracking PR for the
v7line — not intended to merge immediately.mainkeeps shipping the legacy single-package@decocms/start(currently v6.x);v7is where the split package set (@decocms/blocks,@decocms/blocks-admin,@decocms/blocks-cli,@decocms/tanstack,@decocms/nextjs) develops and releases independently until it's ready to become the primary line.Successor to #307 (merged to a branch originally named
7.x, since renamed tov7— see below for why).@decocms/startinto a 5-package Bun workspace with a one-way dependency graph. Root-caused the module-state-duplication bug that sank an earlier attempt at this (shipped as v5.2.2, reverted) — this split uses plain.tssource exports, nodist/tsup bundling.@decocms/nextjs, a new Next.js App Router binding mirroring@decocms/tanstack's capabilities (deferred/streamed sections via RSC Suspense,DecoPageRenderer,createDecoPage,DecoRootLayout, admin protocol Route Handlers).@decocms/runtime→@decocms/live(collided with a real, unrelated, actively-published npm package) →@decocms/blocks(per a later naming decision).@decocms/admin→@decocms/blocks-adminand@decocms/cli→@decocms/blocks-clito disambiguate them as part of the blocks family (@decocms/tanstack/@decocms/nextjsare already self-explanatory as framework bindings, left unchanged).@decocms/next→@decocms/nextjsfor explicitness..releaserc.v7.json+.github/workflows/release.yml): separate tag namespace (blocks-v*.*.*) from main'sv*.*.*,workflow_dispatchdry-run escape hatch verified against real CI.7.0.0) via a manual local publish once a properly-configured npm granular access token (2FA bypass + org read/write) was provisioned. Push-triggered releases onv7are now live — the first real CI release (7.0.1, picking up the renames/fixes since the7.0.0seed) is in flight as of this writing.Test plan
bun run typecheck)workflow_dispatchdry-run against real CI7.0.0), confirmed live on npmjs.com7.0.1) — in flight, retrying after npm registry replication lag on the first attempt🤖 Generated with Claude Code
Summary by cubic
Split the legacy
@decocms/startinto a Bun workspace monorepo, shipped@decocms/nextjs, and merged the former@decocms/appsinto@decocms/apps-*. Recent updates add a Next.js glue tier (withDeco,createNextSetup,createDecoRouteHandlers), non‑Vite section registry generation, codegen/CJS compatibility fixes that stabilize schema IDs and improve admin routing, and now default all codegen outputs into.deco/with a legacy-path guard; the release pipeline uses npm Trusted Publishing and pins npm to 11.x to avoid npm 12 provenance failures. Docs now record whygenerate-invokemust stay atsrc/server/invoke.gen.ts.New Features
withDeco(Studio rewrites +transpilePackages),createNextSetup(one‑call site bootstrap), andcreateDecoRouteHandlers(single App Router dispatcher).examples/nextjs-smokeupdated to use them.@decocms/blocks-cli generate-sections --registryemits a lazysectionImportsmap for Next.js.@decocms/blocks-clinow writes generated artifacts to.deco/by default —.deco/blocks.gen.ts,.deco/loaders.gen.ts,.deco/sections.gen.ts,.deco/meta.gen.json— with a one‑line warning when an old default file still exists;generate-invokestays atsrc/server/invoke.gen.ts. Dev watcher (Vite plugin), templates, and@decocms/nextjsdocs updated to match (use adeco/*alias).Bug Fixes
/live/_metaETags are stable across machines./.decofile,/live/_meta,/live/previews/*), and enforce POST‑only oninvoke/*;createNextSetupnow clears a rejected bootstrap to avoid poisoning warm instances.import.meta.envreads withprocess.env.NODE_ENVin@decocms/blocks-adminand@decocms/blocks; added"use client"toPictureto prevent react‑server import crashes.tsxto^4.22.5in@decocms/blocks-clito fix VitetsImportreturning an empty namespace (4.22.0–4.22.4); improved the@decocms/tanstackVite plugin’s error message for this case.invoke.gen.tsmust remain undersrc/(server function resolution), no runtime change.Written for commit 336b91e. Summary will update on new commits.