Skip to content

ci(root): derive the packed workspace set instead of listing it - #835

Merged
mobeenabdullah merged 2 commits into
mainfrom
fix/scaffold-packs-every-workspace-package
Aug 15, 2026
Merged

mobeenabdullah merged 2 commits into
mainfrom
fix/scaffold-packs-every-workspace-package

Conversation

@mobeenabdullah

@mobeenabdullah mobeenabdullah commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator

What was wrong

The scaffold's core: workspace legs packed a hand-written list of nine packages. nextly and plugin-sdk both depend on @nextlyhq/blocks-engine, which was not on it — so pnpm pack rewrote that workspace:* to a concrete version and the install resolved it from the registry.

Nine packages were missing, not one: blocks-engine, blocks-react, builder, plugin-page-builder, plugin-seo, admin-css, and all three storage-*.

The failure a reviewer can reproduce today

Workspace nextly installs beside registry @nextlyhq/blocks-engine@0.0.2-alpha.42, and nextly imports a subpath that copy does not export:

packages/nextly/src/plugins/codegen/block-document.ts:56
  } from "@nextlyhq/blocks-engine/format";

main is self-consistent — the import and the export both arrived in 2f3bb5767 (#738). Only the packed/registry skew breaks it.

Why most of the matrix stayed green — the separating property

Only the two blog legs declare core: workspace. The four blank legs never mix registry and workspace packages at all, so a green blank proves nothing about this class of defect. That is why a nine-package list drifted for as long as it did while the matrix looked healthy.

The second, release-only symptom

On the Changesets Version PR the bumped version does not exist yet, so the same gap fails outright:

npm error code ETARGET
npm error notarget No matching version found for @nextlyhq/blocks-engine@0.0.2-alpha.58.

That is a check that can never pass on a release, on every release, forever — while the repository's merge policy is "CI fully green". It is currently sitting on #744, whose merge drains the changeset backlog. This removes that particular red; I am not claiming it is the only one on #744.

The fix

The packed set is derived from the workspace: every non-private package under packages/, minus create-nextly-app, which is the scaffolder rather than a dependency of what it scaffolds. The same derived list drives the turbo build filter, the pack loop, and the count control.

pin-workspace-packages.mjs already states this rule about its own names:

The package names are read from the tarballs rather than listed here. A list in this file would be a second answer to a question the manifests already answer, and the two agree only until someone adds a package.

The workflow was breaking that rule one layer up.

Two literals removed, not one

The count control was [ "$count" -ge 9 ] — that literal is what let the gap hide, since nine tarballs satisfied it while the tenth was never packed. It is now derived and asserts equality.

The content control asserted package/dist/ on every tarball. @nextlyhq/admin-css has no build script and publishes src and bin, so that assertion failed it forever — the same mistake one level down, caught by Codex and by this PR's own matrix within one round. The expectation is now read from each manifest's files[]; negation entries (ui's !dist/metafile-*.json) are skipped, and a package declaring no files[] refuses rather than passing unverified.

Verification

check result
derivation against the real workspace 18 packages, blocks-engine included
old list vs derived 9 omitted
nextly / plugin-sdk depend on blocks-engine confirmed from their manifests
nextly imports @nextlyhq/blocks-engine/format confirmed, block-document.ts:56
derivation with no packages present exits 1 — refuses rather than returning empty
admin-css tarball vs derived package/src 5 entries
admin-css tarball vs old package/dist/ 0 entries — the predicted failure

The refusal control was re-run without a pipe: tail had been reporting its own exit status, which is the false-clean shape .claude/rules/reading-a-ci-verdict.md warns about.

This PR is self-testing — it edits scaffold-build.yml, which is in that workflow's own paths trigger, so the matrix runs against the change that alters it.

Corroboration

The blocks-engine skew was diagnosed independently by another session from a different failing leg, on run 31865420109, reaching the same cause by a different route. The separating property above is theirs.

A SECOND, independent cause — measured after merge, corrected

Scaffold blog-visual (pnpm) still fails while blog-code-first (npm) now passes. That split is the evidence: this PR fixed cause 1; cause 2 is independent of it.

Error: Module not found: Can't resolve '@nextlyhq/plugin-sdk/admin'   (x6)

The mechanism first recorded here was wrong and is corrected rather than quietly edited. This section originally said the pin script's overrides "may not reach peer ranges". The opposite is true, and it was settled by running it (run 31866038617) rather than by reading the diff:

The override DOES reach peer ranges, and reaching them is the defect. pnpm.overrides maps each packed name to file:/tmp/....tgz, pnpm applies overrides to peerDependencies too, and no resolved semver version can satisfy a file: specifier.

The install log is the line neither theory could be talked into:

✕ unmet peer nextly@file:/tmp/.../nextly-0.0.2-alpha.57.tgz: found 0.0.2-alpha.57

The package is present at the exact version and the peer is still unmet. Under the original theory that line cannot occur. Both theories predicted the same failing import, so only execution separated them.

pin-workspace-packages.mjs never mentions peerDependencies and does not need to — writing the override is enough. The follow-up therefore lives there (and possibly in the scaffolded manifest's peerDependencyRules), not in this workflow. Diagnosed and owned by another session; full write-up at tasks/left-tasks/2026-08-15-1500-blog-scaffold-broken-by-a-hand-listed-pack-set.md.

Publishing is unaffected: npm view @nextlyhq/plugin-form-builder peerDependencies shows a correctly rewritten 0.0.2-alpha.42. Harness defect, not shipped.

Scope

CI-only, so no changeset per the repo rule.

The scaffold's workspace legs packed a hand-written list of nine
packages. nextly and plugin-sdk both depend on @nextlyhq/blocks-engine,
which was not on it, so pnpm pack rewrote that workspace:* to a concrete
version and the install resolved it FROM THE REGISTRY.

Two consequences, and the second is the one that was invisible. On an
ordinary pull request those legs tested the PUBLISHED blocks-engine while
reporting on the workspace, so a change to it could break a scaffold and
merge green. On the Changesets version pull request the bumped version
does not exist yet, and the same gap fails outright with ETARGET --
making a check that can never pass on a release, which is how a merge
policy of 'CI fully green' teaches people to wave red through.

The list is now derived from the workspace: every non-private package
under packages/, minus create-nextly-app, which is the scaffolder rather
than a dependency of what it scaffolds. Nine packages were missing, not
one -- blocks-engine, blocks-react, builder, plugin-page-builder,
plugin-seo, admin-css and all three storage adapters.

pin-workspace-packages.mjs already states this rule about its own names:
a list is a second answer to a question the manifests answer, and the two
agree only until someone adds a package. The workflow was breaking it one
layer up.

The count control is derived from the same list rather than a literal
-ge 9, which is what let the gap hide: nine tarballs satisfied it while
the tenth package was never packed. The derivation refuses rather than
returning empty -- verified by exit code.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@coderabbitai

coderabbitai Bot commented Aug 15, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@mobeenabdullah, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3bd5676e-d3be-40c3-bf17-4de2032452e1

📥 Commits

Reviewing files that changed from the base of the PR and between 7a23525 and 531a70a.

📒 Files selected for processing (1)
  • .github/workflows/scaffold-build.yml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 15, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@nextlyhq/adapter-drizzle

npm i https://pkg.pr.new/@nextlyhq/adapter-drizzle@531a70a

@nextlyhq/adapter-mysql

npm i https://pkg.pr.new/@nextlyhq/adapter-mysql@531a70a

@nextlyhq/adapter-postgres

npm i https://pkg.pr.new/@nextlyhq/adapter-postgres@531a70a

@nextlyhq/adapter-sqlite

npm i https://pkg.pr.new/@nextlyhq/adapter-sqlite@531a70a

@nextlyhq/admin

npm i https://pkg.pr.new/@nextlyhq/admin@531a70a

@nextlyhq/admin-css

npm i https://pkg.pr.new/@nextlyhq/admin-css@531a70a

@nextlyhq/blocks-engine

npm i https://pkg.pr.new/@nextlyhq/blocks-engine@531a70a

@nextlyhq/blocks-react

npm i https://pkg.pr.new/@nextlyhq/blocks-react@531a70a

@nextlyhq/builder

npm i https://pkg.pr.new/@nextlyhq/builder@531a70a

create-nextly-app

npm i https://pkg.pr.new/create-nextly-app@531a70a

nextly

npm i https://pkg.pr.new/nextly@531a70a

@nextlyhq/plugin-form-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-form-builder@531a70a

@nextlyhq/plugin-page-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-page-builder@531a70a

@nextlyhq/plugin-sdk

npm i https://pkg.pr.new/@nextlyhq/plugin-sdk@531a70a

@nextlyhq/plugin-seo

npm i https://pkg.pr.new/@nextlyhq/plugin-seo@531a70a

@nextlyhq/storage-s3

npm i https://pkg.pr.new/@nextlyhq/storage-s3@531a70a

@nextlyhq/storage-uploadthing

npm i https://pkg.pr.new/@nextlyhq/storage-uploadthing@531a70a

@nextlyhq/storage-vercel-blob

npm i https://pkg.pr.new/@nextlyhq/storage-vercel-blob@531a70a

@nextlyhq/ui

npm i https://pkg.pr.new/@nextlyhq/ui@531a70a

commit: 531a70a

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 297985fdab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/scaffold-build.yml
The dist assertion assumed every publishable package builds one.
@nextlyhq/admin-css has no build script and publishes src and bin, so its
tarball contains zero package/dist/ entries and the check exits 1 every
time. Scaffold blog-code-first failed on exactly that.

Same mistake as the hardcoded package list this step had just stopped
making, one level down: a literal standing in for something the manifests
already answer. The expectation is now read from each package's own
files[], and negation entries are skipped because they subtract from a
set rather than naming something that must be present. A package
declaring no files[] refuses rather than passing unverified.

Tarballs are matched to packages by the name inside them rather than by
filename, since a scope becomes a dash and @nextlyhq/ui and nextlyhq-ui
differ.

Controls, on a real pnpm pack of admin-css: 5 entries under package/src
against the derived expectation, 0 under package/dist against the old
one.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 531a70a375

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mobeenabdullah
mobeenabdullah merged commit 682cc31 into main Aug 15, 2026
19 of 20 checks passed
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

Handover for whoever owns this workflow — posting here because it is the channel that reaches you reliably; I misaddressed a socket message and it went to the wrong lane.

Your fix worked, measured on your merge. Scaffold blog-code-first went from failing to success at 531a70a37. The derived pack set did what it said.

My follow-up for the second cause failed, and its own CI is the evidence. #839 fixed the remaining leg — blog-visual passes — and broke blog-code-first, the one you had just repaired. Run 31868835902:

success   Scaffold blog-visual      <- target, fixed
failure   Scaffold blog-code-first  <- regression from my change

Mechanism: my change added @nextlyhq/ui as a root dependency. The generator declares it only conditionally (packages/create-nextly-app/src/utils/template.ts:554), and templates/blog/src/app/layout.tsx uses next/font/google. Widening the root graph changed what the build resolved, and the hermetic-build assertion caught it.

Four rounds, and rounds 2 to 4 were one defect: adding a root dependency to satisfy a peer cannot avoid changing the dependency boundary, and the scaffold legs exist to test that boundary. Optional adapters, then peers of packages the app never installs, then plugin-sdk, then ui. I measured the cheap escape — pnpm.peerDependencyRules.allowedVersions set to * leaves the peer still missing and still unlinked — so it does not help. I have recommended the founder close #839 unmerged rather than ship a fifth narrowing.

The replacement design is written up and it touches THIS file, which is why I am handing it over rather than starting: tasks/left-tasks/2026-08-15-1700-pin-scaffold-packages-through-a-local-registry.md.

Shape: stop writing file: specs. Publish the packed tarballs to a local registry and pin by VERSION. The root cause is that pnpm applies overrides to peer RANGES, so the range becomes a file: specifier that no resolved semver version can satisfy —

✕ unmet peer nextly@file:/tmp/.../nextly-0.0.2-alpha.57.tgz: found 0.0.2-alpha.57

— the package is present at the exact version and the peer is still unmet. A registry keeps ranges semver, so they resolve as they would for a real user, and the scaffolded manifest keeps exactly what the generator wrote.

The property to judge any fix on, and the thing most likely to be forgotten: only the two blog legs declare core: workspace. A green blank leg proves nothing about this class, and judging on one blog leg is exactly how #839 reached "repaired one, broke the other".

The design is unclaimed. Take it if you want it, or say so and I will pick it up once the founder decides on #839 — either way I will not touch this workflow without coordinating first.

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