From 8375e4bb522d3babc31f63ab20edfe2cd26379c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Ba=CC=88chler?= Date: Thu, 9 Jul 2026 19:50:30 +0200 Subject: [PATCH] Protect Gitignore, exclude generated files. npm removed the .gitignore file from the template when publishing, so the file has been renamed to gitignore. But then all the previously ignored files were added to the package, so that's why those files are now explicitly excluded. --- create/main.js | 18 ++++++++++++++++++ package.json | 15 +++++++++++++-- template/{.gitignore => gitignore} | 3 +++ tests/generator.test.mjs | 11 +++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) rename template/{.gitignore => gitignore} (86%) diff --git a/create/main.js b/create/main.js index 553149d..1df8092 100644 --- a/create/main.js +++ b/create/main.js @@ -22,6 +22,10 @@ export async function main({ cwd, templateDir, exampleTemplatesDir, targetDir, e errorOnExist: true, }); + // npm strips `.gitignore` from published packages, so the template ships it as + // `gitignore`. Restore the dotfile name in the generated project. + await restoreGitignore(targetDir); + const EXAMPLE_ENV_PATH = path.join(targetDir, '.env.example'); const ENV_PATH = path.join(targetDir, '.env'); const PKG_PATH = path.join(targetDir, 'package.json'); @@ -100,6 +104,8 @@ export async function main({ cwd, templateDir, exampleTemplatesDir, targetDir, e errorOnExist: true, }); + await restoreGitignore(exampleTargetDir); + // Apply the same token replacements as for the main project, remapped to exampleTargetDir. const remap = (f) => path.join(exampleTargetDir, path.relative(targetDir, f)); await replaceTokensInFiles(filesWithAppTitle.map(remap), appTitleRegex, APP_TITLE); @@ -148,6 +154,18 @@ What's next? ); } +async function restoreGitignore(dir) { + const shipped = path.join(dir, 'gitignore'); + const dotfile = path.join(dir, '.gitignore'); + try { + await fs.rename(shipped, dotfile); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } +} + async function mergeExampleFiles(sourceDir, targetDir) { const entries = await fs.readdir(sourceDir, { recursive: true, withFileTypes: true }); for (const entry of entries) { diff --git a/package.json b/package.json index bfbae1c..58ced1a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,17 @@ "files": [ "template", "example-templates", - "create" + "create", + "!**/node_modules/**", + "!**/dist/**", + "!**/build/**", + "!**/.turbo/**", + "!**/.react-router/**", + "!**/coverage/**", + "!**/data-mocks/**", + "!**/.cache/**", + "!**/*.tsbuildinfo", + "!**/package-lock.json" ], "engines": { "node": ">=22.3.0" @@ -33,7 +43,8 @@ "create-tinker-stack" ], "scripts": { - "prerelease": "find template example-templates -name package-lock.json -delete && find template example-templates -name tsconfig.tsbuildinfo -delete && find template example-templates -name .turbo -type d -prune -exec rm -rf {} +", + "clean:template": "find template example-templates -type d \\( -name node_modules -o -name dist -o -name build -o -name .turbo -o -name .react-router -o -name coverage -o -name data-mocks -o -name .cache \\) -prune -exec rm -rf {} + && find template example-templates -type f \\( -name package-lock.json -o -name '*.tsbuildinfo' \\) -delete", + "prerelease": "npm run clean:template", "release": "commit-and-tag-version", "test": "npm run test:e2e", "test:e2e": "vitest run --no-file-parallelism", diff --git a/template/.gitignore b/template/gitignore similarity index 86% rename from template/.gitignore rename to template/gitignore index a6fecbd..cf137cd 100644 --- a/template/.gitignore +++ b/template/gitignore @@ -39,6 +39,9 @@ data-mocks/ vite.config.ts.timestamp-* .playwright-mcp/ +# Example templates (self-contained, generated into examples//) +examples/ + # Misc .DS_Store *.pem diff --git a/tests/generator.test.mjs b/tests/generator.test.mjs index 4f3095b..87c2472 100644 --- a/tests/generator.test.mjs +++ b/tests/generator.test.mjs @@ -52,6 +52,17 @@ describe('create-tinker-stack generator', () => { ); expect(examplePackageJson.scripts).toHaveProperty('build'); + // npm strips `.gitignore` from published packages, so the template ships + // it as `gitignore` and the generator restores the dotfile name. + const gitignore = await readFile(path.join(projectDir, '.gitignore'), 'utf8'); + expect(gitignore).toContain('node_modules'); + expect(gitignore).toContain('examples/'); + await expect(readFile(path.join(projectDir, 'gitignore'), 'utf8')) + .rejects.toMatchObject({ code: 'ENOENT' }); + await expect(readFile(path.join(exampleDir, '.gitignore'), 'utf8')).resolves.toContain( + 'node_modules' + ); + await runCommand('npm', ['install'], { cwd: projectDir }); await runCommand('npm', ['run', 'typecheck'], { cwd: projectDir }); await runCommand('npm', ['run', 'build:data'], { cwd: projectDir });