H-6762: Stop @hashintel/ds-helpers publishing empty tarballs - #9190
H-6762: Stop @hashintel/ds-helpers publishing empty tarballs#9190claude[bot] wants to merge 5 commits into
Conversation
libs/@hashintel/ds-helpers/styled-system/ is the entire published payload of @hashintel/ds-helpers, but it is generated by @hashintel/ds-components (panda outdir "../ds-helpers/styled-system"). That codegen ran with --clean, which calls fs-extra emptyDirSync() on the directory. changeset publish publishes packages concurrently (Promise.all, queue concurrency 10), and ds-components' and petrinaut's prepublishOnly hooks both re-run that codegen -- so a `panda codegen --clean` fires while npm is packing ds-helpers, and the tarball ships with only the six metadata files. That is what 0.1.1, 0.2.0 and 0.2.1 look like on npm; 0.2.1 is still `latest`. - drop --clean from ds-components' codegen:panda, so nothing empties the published directory (turbo does not clean outputs before restoring a cache hit, so freshness is unaffected in CI) - record why in ds-components/turbo.json so it is not re-added - add a dependency-free prepack verifier to ds-helpers that fails the pack when any exports/main/types target is missing, turning the remaining deterministic failure modes into a failed release instead of a silent empty publish - drop the ./recipes export and the import/require conditions on ./types: panda never generates styled-system/recipes/ (no recipes in the preset) and emits types/ as .d.ts only, so those targets have never resolved - add a changeset bumping both packages as a patch. Patch rather than major on the grounds that the two removed export entries never resolved to real files in any published version, so no consumer can regress by losing them. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019N9k8hWgVbTi81ufCMpAWv
Same bug class as the previous commit, one level up, and deliberately kept as a separate commit so it can be dropped independently. libs/@hashintel/ds-components/tsup.config.ts had `clean: true`, which empties `dist/` -- the entire published payload of @hashintel/ds-components -- while @hashintel/petrinaut's concurrent `prepublishOnly: turbo run build` rebuilds this package during `changeset publish`. That is the identical mechanism as panda's `--clean` emptying ds-helpers' styled-system/. THIS IS NOT REPRODUCED. The only supporting evidence is that the published file counts are non-monotonic for a growing component library: 0.1.2 24 files 0.2.0 163 files 0.2.1 58 files 0.2.2 86 files (latest) HEAD 254 files (local pack) which is consistent with a truncating race but does not prove one -- HEAD's source is not 0.2.2's source, so the file-count diff is confounded. It is changed here because `clean: false` costs nothing: CI always builds from an absent dist, and a stale local dist is fixed with `rm -rf dist`. Verified after the change: a from-scratch build (dist removed first) still produces 254 files in `npm pack --dry-run`, with all 7 main/types/exports targets resolving. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019N9k8hWgVbTi81ufCMpAWv
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryMedium Risk Overview
Patch changeset for both packages. Reviewed by Cursor Bugbot for commit 37582a5. Bugbot is set up for automated code reviews on this repo. Configure here. |
… place Reworks the fix at Alex's request. Dropping `--clean` traded an empty-tarball race for stale generated CSS accumulating in developers' trees, so `--clean` is restored and the cross-package write is removed instead. `@hashintel/ds-components`' Panda `outdir` is now `styled-system`, private to that package and already git-ignored, and `codegen:panda` runs with `--clean` again. `@hashintel/ds-helpers` materialises its published payload by copying that directory, via a new dependency-free `scripts/sync-styled-system.mjs`: - the copy is staged in a temporary sibling directory, so a concurrent `panda codegen --clean` emptying the source cannot produce a partial payload; - the staged tree is validated against ds-helpers' own `exports` map, and a missing, empty or incomplete source exits non-zero with an actionable message rather than leaving an empty tree (a good existing payload is left untouched); - it is moved in with `rename(2)` and never written in place, and when the staged tree is byte-identical to what is on disk — the case during a concurrent publish, since Panda's output is deterministic — nothing is written at all, so a packer can never observe a partial `styled-system/`. The copy runs from ds-components' `codegen` as well as from ds-helpers' own `codegen`, because ds-helpers' six subpath exports are consumed at dev and build time, not just at publish time: 444 files in ds-components itself, 107 in @hashintel/petrinaut, 74 in @apps/hash-frontend and 2 in @apps/petrinaut-website, including three Vercel builds. `ds-components#codegen` is the one task every one of those graphs already reaches, so keeping it a producer means no consumer loses its styled system on a cache hit; a pack-time-only copy would have broken all of them. `ds-helpers#codegen` is no longer the no-op `"true"` — it runs the copy, which is what `prepack` uses. `ds-helpers#build` keeps its empty `dependsOn` to avoid the documented cycle. Also restores tsup's `clean: true` for ds-components' `dist`. `clean: false` reintroduced exactly the stale-artifact hazard that ruled out dropping `--clean`, and worse here: the `"./*"` entry in ds-components' `exports` maps into `dist/components/`, so a deleted component's stale artifact would stay importable and would still be published. Applying this commit's pattern to `dist` means restructuring the three steps that each write into it directly (`build:lib:js`, `build:lib:dts`, `build:buildinfo`) behind one promote step; that is left as a follow-up rather than half-fixed, since the `dist` race is suspected but has never been reproduced. Verification (turbo 2.6.3, node 22.22.2, npm 10.9.7): - cold cache: ds-helpers packs 126 files, 9/9 export targets resolve, 0 dangling - warm cache (>>> FULL TURBO, 577ms): same 126 files, 0 dangling - ds-components: 254 files, 7/7 targets resolve, 0 dangling; its private `styled-system/` is correctly absent from the tarball - race repro, 30 concurrent packs against a looping `codegen --clean` + copy: 0 short tarballs, min = max = 126; 2096 samples of ds-helpers' `styled-system/` were all at 120 files — never empty, never partial - negative control, same harness with the pre-fix config restored: 5/30 short tarballs including three exact 6-file packs, so the harness does detect it - `--clean` verified to sweep a stale sentinel file and a stale `recipes/` directory from the private outdir, and the copy propagates that sweep - concurrent publish simulation (both publish hooks in flight, cold cache, 3 iterations): 126 / 254 every time, 0 post-population empty samples - ds-components `lint:tsc` and `lint:eslint` pass; `yarn constraints` exits 0; `oxfmt --check` and `markdownlint-cli2` clean on every touched file
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 146b229. Configure here.
The previous revision of `sync-styled-system.mjs` could still truncate the published payload, and the pack-time verifier would accept the result. Both holes are closed here. The copy validated only the nine entry points named in `exports`. A concurrent `panda codegen --clean` empties the shared private source and then rewrites ~120 files asynchronously, so a read can land on a tree that holds those index files and none of the other 111. `promote` then saw a tree differing from the payload and took its two-`rename` replace path -- which unlinked `styled-system/` first, reopening the exact window this PR exists to close, and left a truncated tree behind. Demonstrated: with a source cut down to its nine entry points, the payload went from 120 files to 9, `verify-package-contents.mjs` exited 0, and `npm pack` produced 15 files instead of 126. The comment claiming the replace path was unreachable during a publish was wrong: the trees are only identical when the source is complete at read time. The first race harness missed it because it ran panda and the copy sequentially in one shell, so the copy always observed a finished source. Two invariants now, both load-bearing: - A tree is installed only if it matches `expected-payload.json`, a checked-in list of all 120 files Panda generates for this package, confirmed by a second identical read. Completeness is judged against something a racing process cannot move; deriving it from disk (or from a manifest the copy writes itself) cannot detect a truncation the copy just performed. A stability check alone is also insufficient -- a statically truncated source passes it -- which an intermediate attempt got wrong. - The payload directory is never unlinked. POSIX cannot rename a directory onto a populated one, so files are staged outside `styled-system/` and moved onto their targets individually with `rename(2)`, which is atomic, and extras are pruned last. `styled-system/` always exists with a full set of entry points. `verify-package-contents.mjs` now asserts the whole tree against the same checked-in list rather than just the entry points, so a truncated payload cannot be packed whatever produced it. When Panda's generated file set legitimately changes, both scripts fail and name the difference; the README explains how to regenerate the list, and the diff is reviewed like any other change. Verification: - regression, source truncated to its nine entry points: copy exits 1 naming the 111 missing files, payload untouched at 120; verifier on a payload truncated behind its back exits 1 naming the same - race repro with a separate generator process, syncer process and packer (the interleaving the first harness could not produce): 0/30 short tarballs, min = max = 126; 16,370 payload samples all at 120 files, never absent, never partial; 697 copy runs, all "already up to date", zero writes - stress, 3 concurrent `panda codegen --clean` loops against one syncer for 40s: 257 copy runs, all "already up to date", exit 0, no spurious failures; 6,219 payload samples all at 120 - negative control, pre-fix `outdir` restored: 1/30 short tarballs including a 6-file pack, so the harness still detects the original bug - cold cache 126 files / 0 dangling; warm cache >>> FULL TURBO in 621ms, same; ds-components 254 files / 0 dangling - concurrent publish simulation, cold cache, 3 iterations: 126 and 254 every time, 0 post-population empty samples, 0 partial samples - guards: source missing / empty / unrecognised all exit 1 and leave the payload at 120; no staging directories left behind - `expected-payload.json` and the staging directories stay out of the tarball - oxfmt clean; `yarn constraints` exits 0; no cycles across 8 turbo task graphs

Requested via Slack thread
🌟 What is the purpose of this PR?
@hashintel/ds-helpershas been publishing near-empty tarballs to npm since March.Before. Installing the design system from the registry gave you a package whose every entry point pointed at a file that was not in the tarball.
0.1.1,0.2.0and0.2.1each contain six files —package.json,CHANGELOG.md, three licence files andREADME.md— and no code at all.0.2.1is still thelatesttag.After. The tarball always contains the generated payload — 126 files — and if it ever does not, the pack fails loudly instead of publishing an empty package. Panda keeps
--clean, so stale generated CSS is still swept.Root cause
libs/@hashintel/ds-helpers/styled-system/is the entire published payload of@hashintel/ds-helpers, but it was written by a different package:@hashintel/ds-components' Panda codegen, whosepanda.config.tssetoutdir: "../ds-helpers/styled-system". That codegen ran aspanda codegen --clean, and--cleanreaches@pandacss/node'sctx.output.empty()→fsExtra.emptyDirSync()on the outdir, followed by an asynchronous rewrite. Nothing else in the repo deletes that directory.changeset publishpublishes packages concurrently —@changesets/cli@2.30.0runsPromise.allover a queue withNPM_PUBLISH_CONCURRENCY_LIMIT = 10, and only drops to serial for interactive 2FA (process.stdin.isTTY, false in CI). Each concurrentnpm publish <dir>runs that package's ownprepublishOnly→prepack→prepare.Several of those hooks re-run the deleter:
@hashintel/ds-componentsprepublishOnly: turbo run build@hashintel/petrinautprepublishOnly: turbo run build(depends onds-components)@hashintel/ds-helpersprepack: turbo run codegen --filter @hashintel/ds-helpersRoot
turbo.jsonhasbuild.dependsOn: ["codegen"], so all three reach@hashintel/ds-components#codegen. Turbo does not de-duplicate across processes — in a two-process run both logged the same task hash as a cache miss and executed it simultaneously:So a
panda codegen --cleanfires whilenpm packis reading the directory, and the tarball is written with it emptied.Evidence
Reproduction. Repeatedly
npm packds-helpers (30 iterations) while ds-components' codegen runs concurrently in a loop:0.2.1The negative control is the same harness with only
outdirpointed back at../ds-helpers/styled-system, so the harness demonstrably detects the bug it is claiming to have fixed.The preconditions held in the actual release. npm publish timestamps for the 2026-05-22 run:
ds-helpers finished packing at 18:00:24Z while
ds-componentsandpetrinaut— both runningturbo run build→panda codegen --clean— were still in flight, i.e. two codegens were live when 0.2.1 packed.Directly observed, pre-fix. A 50 ms sampler on the directory during concurrent
npm publish --dry-runof both packages (cold turbo cache, 5 iterations) saw the directory reach zero files after being fully populated in 2 of 5 runs:0 → 41 → 120 → 0 → 120.0.2.0(also 6 files) has a second, fully deterministic cause on top of this: its publishedpackage.jsonhas noscriptskey at all — the "thin artifact package" refactor left ds-helpers with no generation hook, so on a fresh CI checkoutstyled-system/simply never existed.0.1.1(5 files) predates the current layout entirely; not reproduced, no claim made about it.distrace described under commit 3 is suspected by analogy and has never been reproduced.🔗 Related links
@hashintel/ds-helperson npm —0.2.1islatest🚫 Blocked by
🔍 What does this change?
Panda no longer writes across a package boundary, and it keeps
--clean.ds-helpersobtains its payload by copying.The design
libs/@hashintel/ds-helpers/scripts/sync-styled-system.mjs(new, dependency-free) is the whole mechanism. The source is shared mutable state — a concurrentpanda codegen --cleanempties it and rewrites ~120 files asynchronously, so any read of it can land mid-rewrite. Two invariants make that harmless, and both are load-bearing:expected-payload.json— a checked-in list of all 120 files Panda generates here — and confirmed by a second identical read. Checking only the entry points named inexportsis not enough, and neither is a stability check on its own; see the second review round for why, with a reproduction. A checked-in expectation is the one authority a racing process cannot move.styled-system/and moved onto their targets individually withrename(2), which is atomic, and extras are pruned last.styled-system/therefore always exists and always holds a complete set of entry points, whenever a packer reads it.The common case — a publish, where the payload already matches the source because Panda's output is byte-for-byte deterministic (verified: three consecutive
panda codegenruns produce an identical tree hash, and no timestamps appear anywhere in the output) — writes nothing at all, which is what keeps a concurrentnpm packundisturbed.Where the copy runs, and why not only in
prepackprepackalone would have broken every consumer, becauseds-helpers' subpath exports are consumed at dev and build time, not just at publish time:@hashintel/ds-helpers/*@hashintel/ds-components(itself)@hashintel/petrinaut@apps/hash-frontend@apps/petrinaut-websiteBy subpath:
/css(428 imports),/jsx(205),/types(43),/tokens(20). Three of those packages have avercel.jsonand build on Vercel (ds-components→turbo run build:ladle,hash-frontend,petrinaut-website).So the copy runs from
ds-components'codegenas well as fromds-helpers' owncodegen. The reason is a structural fact about the existing graph:@hashintel/ds-components#codegenis the one task that every one of those consumers already reaches — directly fords-components' own tasks, and transitively through^buildfor the other three. Verified mechanically across 5 packages × 8 tasks that a producer ofds-helpers/styled-systemis present in each graph. Keepingds-components#codegena producer therefore means no consumer can lose its styled system on a turbo cache hit, and no new edges were needed anywhere.ds-helpers#codegenindependently runs the same copy so thatprepackworks standalone.File by file
libs/@hashintel/ds-components/panda.config.ts—outdir: "../ds-helpers/styled-system"→outdir: "styled-system", with a comment recording why it must never point outside the package. Already covered byds-components/.gitignore(styled-system, under# PandaCSS), so no new ignore rule is needed; confirmed withgit check-ignore.libs/@hashintel/ds-components/panda.local.config.ts— drops its duplicateoutdiroverride so it inheritscoreConfig's. Both configs must target the same directory (as they did before), and inheriting means they cannot drift apart.libs/@hashintel/ds-components/package.json—codegen:pandaispanda codegen --cleanagain, and a newcodegen:ds-helpersstep (node ../ds-helpers/scripts/sync-styled-system.mjs) is appended to thecodegenchain.libs/@hashintel/ds-components/turbo.json— the old "never re-add--clean" comment is replaced.codegen.outputsgains the new privatestyled-system/**and keeps../ds-helpers/styled-system/**, with a comment explaining that the second entry is load-bearing for consumer cache hits.libs/@hashintel/ds-helpers/turbo.json—codegenis no longer a no-op:inputsgains the copy script and../ds-components/panda.local.config.ts;outputsstaysstyled-system/**;dependsOnstays@hashintel/ds-components#codegen.build.dependsOn: []is kept — it is what preventsds-helpers#codegen → ds-components#codegen → ^build → ds-helpers#build → ds-helpers#codegen. Verified withturbo run <task> --dry=jsonacross 10 task names: exit 0, no cycles.libs/@hashintel/ds-helpers/package.json—codegengoes from"true"tonode scripts/sync-styled-system.mjs.prepackis unchanged: it still runs codegen and then the verifier.libs/@hashintel/ds-helpers/expected-payload.json(new, committed) — the list of all 120 generated files. Asserted by both the copy and the pack-time verifier. Deliberately committed rather than derived at runtime: a record written by the step that might have truncated the tree cannot detect the truncation. Panda's generated file set is driven by its config (JSX elements, patterns), not by how many componentsds-componentshas, so it is stable — adding a component does not change it. When it does change, both scripts fail and name the difference, and it is regenerated as a reviewed diff.libs/@hashintel/ds-helpers/README.md— documents the copy, why Panda must not write here directly, and how to regenerateexpected-payload.json.libs/@hashintel/ds-helpers/.gitignore— ignores thestyled-system.tmp-*staging directory (removed in afinally, so it only survives akill -9mid-copy).expected-payload.jsonis deliberately not ignored. Verified that neither it nor the staging directory can enter the tarball.libs/@hashintel/ds-*/AGENTS.md— both files documented the oldoutdirmechanism in several places (architecture diagrams, boundary rules, the inlinedpanda.config.tssample, the script table). Updated to match. Also corrects the pre-existing claim thatstyled-system/is "(committed)" — it is git-ignored.Unchanged from the first review round
Still present, still as reviewed:
libs/@hashintel/ds-helpers/scripts/verify-package-contents.mjs— the pack-time backstop, still run fromprepackafter the copy. It resolves every literal target reachable frommain/module/types/exportsand asserts each exists and is non-empty. Strengthened in the second review round to also assert the whole tree againstexpected-payload.json, so a truncated payload cannot be packed whatever produced it. Not itself published (filesdoes not includescripts).exportscleanups —./recipesremoved (styled-system/recipes/is never generated: nodefineRecipe/defineSlotRecipeinds-components/src/, and the generated tree contains onlycss jsx patterns tokens types helpers.mjs; the only in-repo references are the legacy import string a codemod migrates away from), and theimport/requireconditions dropped from./types(Panda emitstypes/as.d.tsonly, so they could never resolve — they also dangled in the last good release,0.1.2).patchfor both packages, text rewritten to describe this approach.exportsentries would normally be breaking, but neither has ever resolved to a real file in any published version, so nothing can regress by losing them. Hencepatch, notmajor.Second review round: the copy could still truncate the payload
Bugbot filed a High Severity finding against the first version of the copy. It was right, it was the same hazard as the original bug, and it is fixed in the second commit. Recording it here because the reasoning should outlive the review thread.
The copy validated only the nine entry points named in
exports. But a concurrentpanda codegen --cleanempties the shared private source and then rewrites ~120 files asynchronously, so a read can land on a tree that holds those handful of index files and none of the other 111. That tree passed the check, then failed the byte-comparison against the existing payload, and so took the "replace" path — which renamed the payload directory out of the way first, reopening the exact window this PR exists to close, and left a truncated tree behind. The code comment asserting that path was unreachable during a publish was simply wrong: the trees are only identical if the source is complete at read time.Reproduced deterministically rather than waiting on a timing lottery. With the source cut down to its nine entry points:
Two further things worth flagging, because they are the traps in this area:
panda codegen --cleanand the copy sequentially in one shell, so the copy always observed a finished source and always took the no-op path. Its clean 0/30 was real but tested the wrong interleaving. The harness now runs a generator process, a copy process and a packer concurrently — 458 and then 697 copy invocations under that harness still never hit the bad path by timing alone, which is precisely why the deterministic reproduction above was necessary.expected-payload.json.On the two directions suggested in review: moving the copy earlier and leaving
prepackto verify only is not sufficient on its own —ds-components' and@hashintel/petrinaut'sprepublishOnly: turbo run buildboth reachds-components#codegen, which runs the copy, so a copy runs during the publish window regardless of whatds-helpers'prepackdoes. And "rename the new tree over the old in one operation" is not implementable: POSIX cannot rename a directory onto a populated one. Never unlinking the payload and moving files onto their targets individually achieves the stated goal —styled-system/is never absent — strictly better than a directory swap could.Commit 3 —
tsup cleanreverted totrueThe previous round set
clean: falseonds-components' tsup config, on the theory thatdist/is exposed to the same race one level up (@hashintel/petrinaut's concurrentprepublishOnly: turbo run buildrebuilding this package while it is being packed).That is reverted.
clean: falsereintroduces exactly the stale-artifact hazard that ruled out dropping--clean, and it is worse here than fords-helpers: the"./*"entry inds-components'exportsmaps straight intodist/components/, so a deleted component's stale artifact would stay importable and would still be published.Applying this PR's pattern to
distis the consistent fix, but it is not a config flag:dist/is assembled by three independent steps that each write into it directly —build:lib:js(tsup),build:lib:dts(tsc -p tsconfig.dts.jsonplusscripts/generate-flat-dts.ts) andbuild:buildinfo(panda ship --outfile dist/...). Staging it means restructuring all three behind a single promote step. Given thedistrace is suspected but has never been reproduced, that is left as a follow-up rather than half-fixed here, and a comment intsup.config.tsrecords the hazard and the reasoning. Happy to do it in this PR if you'd rather.What this deliberately does not do
A pre-publish tarball-content assertion in
release.ymlwould not have caught 0.2.1. Such a gate runs serially, and a serial pack always succeeds — 126 files every time. The failure only exists during the concurrent publish, after the gate has already passed. A gate placed afterchangeset publishwould detect it but cannot un-publish; npm has already movedlatest. That is why the fix removes the cross-boundary wipe rather than adding a check.The
prepackverifier is second-line defence for the deterministic failure modes — missing generation hook (the real 0.2.0 cause), codegen failure, wrongoutdir— not for the race.Residual sharp edge
Two, both deliberate:
expected-payload.jsonhas to be regenerated when Panda's generated file set legitimately changes — a new pattern, a differentjsxFramework, a Panda upgrade. That is a real maintenance cost, accepted because the alternative is a completeness check that a racing process can move. Drift is loud, not silent: both scripts fail and name the difference, and the README says how to regenerate.ds-helpers#codegenrestoresstyled-system/**over whatever is on disk without emptying it first, so a hit cannot sweep stale files the way a cache miss (which runs the copy) does. Inputs being unchanged means the output set should be identical anyway, and it is the same behaviour turbo has always had here. The pack-time whole-tree check would catch it regardless.🐾 Next steps
Follow-ups found while investigating, not addressed here:
ds-components'dist/and promote it atomically, as described under commit 3 — the same pattern, applied to the suspected-but-unreproduced race one level up.@hashintel/ds-componentslists@hashintel/ds-helpersin bothdependenciesandpeerDependencies, so registry consumers ofds-components@0.2.1/0.2.2pull the brokends-helperstwice over.0.2.1is stilllateston npm for@hashintel/ds-helpers. Merging this fixes future publishes but does not fix the registry — someone still needs to cut and promote a good release.🛡 What tests cover this?
No automated test — a concurrency race is not something to assert on in CI. The
prepackverifier is itself the runtime check, and it fails the publish rather than a test run. Verified by hand on turbo 2.6.3 / node 22.22.2 / npm 10.9.7:❓ How to test this?
yarn install.rm -rf libs/@hashintel/ds-components/styled-system libs/@hashintel/ds-helpers/styled-systemcd libs/@hashintel/ds-helpers && npm pack --dry-run—prepackgenerates the private directory, copies it here (styled-system/ created (120 files)), the verifier printsverified 9 entry-point target(s) present., and npm reports 126 files.--cleanstill sweeps:touch libs/@hashintel/ds-components/styled-system/STALE.mjs, thenyarn workspace @hashintel/ds-components codegen.STALE.mjsis gone from both directories, and the copy reportsreplaced.rm -rf libs/@hashintel/ds-components/styled-system && node libs/@hashintel/ds-helpers/scripts/sync-styled-system.mjs— exits 1, names the missing source, and leaves the existing payload alone.index.*files fromlibs/@hashintel/ds-components/styled-system, then run the copy — it exits 1 naming the 111 missing files and the payload stays at 120. Delete the same files fromlibs/@hashintel/ds-helpers/styled-systeminstead and runnode scripts/verify-package-contents.mjs— it exits 1.npm pack --dry-runon ds-helpers; in another looppanda codegen --cleaninsideds-componentswithoutdirtemporarily pointed back at../ds-helpers/styled-system. Short packs (some at exactly 6 files) appear only with the oldoutdir.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
The two
AGENTS.mdfiles are updated in-place, since they documented theoutdirmechanism this PR changes.🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
turbo.json's have been updated to reflect this@hashintel/ds-helpers#codegenchanges from the no-op"true"to a real task that copies the payload, so itsinputsgain the copy script,expected-payload.jsonandds-components' local Panda config.@hashintel/ds-components#codegengains a script step and declares the new privatestyled-system/**alongside the existing../ds-helpers/styled-system/**output. No task or dependency edge was added or removed, andds-helpers#build's emptydependsOnis retained to keep the graph acyclic — verified with--dry=jsonacross 10 task names (exit 0, no cycles).