@@ -6,7 +6,24 @@ import Plugin from '../src/index.ts';
66
77// Type definitions for mocking
88type OnLoadConfig = { filter : RegExp } ;
9- type OnLoadCallback = ( args : { path : string } ) => Promise < { contents : string ; loader : string } > ;
9+ type OnLoadResultSource = { contents : string ; loader : string } ;
10+ type OnLoadResultObject = { exports : unknown ; loader : string } ;
11+ type OnLoadResult = OnLoadResultSource | OnLoadResultObject ;
12+ type OnLoadCallback = ( args : { path : string } ) => Promise < OnLoadResult > ;
13+
14+ /**
15+ * Type guard to check if result is a source code result
16+ */
17+ function isSourceResult ( result : OnLoadResult ) : result is OnLoadResultSource {
18+ return 'contents' in result ;
19+ }
20+
21+ /**
22+ * Type guard to check if result is an object result
23+ */
24+ function isObjectResult ( result : OnLoadResult ) : result is OnLoadResultObject {
25+ return 'exports' in result ;
26+ }
1027
1128/**
1229 * Helper to test async rejections in Bun 1.0+
@@ -111,6 +128,7 @@ console.log square 5
111128 expect ( result ) . toHaveProperty ( 'contents' ) ;
112129 expect ( result ) . toHaveProperty ( 'loader' ) ;
113130 expect ( result . loader ) . toBe ( 'js' ) ;
131+ if ( ! isSourceResult ( result ) ) throw new Error ( 'Expected source result' ) ;
114132 expect ( typeof result . contents ) . toBe ( 'string' ) ;
115133 expect ( result . contents ) . toContain ( 'square' ) ;
116134 } ) ;
@@ -144,6 +162,7 @@ dog.speak()
144162 if ( ! onLoadCallback ) throw new Error ( 'onLoad was not called' ) ;
145163 const result = await onLoadCallback ( { path : coffeeFile } ) ;
146164
165+ if ( ! isSourceResult ( result ) ) throw new Error ( 'Expected source result' ) ;
147166 expect ( result . contents ) . toContain ( 'Animal' ) ;
148167 expect ( result . contents ) . toContain ( 'constructor' ) ;
149168 expect ( result . contents ) . toContain ( 'speak' ) ;
@@ -188,6 +207,7 @@ dog.speak()
188207 const result = await onLoadCallback ( { path : coffeeFile } ) ;
189208
190209 // When bare: true, the output should not be wrapped in a function
210+ if ( ! isSourceResult ( result ) ) throw new Error ( 'Expected source result' ) ;
191211 expect ( result . contents ) . toContain ( 'add' ) ;
192212 expect ( result . contents ) . not . toContain ( '(function()' ) ;
193213 } ) ;
@@ -215,6 +235,7 @@ dog.speak()
215235 const result = await onLoadCallback ( { path : coffeeFile } ) ;
216236
217237 // Result should not contain inline source map
238+ if ( ! isSourceResult ( result ) ) throw new Error ( 'Expected source result' ) ;
218239 expect ( result . contents ) . toBeDefined ( ) ;
219240 expect ( typeof result . contents ) . toBe ( 'string' ) ;
220241 } ) ;
@@ -239,6 +260,7 @@ dog.speak()
239260 if ( ! onLoadCallback ) throw new Error ( 'onLoad was not called' ) ;
240261 const result = await onLoadCallback ( { path : coffeeFile } ) ;
241262
263+ if ( ! isSourceResult ( result ) ) throw new Error ( 'Expected source result' ) ;
242264 expect ( result . contents ) . toBeDefined ( ) ;
243265 expect ( result . loader ) . toBe ( 'js' ) ;
244266 } ) ;
@@ -363,12 +385,214 @@ More documentation here.
363385 if ( ! onLoadCallback ) throw new Error ( 'onLoad was not called' ) ;
364386 const result = await onLoadCallback ( { path : litcoffeeFile } ) ;
365387
388+ if ( ! isSourceResult ( result ) ) throw new Error ( 'Expected source result' ) ;
366389 expect ( result . contents ) . toBeDefined ( ) ;
367390 expect ( result . loader ) . toBe ( 'js' ) ;
368391 expect ( result . contents ) . toContain ( 'square' ) ;
369392 } ) ;
370393 } ) ;
371394
395+ describe ( 'CSON support' , ( ) => {
396+ let tempDir : string ;
397+
398+ beforeEach ( async ( ) => {
399+ tempDir = await mkdtemp ( join ( tmpdir ( ) , 'bun-cson-' ) ) ;
400+ } ) ;
401+
402+ afterEach ( async ( ) => {
403+ await rm ( tempDir , { recursive : true , force : true } ) ;
404+ } ) ;
405+
406+ test ( 'matches .cson files in filter' , ( ) => {
407+ const plugin = Plugin ( ) ;
408+ let filterRegex : RegExp | undefined ;
409+
410+ const mockBuilder = {
411+ onLoad : mock ( ( config : OnLoadConfig , _callback : OnLoadCallback ) => {
412+ filterRegex = config . filter ;
413+ } ) ,
414+ } ;
415+
416+ // biome-ignore lint/suspicious/noExplicitAny: Mock builder for testing
417+ plugin . setup ( mockBuilder as any ) ;
418+
419+ if ( ! filterRegex ) throw new Error ( 'onLoad was not called' ) ;
420+ expect ( filterRegex . test ( 'config.cson' ) ) . toBe ( true ) ;
421+ expect ( filterRegex . test ( 'package.cson' ) ) . toBe ( true ) ;
422+ expect ( filterRegex . test ( 'data/settings.cson' ) ) . toBe ( true ) ;
423+ } ) ;
424+
425+ test ( 'parses CSON files' , async ( ) => {
426+ const csonFile = join ( tempDir , 'data.cson' ) ;
427+ const csonSource = `
428+ # CSON Configuration
429+ name: "test-package"
430+ version: "1.0.0"
431+ config:
432+ enabled: true
433+ count: 42
434+ items: [
435+ "one"
436+ "two"
437+ "three"
438+ ]
439+ ` ;
440+ await writeFile ( csonFile , csonSource ) ;
441+
442+ const plugin = Plugin ( ) ;
443+ let onLoadCallback : OnLoadCallback | undefined ;
444+
445+ const mockBuilder = {
446+ onLoad : mock ( ( _config : OnLoadConfig , callback : OnLoadCallback ) => {
447+ onLoadCallback = callback ;
448+ } ) ,
449+ } ;
450+
451+ // biome-ignore lint/suspicious/noExplicitAny: Mock builder for testing
452+ plugin . setup ( mockBuilder as any ) ;
453+
454+ if ( ! onLoadCallback ) throw new Error ( 'onLoad was not called' ) ;
455+ const result = await onLoadCallback ( { path : csonFile } ) ;
456+
457+ expect ( result ) . toHaveProperty ( 'exports' ) ;
458+ expect ( result ) . toHaveProperty ( 'loader' ) ;
459+ expect ( result . loader ) . toBe ( 'object' ) ;
460+ if ( ! isObjectResult ( result ) ) throw new Error ( 'Expected object result' ) ;
461+ expect ( result . exports ) . toEqual ( {
462+ name : 'test-package' ,
463+ version : '1.0.0' ,
464+ config : {
465+ enabled : true ,
466+ count : 42 ,
467+ items : [ 'one' , 'two' , 'three' ] ,
468+ } ,
469+ } ) ;
470+ } ) ;
471+
472+ test ( 'handles simple CSON objects' , async ( ) => {
473+ const csonFile = join ( tempDir , 'simple.cson' ) ;
474+ const csonSource = `
475+ key: "value"
476+ number: 123
477+ boolean: true
478+ ` ;
479+ await writeFile ( csonFile , csonSource ) ;
480+
481+ const plugin = Plugin ( ) ;
482+ let onLoadCallback : OnLoadCallback | undefined ;
483+
484+ const mockBuilder = {
485+ onLoad : mock ( ( _config : OnLoadConfig , callback : OnLoadCallback ) => {
486+ onLoadCallback = callback ;
487+ } ) ,
488+ } ;
489+
490+ // biome-ignore lint/suspicious/noExplicitAny: Mock builder for testing
491+ plugin . setup ( mockBuilder as any ) ;
492+
493+ if ( ! onLoadCallback ) throw new Error ( 'onLoad was not called' ) ;
494+ const result = await onLoadCallback ( { path : csonFile } ) ;
495+
496+ if ( ! isObjectResult ( result ) ) throw new Error ( 'Expected object result' ) ;
497+ expect ( result . exports ) . toEqual ( {
498+ key : 'value' ,
499+ number : 123 ,
500+ boolean : true ,
501+ } ) ;
502+ } ) ;
503+
504+ test ( 'handles nested CSON structures' , async ( ) => {
505+ const csonFile = join ( tempDir , 'nested.cson' ) ;
506+ const csonSource = `
507+ database:
508+ host: "localhost"
509+ port: 5432
510+ credentials:
511+ username: "admin"
512+ password: "secret"
513+ options:
514+ ssl: true
515+ poolSize: 10
516+ ` ;
517+ await writeFile ( csonFile , csonSource ) ;
518+
519+ const plugin = Plugin ( ) ;
520+ let onLoadCallback : OnLoadCallback | undefined ;
521+
522+ const mockBuilder = {
523+ onLoad : mock ( ( _config : OnLoadConfig , callback : OnLoadCallback ) => {
524+ onLoadCallback = callback ;
525+ } ) ,
526+ } ;
527+
528+ // biome-ignore lint/suspicious/noExplicitAny: Mock builder for testing
529+ plugin . setup ( mockBuilder as any ) ;
530+
531+ if ( ! onLoadCallback ) throw new Error ( 'onLoad was not called' ) ;
532+ const result = await onLoadCallback ( { path : csonFile } ) ;
533+
534+ if ( ! isObjectResult ( result ) ) throw new Error ( 'Expected object result' ) ;
535+ expect ( result . exports ) . toHaveProperty ( 'database' ) ;
536+ // biome-ignore lint/suspicious/noExplicitAny: Testing dynamic CSON structure
537+ expect ( ( result . exports as any ) . database ) . toHaveProperty ( 'credentials' ) ;
538+ // biome-ignore lint/suspicious/noExplicitAny: Testing dynamic CSON structure
539+ expect ( ( result . exports as any ) . database . credentials . username ) . toBe ( 'admin' ) ;
540+ } ) ;
541+
542+ test ( 'handles CSON arrays' , async ( ) => {
543+ const csonFile = join ( tempDir , 'array.cson' ) ;
544+ const csonSource = `[
545+ "item1"
546+ "item2"
547+ "item3"
548+ ]` ;
549+ await writeFile ( csonFile , csonSource ) ;
550+
551+ const plugin = Plugin ( ) ;
552+ let onLoadCallback : OnLoadCallback | undefined ;
553+
554+ const mockBuilder = {
555+ onLoad : mock ( ( _config : OnLoadConfig , callback : OnLoadCallback ) => {
556+ onLoadCallback = callback ;
557+ } ) ,
558+ } ;
559+
560+ // biome-ignore lint/suspicious/noExplicitAny: Mock builder for testing
561+ plugin . setup ( mockBuilder as any ) ;
562+
563+ if ( ! onLoadCallback ) throw new Error ( 'onLoad was not called' ) ;
564+ const result = await onLoadCallback ( { path : csonFile } ) ;
565+
566+ if ( ! isObjectResult ( result ) ) throw new Error ( 'Expected object result' ) ;
567+ expect ( Array . isArray ( result . exports ) ) . toBe ( true ) ;
568+ expect ( result . exports ) . toEqual ( [ 'item1' , 'item2' , 'item3' ] ) ;
569+ } ) ;
570+
571+ test ( 'handles invalid CSON syntax' , async ( ) => {
572+ const csonFile = join ( tempDir , 'invalid.cson' ) ;
573+ const invalidSource = `
574+ key: "value
575+ missing closing quote
576+ ` ;
577+ await writeFile ( csonFile , invalidSource ) ;
578+
579+ const plugin = Plugin ( ) ;
580+ let onLoadCallback : OnLoadCallback | undefined ;
581+
582+ const mockBuilder = {
583+ onLoad : mock ( ( _config : OnLoadConfig , callback : OnLoadCallback ) => {
584+ onLoadCallback = callback ;
585+ } ) ,
586+ } ;
587+
588+ // biome-ignore lint/suspicious/noExplicitAny: Mock builder for testing
589+ plugin . setup ( mockBuilder as any ) ;
590+
591+ if ( ! onLoadCallback ) throw new Error ( 'onLoad was not called' ) ;
592+ await expectToReject ( onLoadCallback ( { path : csonFile } ) ) ;
593+ } ) ;
594+ } ) ;
595+
372596 describe ( 'Error handling' , ( ) => {
373597 let tempDir : string ;
374598
@@ -461,6 +685,7 @@ result = add 2, 3
461685 const result = await onLoadCallback ( { path : coffeeFile } ) ;
462686
463687 // Try to evaluate the compiled JavaScript (basic syntax check)
688+ if ( ! isSourceResult ( result ) ) throw new Error ( 'Expected source result' ) ;
464689 expect ( ( ) => {
465690 new Function ( result . contents ) ;
466691 } ) . not . toThrow ( ) ;
@@ -488,6 +713,7 @@ result = add 2, 3
488713 const result = await onLoadCallback ( { path : coffeeFile } ) ;
489714
490715 expect ( result ) . toBeDefined ( ) ;
716+ if ( ! isSourceResult ( result ) ) throw new Error ( 'Expected source result' ) ;
491717 expect ( result . contents ) . toBeDefined ( ) ;
492718 // The compilation succeeds, meaning filename was passed correctly
493719 } ) ;
0 commit comments