Skip to content

Switch to pnpm and fix package dependency isolation - #16

Merged
jpslav merged 9 commits into
mainfrom
react-dedupe
Mar 27, 2026
Merged

jpslav merged 9 commits into
mainfrom
react-dedupe

Conversation

@jpslav

@jpslav jpslav commented Mar 27, 2026

Copy link
Copy Markdown
Contributor
  • Switch from npm to pnpm for workspace isolation, replacing install-strategy=nested which caused cascading dependency resolution issues with third-party packages
  • Make satellite packages self-sufficient by adding peer deps as devDependencies for local development/testing
  • Use standard Request/Response types in canopycms-next handler API instead of NextRequest/NextResponse to avoid type duplication across packages
  • Add canopycms/test-utils subpath export, replacing fragile cross-package relative imports
  • Move @tabler/icons-react from root to canopycms package where it belongs
  • Add @codemirror/language and @lezer/highlight as direct deps to work around missing dependency declarations in @mdxeditor/editor
  • Update CI workflows, docs, agent files, and settings for pnpm

CI Bot and others added 5 commits March 26, 2026 13:26
Add peer deps as devDependencies in satellite packages so they can
typecheck and test without hoisting. Move @tabler/icons-react from root
to canopycms package, add vitest to canopycms devDeps, and clean root
package.json to only contain tooling devDeps.

Add canopycms/test-utils subpath export replacing fragile cross-package
relative imports. Use standard Request/Response types in canopycms-next
handler API to avoid NextRequest type duplication across packages.

Add @codemirror/language and @lezer/highlight as direct deps to work
around missing dependency declarations in @mdxeditor/editor.
Replace npm workspaces + install-strategy=nested with pnpm which
provides per-workspace isolation by default. Remove package-lock.json,
add pnpm-workspace.yaml and pnpm-lock.yaml. Update workspace
cross-references to use workspace: protocol. Update root scripts
to use pnpm -r.
@jpslav jpslav changed the title React dedupe Switch to pnpm and fix package dependency isolation Mar 27, 2026
CI Bot added 4 commits March 27, 2026 08:03
Move auth cache refresh logic from hardcoded dynamic imports in
cli/init.ts to a plugin interface method. Each auth plugin implements
createCacheRefresher() to provide its own cache refresh function,
eliminating the circular dependency where the core package imported
from its own plugins.
Add @types/react to canopycms-next, canopycms-auth-dev, and
canopycms-cdk devDeps. Add simple-git to test-app devDeps. These were previously resolved via stale node_modules from npm; clean pnpm install correctly exposes them.
@jpslav
jpslav merged commit 55d6380 into main Mar 27, 2026
5 checks passed
@jpslav
jpslav deleted the react-dedupe branch March 27, 2026 15:23
jpslav added a commit that referenced this pull request Aug 15, 2026
…hared-block docs

Adopter requests #13, #15, #16 from the go-live backlog re-baseline.

- entry-schema.ts: add BlockValueOf<Blocks, N> and BlockComponentRegistry<Blocks,
  ExtraProps> — a mapped type keyed off a block field's discriminated union that
  requires exactly one component per template, making a block-to-component
  registry exhaustive at compile time. Ship the types, not a renderBlocks()
  component: the adopter who requested this already arrived at the mapped-type
  solution independently, and a runtime helper would have to pick a key
  strategy, an unknown-template policy, and prop-threading shape that isn't
  right for everyone. Type-level tests prove exhaustiveness in both directions
  (missing template, unknown extra key both fail to compile).
- entry-schema.ts: add defineFieldFragment(), a 3-line const-inference identity
  helper beside defineBlockTemplate, for discoverability of the
  already-working field-array-spread pattern.
- README: document Block Component Registries, Reusable Field Fragments (both
  composition mechanisms plus a per-use-override example), and Shared /
  Referenced Blocks (recipe + a prominent listEntries-never-resolves-references
  caveat, called out in two places).
- apps/example1: refactor PostView.tsx from an if-chain ending in "Unknown
  block" to a BlockComponentRegistry; wire one real shared/referenced block
  (snippet entry type + sharedCta block template) into schemas.ts and content.
- docs/adopter-migration.md: add the three Unreleased entries.
- Move block-registry-types.md, field-fragments-docs.md and
  shared-blocks-listentries-caveat.md to .claude/future-tasks/resolved/,
  updating index.md and inbound links.

Verified: pnpm lint, lint:bundle, lint:tasks, typecheck, and the full test
suite (3910 tests across 5 packages) all pass.
jpslav added a commit that referenced this pull request Aug 15, 2026
…hared-block docs

Adopter requests #13, #15, #16 from the go-live backlog re-baseline.

- entry-schema.ts: add BlockValueOf<Blocks, N> and BlockComponentRegistry<Blocks,
  ExtraProps> — a mapped type keyed off a block field's discriminated union that
  requires exactly one component per template, making a block-to-component
  registry exhaustive at compile time. Ship the types, not a renderBlocks()
  component: the adopter who requested this already arrived at the mapped-type
  solution independently, and a runtime helper would have to pick a key
  strategy, an unknown-template policy, and prop-threading shape that isn't
  right for everyone. Type-level tests prove exhaustiveness in both directions
  (missing template, unknown extra key both fail to compile).
- entry-schema.ts: add defineFieldFragment(), a 3-line const-inference identity
  helper beside defineBlockTemplate, for discoverability of the
  already-working field-array-spread pattern.
- README: document Block Component Registries, Reusable Field Fragments (both
  composition mechanisms plus a per-use-override example), and Shared /
  Referenced Blocks (recipe + a prominent listEntries-never-resolves-references
  caveat, called out in two places).
- apps/example1: refactor PostView.tsx from an if-chain ending in "Unknown
  block" to a BlockComponentRegistry; wire one real shared/referenced block
  (snippet entry type + sharedCta block template) into schemas.ts and content.
- docs/adopter-migration.md: add the three Unreleased entries.
- Move block-registry-types.md, field-fragments-docs.md and
  shared-blocks-listentries-caveat.md to .claude/future-tasks/resolved/,
  updating index.md and inbound links.

Verified: pnpm lint, lint:bundle, lint:tasks, typecheck, and the full test
suite (3910 tests across 5 packages) all pass.
jpslav added a commit that referenced this pull request Aug 21, 2026
Found by a second independent review, reproduced before fixing.

gray-matter@4 caches parsed files PROCESS-GLOBALLY by content and hands every
caller the same `data` object instance. `readEntryData` merged the body into
that shared object in place, so any listing that touched an md entry rewrote
the cached frontmatter for the whole process.

The consequence landed squarely on this branch's headline use case. Because
`ContentStore.read()`'s md branch calls `matter()` again, a resolved reference
to an md entry picked up the poisoned frontmatter — so the SAME snippet
resolved differently depending on unrelated scoping:

  listEntries({ rootPath: 'content/posts', resolveReferences: true })
    -> id, slug, collection, title
  listEntries({ resolveReferences: true })            // whole site
    -> id, slug, collection, title, body

A search index over md shared blocks therefore contained the snippet's text or
not, decided by whether the listing happened to also list the snippet's own
collection. That is the exact silent-data class #16 exists to fix, one level
down, and every test added in the first two commits used a `format: 'json'`
target — which reparses fresh and cannot show it. Two md-target regression
tests now cover both directions, verified red against the pre-fix code.

Fixed at the single mutation site by copying before the merge; `readEntryData`
is the only writer of a gray-matter result in the package.

Also from the same review:

- Corrected a claim this branch introduced in two places (`resolveSingleReference`
  and concurrency.md) that uncached resolution "reparses per occurrence and has
  no such hazard". False for md/mdx: the same global cache means nested
  frontmatter objects alias across occurrences, calls and requests. The cached
  path is in fact the safer of the two, since it hands out a structuredClone.
  Filed as graymatter-cache-shared-frontmatter.md — fixing it means changing
  `read()`, which this branch promised byte-identical.

- With poisoning fixed, a resolved md/mdx reference deterministically carries
  frontmatter and no body, while a *listed* md entry's data does carry it. That
  inconsistency needs a product decision, not a patch: filed as
  resolved-reference-md-body.md with three options weighed, and documented in
  README as a third caveat so adopters are not surprised meanwhile.

- Corrected an overstated perf claim of my own: cloning is ~63x cheaper than
  re-read-and-parse for a snippet-sized target but ~0.8x for a 265KB JSON one.
  The comment now carries the measured numbers and the two facts that keep the
  bad end narrow.

- Noted the one shape structuredClone does not preserve (`!!binary` Buffer ->
  Uint8Array), verified against real parser output.

- Added the missing tree test for a block shared ACROSS collections, which is
  the only thing pinning that the cache spans the whole recursive walk.
jpslav added a commit that referenced this pull request Aug 21, 2026
…resolution

feat: opt-in reference resolution for listEntries and buildContentTree (#16)
jpslav added a commit that referenced this pull request Sep 13, 2026
The same examples/aws-deployment drift was filed three times on 2026-09-12, twice on int-202609-a and once on int-202609-cms-image, and the base merge brought all three together. example-aws-deployment-drift-from-template.md, the P1 filing, is now canonical and carries every distinct fact from the other two, re-checked against the merged tree: five template/example pairs and their current state, the workflow and stack drift, the guards that exist and what each pins, and the three fix options with the seven placeholders. example-deploy-workflow-drifted.md and examples-aws-deployment-drift.md move to resolved/ as duplicates. Do-next #16 now points at the canonical file, its P3 row is gone, and the epic spec links the resolved copy.
jpslav added a commit that referenced this pull request Sep 13, 2026
- scaffold-synth.test.ts: the imagePlatforms comment and the GitHub App test's comment now state the invariant precisely: no other test reads cdk.out after beforeAll. Before, the App test's comment said every other assertion reads beforeAll state, but the prod-mode and workflow tests read scaffold files from disk.
- example-aws-deployment-drift-from-template.md: PR #323 edited more of the example than runs-on and build args, and it added NEXT_PUBLIC_CANOPY_MODE independently of #322. Names the test that pins it, calls the cross-copy checks per-feature pins rather than textual ones (the asset-support check instantiates AssetSupport), and adds that the example's tsconfig.json extends a ../tsconfig.json that examples/aws-deployment/ does not have.
- index.md: Do-next #16 and the P1 row use the same wording for the cross-copy checks.
- cms-image-pr5-review-followups.md: item 6, from the base merge's code review. The file-level beforeAll in scaffold-synth.test.ts also runs, and can fail, for the type-check describe, which has a scaffold of its own.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant