@@ -74,13 +74,12 @@ describe('blank template manifest engines.protocol (ADR-0087 D1)', () => {
7474 } ) ;
7575} ) ;
7676
77- // Packing ratchet (#3120): every file the blank template ships must survive a
78- // real `npm pack` and land in a scaffold under its intended name. `.gitignore`
79- // did not — npm strips it from the tarball at every depth, so the file the
80- // build had faithfully copied to dist/templates/blank/ was dropped at publish
81- // and every scaffolded project came out with no `.gitignore`, leaving
82- // node_modules/ and the secret-bearing .env from the template README
83- // un-ignored for every new user.
77+ // Packing ratchet (#3120): every template file must survive a real `npm pack`
78+ // and land in a scaffold under its intended name. `.gitignore` did not — npm
79+ // strips it from the tarball at every depth, so the file the build had
80+ // faithfully copied to dist/templates/blank/ was dropped at publish and every
81+ // scaffolded project came out with no `.gitignore`, leaving node_modules/ and
82+ // the secret-bearing .env from the template README un-ignored for every user.
8483//
8584// This bug is invisible to source-level assertions: the file is present in
8685// src/templates/, present in a local build, and only vanishes at publish. So
@@ -89,8 +88,9 @@ describe('blank template manifest engines.protocol (ADR-0087 D1)', () => {
8988// `test` task only dependsOn `^build` (not its own build) and excludes dist/**
9089// from its inputs, so dist/ here is routinely absent or stale, and a pass on it
9190// would be cached.
92- describe ( 'blank template survives npm packing' , ( ) => {
93- const blankSrc = path . join ( pkgRoot , 'src' , 'templates' , 'blank' ) ;
91+ describe ( 'templates survive npm packing' , ( ) => {
92+ const templatesSrc = path . join ( pkgRoot , 'src' , 'templates' ) ;
93+ const blankSrc = path . join ( templatesSrc , 'blank' ) ;
9494
9595 const walkRel = ( dir : string , rel = '' ) : string [ ] =>
9696 fs . readdirSync ( dir , { withFileTypes : true } ) . flatMap ( ( entry ) => {
@@ -109,6 +109,7 @@ describe('blank template survives npm packing', () => {
109109 } ;
110110
111111 let tmp : string ;
112+ let packed : string [ ] ;
112113 let scaffolded : string [ ] ;
113114 let collected : string [ ] ;
114115
@@ -120,14 +121,13 @@ describe('blank template survives npm packing', () => {
120121 // dist/templates, which is what tsup.config.ts's onSuccess hook does. The
121122 // test asserts that mirror below rather than assuming it.
122123 fs . cpSync ( path . join ( pkgRoot , 'package.json' ) , path . join ( tmp , 'package.json' ) ) ;
123- fs . cpSync ( path . join ( pkgRoot , 'src' , 'templates' ) , path . join ( tmp , 'dist' , 'templates' ) , {
124- recursive : true ,
125- } ) ;
124+ fs . cpSync ( templatesSrc , path . join ( tmp , 'dist' , 'templates' ) , { recursive : true } ) ;
126125
127126 execFileSync ( 'npm' , [ 'pack' , '--ignore-scripts' ] , { cwd : tmp , stdio : 'pipe' } ) ;
128127 const tgz = fs . readdirSync ( tmp ) . find ( ( f ) => f . endsWith ( '.tgz' ) ) ;
129128 if ( ! tgz ) throw new Error ( `npm pack produced no tarball in ${ tmp } ` ) ;
130129 execFileSync ( 'tar' , [ 'xzf' , tgz ] , { cwd : tmp } ) ;
130+ packed = walkRel ( path . join ( tmp , 'package' , 'dist' , 'templates' ) ) ;
131131
132132 // Scaffold from the *packed* template with the real copy logic.
133133 const out = path . join ( tmp , 'scaffold' ) ;
@@ -150,15 +150,21 @@ describe('blank template survives npm packing', () => {
150150 ) . toContain ( "cpSync('src/templates', 'dist/templates', { recursive: true })" ) ;
151151 } ) ;
152152
153- it ( 'lands every template file — dotfiles included — in the scaffold' , ( ) => {
154- const expected = walkRel ( blankSrc ) . map ( scaffoldedAs ) . sort ( ) ;
153+ // Covers every bundled template, not just blank: the tarball must carry
154+ // src/templates/** verbatim (aliases are a scaffold-time concern).
155+ it ( 'carries every src/templates file into the tarball' , ( ) => {
155156 expect (
156- scaffolded . sort ( ) ,
157- 'a file in src/templates/blank/ did not survive `npm pack` ( npm strips ' +
158- '.gitignore and .npmrc from tarballs at every depth). Commit it under a ' +
157+ packed . sort ( ) ,
158+ 'a file under src/templates/ did not survive `npm pack`. npm strips ' +
159+ '.gitignore and .npmrc from tarballs at every depth — commit it under a ' +
159160 'placeholder name and map it back in TEMPLATE_FILE_ALIASES, the way ' +
160161 '_gitignore → .gitignore works.' ,
161- ) . toEqual ( expected ) ;
162+ ) . toEqual ( walkRel ( templatesSrc ) . sort ( ) ) ;
163+ } ) ;
164+
165+ it ( 'restores the aliased names when scaffolding' , ( ) => {
166+ const expected = walkRel ( blankSrc ) . map ( scaffoldedAs ) . sort ( ) ;
167+ expect ( scaffolded . sort ( ) ) . toEqual ( expected ) ;
162168 // What the CLI prints as "Created files:" must match what it actually wrote.
163169 expect ( collected . sort ( ) ) . toEqual ( expected ) ;
164170 } ) ;
0 commit comments