fix(create-objectstack): ship a .gitignore in scaffolded projects#3129
Merged
Conversation
`npm pack` / `pnpm pack` strip `.gitignore` from a tarball unconditionally, at every depth. The blank template committed one at src/templates/blank/.gitignore and tsup faithfully copied it to dist/templates/blank/.gitignore, but `files: ["dist"]` publishing dropped it on the way to the registry — so the file was present in the repo, present in every local build, and absent from all 11 files of a real scaffold. Every project created with `npx create-objectstack` had no .gitignore at all, leaving node_modules/ and .env un-ignored. Verified against the published 15.1.1 tarball, which ships dist/templates/blank/.dockerignore and no .gitignore. Commit the template as `_gitignore` and restore the real name at copy time through a TEMPLATE_FILE_ALIASES map. Only .gitignore is aliased: the strip list is .gitignore and .npmrc, not "every dotfile" — .dockerignore packs fine and stays literal. The restored rules also cover .env / .env.*, which they never did. The template README has users write OS_AUTH_SECRET and OS_SECRET_KEY into a .env and docker-compose.yml calls that file "never committed", but only the prose said so — .dockerignore was the only file that listed it. copyDir moves to template-copy.ts so a test can import it without running the CLI (index.ts calls program.parse() on import), the same reason pkg-utils.ts exists. Regression test packs the real package, scaffolds from the extracted tarball with the real copy logic, and asserts every template file lands under its intended name. Source-level assertions cannot see this bug — the file only vanishes at publish — and reading the repo's own dist/ would be false green, since turbo's `test` task only dependsOn `^build` and excludes dist/** from its inputs. Both halves are guarded independently: reverting the rename fails the file-set check, dropping the alias fails the .gitignore assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…template The file-set check only walked src/templates/blank, so a second bundled template committing a raw .gitignore — or a stripped dotfile added beside src/templates/AGENTS.md — would reintroduce the bug silently, which is the one thing this ratchet exists to prevent. Assert the tarball carries src/templates/** verbatim instead (aliases are a scaffold-time concern, so no alias mapping on either side), and keep the scaffold-based check for the alias restoration. Verified red for all three reverts: the original rename, an emptied alias map, and a new bundled template with a raw .gitignore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # packages/create-objectstack/src/template-consistency.test.ts
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found while fixing #3110 (PR #3119) — out of scope there, filed and fixed separately.
The bug
npx create-objectstack myappproduces a project with no.gitignore, sonode_modules/and.envare un-ignored for every new user.npm pack/pnpm packstrip.gitignorefrom a tarball unconditionally, at every depth. The blank template committed one atsrc/templates/blank/.gitignoreand tsup faithfully copied it todist/templates/blank/.gitignore, butfiles: ["dist"]publishing dropped it on the way to the registry. The file was present in the repo, present in every local build, and absent from a real scaffold — which is why nothing caught it.Verified against the published 15.1.1 tarball (not inferred):
The strip list is
.gitignoreand.npmrc, not "every dotfile" — probed both packers directly..dockerignoreand.env.examplepack fine, so.dockerignoreis left literal rather than renamed on a guess.The
.envhalfThe template
.gitignorenever listed.enveither. The template README has users write real secrets into one (POSTGRES_PASSWORD,OS_AUTH_SECRET,OS_SECRET_KEY), anddocker-compose.yml:3calls that file "never committed" — but only the prose said so;.dockerignorewas the only file that listed it. So the restored rules now cover.env/.env.*.The fix
Commit the template as
_gitignore(a name npm does not strip) and restore the real name at copy time via aTEMPLATE_FILE_ALIASESmap in the newtemplate-copy.ts.copyDirmoves there so a test can import it without running the CLI (index.tscallsprogram.parse()on import) — the same reasonpkg-utils.tsexists.Verification
Built, packed, installed the tarball into a clean project, and ran the real binary:
The ratchet
template-consistency.test.tspacks the real package, scaffolds from the extracted tarball with the real copy logic, and asserts every template file lands under its intended name. A source-level assertion would be false green here — the file only vanishes at publish. Reading the repo's owndist/would be false green too: turbo'stesttask onlydependsOn: ["^build"](not its own build) and excludesdist/**from its inputs, sodist/is routinely absent or stale, and a pass on it would be cached.Both halves are guarded independently — confirmed by reverting each and watching it go red:
_gitignore→.gitignore(the original bug)The file-set check alone cannot catch the second case (it applies the alias map to both sides, so an empty map agrees with itself on
_gitignore) — which is why the literal.gitignoreassertion is separate and commented as non-redundant.Adding
.npmrcor any other stripped dotfile to the template in future fails the same ratchet with a message naming the fix.🤖 Generated with Claude Code