Skip to content

fix(create-objectstack): ship a .gitignore in scaffolded projects - #3129

Merged
os-zhuang merged 3 commits into
mainfrom
claude/zen-wilson-dde758
Jul 17, 2026
Merged

fix(create-objectstack): ship a .gitignore in scaffolded projects#3129
os-zhuang merged 3 commits into
mainfrom
claude/zen-wilson-dde758

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Found while fixing #3110 (PR #3119) — out of scope there, filed and fixed separately.

The bug

npx create-objectstack myapp produces a project with no .gitignore, so node_modules/ and .env are un-ignored for every new user.

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. 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):

$ tar tzf create-objectstack-15.1.1.tgz | grep templates/blank
package/dist/templates/blank/.dockerignore ← ships
package/dist/templates/blank/Dockerfile
... ← .gitignore absent

The strip list is .gitignore and .npmrc, not "every dotfile" — probed both packers directly. .dockerignore and .env.example pack fine, so .dockerignore is left literal rather than renamed on a guess.

The .env half

The template .gitignore never listed .env either. The template README has users write real secrets into one (POSTGRES_PASSWORD, OS_AUTH_SECRET, OS_SECRET_KEY), and docker-compose.yml:3 calls that file "never committed" — but only the prose said so; .dockerignore was 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 a TEMPLATE_FILE_ALIASES map in the new template-copy.ts. copyDir moves there so a test can import it without running the CLI (index.ts calls program.parse() on import) — the same reason pkg-utils.ts exists.

Verification

Built, packed, installed the tarball into a clean project, and ran the real binary:

 Created files:
+ .gitignore ← was absent
+ .dockerignore
...
$ git add -A # after creating node_modules/ and a .env with secrets
node_modules staged: 0 .env staged: 0

The ratchet

template-consistency.test.ts packs 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 own dist/ would be false green too: turbo's test task only dependsOn: ["^build"] (not its own build) and excludes dist/** from its inputs, so dist/ 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:

revertedresult
rename _gitignore.gitignore (the original bug)2 tests red, error text names the required alias
drop the alias mapping1 test red

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 .gitignore assertion is separate and commented as non-redundant.

Adding .npmrc or any other stripped dotfile to the template in future fails the same ratchet with a message naming the fix.

🤖 Generated with Claude Code

`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>
@vercel

vercelBot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 17, 2026 1:30pm

Request Review

os-zhuangand others added 2 commits July 17, 2026 20:14
…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
@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): create-objectstack.

7 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx(via create-objectstack)
  • content/docs/ai/skills.mdx(via create-objectstack)
  • content/docs/getting-started/index.mdx(via create-objectstack)
  • content/docs/getting-started/your-first-project.mdx(via create-objectstack)
  • content/docs/plugins/packages.mdx(via create-objectstack)
  • content/docs/releases/v15.mdx(via create-objectstack)
  • content/docs/releases/v9.mdx(via create-objectstack)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang