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:

reverted result
rename _gitignore.gitignore (the original bug) 2 tests red, error text names the required alias
drop the alias mapping 1 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

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 17, 2026 1:30pm

Request Review

os-zhuang and 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-actions github-actions Bot 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.

@os-zhuang
os-zhuang merged commit 3b6ef8a into main Jul 17, 2026
17 of 18 checks passed
@os-zhuang
os-zhuang deleted the claude/zen-wilson-dde758 branch July 17, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant