@@ -12,16 +12,20 @@ const packageTemplate = {
1212describe ( 'cli' , ( ) => {
1313 test ( 'applies configuration sourced from package.json' , async ( ) => {
1414 await withTempDir ( async ( cwd ) => {
15+ const modulePath = path . join ( cwd , 'cpconfig.config.mjs' ) ;
16+
17+ await writeFile (
18+ modulePath ,
19+ `export default {\n files: {\n 'secrets/.env': { contents: 'SECRET=1' },\n 'config/app.json': { contents: '{"flag":true}' }\n }\n};\n` ,
20+ ) ;
21+
1522 await writeFile (
1623 path . join ( cwd , 'package.json' ) ,
1724 JSON . stringify (
1825 {
1926 ...packageTemplate ,
20- cpconfig : {
21- files : {
22- 'secrets/.env' : { contents : 'SECRET=1' } ,
23- 'config/app.json' : { contents : '{"flag":true}' } ,
24- } ,
27+ config : {
28+ cpconfig : './cpconfig.config.mjs' ,
2529 } ,
2630 } ,
2731 null ,
@@ -97,15 +101,19 @@ describe('cli', () => {
97101
98102 await writeFile (
99103 modulePath ,
100- `export default async function buildConfig() {\n await Promise.resolve();\n return {\n files: {\n 'factory.txt': { contents: 'from factory' } \n },\n options: {\n gitignorePath: 'generated.ignore'\n }\n };\n}\n` ,
104+ `export default async function buildConfig(config, cliArgs ) {\n await Promise.resolve();\n return {\n files: {\n 'factory.txt': { contents: JSON.stringify({ config, cliArgs }) } \n },\n options: {\n gitignorePath: config?.output ?? 'generated.ignore'\n }\n };\n}\n` ,
101105 ) ;
102106
103107 await writeFile (
104108 path . join ( cwd , 'package.json' ) ,
105109 JSON . stringify (
106110 {
107111 ...packageTemplate ,
108- cpconfig : './cpconfig.factory.mjs' ,
112+ config : {
113+ cpconfig : './cpconfig.factory.mjs' ,
114+ output : 'generated.ignore' ,
115+ feature : 'enabled' ,
116+ } ,
109117 } ,
110118 null ,
111119 2 ,
@@ -115,29 +123,49 @@ describe('cli', () => {
115123 const stdout = createBuffer ( ) ;
116124 const stderr = createBuffer ( ) ;
117125
118- const exitCode = await runCli ( [ ] , { cwd, stdout, stderr } ) ;
126+ const exitCode = await runCli ( [ '--json' ] , { cwd, stdout, stderr } ) ;
119127
120128 expect ( exitCode ) . toBe ( 0 ) ;
121129 expect ( stderr . toString ( ) ) . toBe ( '' ) ;
122130
123- await expect ( readFile ( path . join ( cwd , 'factory.txt' ) , 'utf8' ) ) . resolves . toBe ( 'from factory' ) ;
131+ const fileContents = await readFile ( path . join ( cwd , 'factory.txt' ) , 'utf8' ) ;
132+ const deserialised = JSON . parse ( fileContents ) as {
133+ config : Record < string , unknown > ;
134+ cliArgs : string [ ] ;
135+ } ;
136+
137+ expect ( deserialised . config ) . toMatchObject ( {
138+ cpconfig : './cpconfig.factory.mjs' ,
139+ feature : 'enabled' ,
140+ output : 'generated.ignore' ,
141+ } ) ;
142+ expect ( deserialised . cliArgs ) . toEqual ( [ '--json' ] ) ;
143+
124144 const gitignore = await readFile ( path . join ( cwd , 'generated.ignore' ) , 'utf8' ) ;
125145 expect ( gitignore ) . toContain ( 'factory.txt' ) ;
126- expect ( stdout . toString ( ) ) . toContain ( 'cpconfig.factory.mjs' ) ;
146+ const stdoutJson = JSON . parse ( stdout . toString ( ) ) as {
147+ gitignore : { path : string } ;
148+ } ;
149+ expect ( stdoutJson . gitignore . path ) . toContain ( 'generated.ignore' ) ;
127150 } ) ;
128151 } ) ;
129152
130153 test ( 'supports dry runs without writing files' , async ( ) => {
131154 await withTempDir ( async ( cwd ) => {
155+ const modulePath = path . join ( cwd , 'cpconfig.dry.mjs' ) ;
156+
157+ await writeFile (
158+ modulePath ,
159+ `export default {\n files: {\n 'generated.txt': { contents: 'dry' }\n }\n};\n` ,
160+ ) ;
161+
132162 await writeFile (
133163 path . join ( cwd , 'package.json' ) ,
134164 JSON . stringify (
135165 {
136166 ...packageTemplate ,
137167 config : {
138- cpconfig : {
139- 'generated.txt' : { contents : 'dry' } ,
140- } ,
168+ cpconfig : './cpconfig.dry.mjs' ,
141169 } ,
142170 } ,
143171 null ,
@@ -167,7 +195,97 @@ describe('cli', () => {
167195 const exitCode = await runCli ( [ ] , { cwd, stderr, stdout : createBuffer ( ) } ) ;
168196
169197 expect ( exitCode ) . toBe ( 1 ) ;
170- expect ( stderr . toString ( ) ) . toMatch ( / N o c p c o n f i g d e f i n i t i o n / ) ;
198+ expect ( stderr . toString ( ) ) . toMatch (
199+ / E x p e c t e d c o n f i g \. c p c o n f i g t o r e f e r e n c e a m o d u l e u s i n g a s t r i n g / ,
200+ ) ;
201+ } ) ;
202+ } ) ;
203+
204+ test ( 'warns when an existing file is missing the configured sentinel' , async ( ) => {
205+ await withTempDir ( async ( cwd ) => {
206+ const modulePath = path . join ( cwd , 'cpconfig.sentinel.mjs' ) ;
207+
208+ await writeFile (
209+ modulePath ,
210+ `export default {\n files: {\n 'managed.txt': { contents: '// sentinel\\nmanaged=true\\n', sentinel: '// sentinel' }\n }\n};\n` ,
211+ ) ;
212+
213+ await writeFile (
214+ path . join ( cwd , 'package.json' ) ,
215+ JSON . stringify (
216+ {
217+ ...packageTemplate ,
218+ config : {
219+ cpconfig : './cpconfig.sentinel.mjs' ,
220+ } ,
221+ } ,
222+ null ,
223+ 2 ,
224+ ) ,
225+ ) ;
226+
227+ const existingPath = path . join ( cwd , 'managed.txt' ) ;
228+ await writeFile ( existingPath , 'managed=false\n' , 'utf8' ) ;
229+
230+ const stdout = createBuffer ( ) ;
231+ const stderr = createBuffer ( ) ;
232+
233+ const exitCode = await runCli ( [ ] , { cwd, stdout, stderr } ) ;
234+
235+ expect ( exitCode ) . toBe ( 0 ) ;
236+ expect ( stderr . toString ( ) ) . toMatch ( / N o t o v e r w r i t i n g " m a n a g e d .t x t " / ) ;
237+
238+ await expect ( readFile ( existingPath , 'utf8' ) ) . resolves . toBe ( 'managed=false\n' ) ;
239+ await expect ( readFile ( path . join ( cwd , '.gitignore' ) , 'utf8' ) ) . rejects . toThrow ( ) ;
240+ expect ( stdout . toString ( ) ) . toContain ( 'managed.txt (unmanaged)' ) ;
241+ } ) ;
242+ } ) ;
243+
244+ test ( 'emits helpful errors when cpconfig is defined at the package root' , async ( ) => {
245+ await withTempDir ( async ( cwd ) => {
246+ await writeFile (
247+ path . join ( cwd , 'package.json' ) ,
248+ JSON . stringify (
249+ {
250+ ...packageTemplate ,
251+ cpconfig : './cpconfig.config.mjs' ,
252+ } ,
253+ null ,
254+ 2 ,
255+ ) ,
256+ ) ;
257+
258+ const stderr = createBuffer ( ) ;
259+ const exitCode = await runCli ( [ ] , { cwd, stderr, stdout : createBuffer ( ) } ) ;
260+
261+ expect ( exitCode ) . toBe ( 1 ) ;
262+ expect ( stderr . toString ( ) ) . toMatch ( / M o v e t h e v a l u e t o c o n f i g \. c p c o n f i g / ) ;
263+ } ) ;
264+ } ) ;
265+
266+ test ( 'emits helpful errors when config.cpconfig is not a string' , async ( ) => {
267+ await withTempDir ( async ( cwd ) => {
268+ await writeFile (
269+ path . join ( cwd , 'package.json' ) ,
270+ JSON . stringify (
271+ {
272+ ...packageTemplate ,
273+ config : {
274+ cpconfig : { invalid : true } ,
275+ } ,
276+ } ,
277+ null ,
278+ 2 ,
279+ ) ,
280+ ) ;
281+
282+ const stderr = createBuffer ( ) ;
283+ const exitCode = await runCli ( [ ] , { cwd, stderr, stdout : createBuffer ( ) } ) ;
284+
285+ expect ( exitCode ) . toBe ( 1 ) ;
286+ expect ( stderr . toString ( ) ) . toMatch (
287+ / E x p e c t e d c o n f i g \. c p c o n f i g t o b e a n o n - e m p t y s t r i n g m o d u l e s p e c i f i e r / ,
288+ ) ;
171289 } ) ;
172290 } ) ;
173291} ) ;
0 commit comments