@mui/internal-code-infra 0.0.4-canary.56 → 0.0.4-canary.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import * as path from 'node:path';
3
- import { describe, expect, it } from 'vitest';
3
+ import { describe, expect, it, vi } from 'vitest';
4
4
 
5
5
  import { makeTempDir } from './testUtils.mjs';
6
- import { createPackageBin, createPackageExports } from './build.mjs';
6
+ import { createPackageBin, createPackageExports, createPackageImports } from './build.mjs';
7
7
 
8
8
  /**
9
9
  * @param {string} filePath
@@ -29,20 +29,22 @@ describe('createPackageExports', () => {
29
29
  ]);
30
30
 
31
31
  // Pass cjs before esm to verify the key order is still 'import' then 'require'
32
- const { exports: packageExports } = await createPackageExports({
33
- exports: {
32
+ const { exports: packageExports } = await createPackageExports(
33
+ {
34
34
  '.': './src/index.ts',
35
35
  './feature': './src/feature.ts',
36
36
  },
37
- bundles: [
38
- { type: 'cjs', dir: '.' },
39
- { type: 'esm', dir: '.' },
40
- ],
41
- outputDir,
42
- cwd,
43
- isFlat: true,
44
- packageType: 'module',
45
- });
37
+ {
38
+ bundles: [
39
+ { type: 'cjs', dir: '.' },
40
+ { type: 'esm', dir: '.' },
41
+ ],
42
+ outputDir,
43
+ cwd,
44
+ isFlat: true,
45
+ packageType: 'module',
46
+ },
47
+ );
46
48
 
47
49
  expect(Object.keys(/** @type {Record<string, unknown>} */ (packageExports['.']))).toEqual([
48
50
  'import',
@@ -86,18 +88,20 @@ describe('createPackageExports', () => {
86
88
  exports: packageExports,
87
89
  main,
88
90
  types,
89
- } = await createPackageExports({
90
- exports: {
91
+ } = await createPackageExports(
92
+ {
91
93
  '.': './src/index.ts',
92
94
  './feature': './src/feature.ts',
93
95
  },
94
- bundles,
95
- outputDir,
96
- cwd,
97
- addTypes: true,
98
- isFlat: true,
99
- packageType: 'module',
100
- });
96
+ {
97
+ bundles,
98
+ outputDir,
99
+ cwd,
100
+ addTypes: true,
101
+ isFlat: true,
102
+ packageType: 'module',
103
+ },
104
+ );
101
105
 
102
106
  expect(main).toBe('./index.cjs');
103
107
  expect(types).toBe('./index.d.cts');
@@ -123,17 +127,19 @@ describe('createPackageExports', () => {
123
127
  exports: packageExports2,
124
128
  main: main2,
125
129
  types: types2,
126
- } = await createPackageExports({
127
- exports: {
130
+ } = await createPackageExports(
131
+ {
128
132
  '.': './src/index.ts',
129
133
  './feature': './src/feature.ts',
130
134
  },
131
- bundles: [bundles[1]], // only CJS bundle
132
- outputDir,
133
- cwd,
134
- addTypes: true,
135
- isFlat: true,
136
- });
135
+ {
136
+ bundles: [bundles[1]], // only CJS bundle
137
+ outputDir,
138
+ cwd,
139
+ addTypes: true,
140
+ isFlat: true,
141
+ },
142
+ );
137
143
 
138
144
  expect(main2).toBe('./index.js');
139
145
  expect(types2).toBe('./index.d.ts');
@@ -181,17 +187,19 @@ describe('createPackageExports', () => {
181
187
  createFile(path.join(outputDir, 'TextField.d.cts')),
182
188
  ]);
183
189
 
184
- const { exports: packageExports } = await createPackageExports({
185
- exports: {
190
+ const { exports: packageExports } = await createPackageExports(
191
+ {
186
192
  './*': './src/*.ts',
187
193
  },
188
- bundles,
189
- outputDir,
190
- cwd,
191
- addTypes: true,
192
- isFlat: true,
193
- packageType: 'module',
194
- });
194
+ {
195
+ bundles,
196
+ outputDir,
197
+ cwd,
198
+ addTypes: true,
199
+ isFlat: true,
200
+ packageType: 'module',
201
+ },
202
+ );
195
203
 
196
204
  expect(packageExports['./Button']).toEqual({
197
205
  import: { types: './Button.d.ts', default: './Button.js' },
@@ -227,17 +235,19 @@ describe('createPackageExports', () => {
227
235
  createFile(path.join(outputDir, 'Button.d.mts')),
228
236
  ]);
229
237
 
230
- const { exports: packageExports } = await createPackageExports({
231
- exports: {
238
+ const { exports: packageExports } = await createPackageExports(
239
+ {
232
240
  './*': './src/*.ts',
233
241
  },
234
- bundles,
235
- outputDir,
236
- cwd,
237
- addTypes: true,
238
- isFlat: true,
239
- packageType: 'commonjs',
240
- });
242
+ {
243
+ bundles,
244
+ outputDir,
245
+ cwd,
246
+ addTypes: true,
247
+ isFlat: true,
248
+ packageType: 'commonjs',
249
+ },
250
+ );
241
251
 
242
252
  expect(packageExports['./Button']).toEqual({
243
253
  import: { types: './Button.d.mts', default: './Button.mjs' },
@@ -257,17 +267,19 @@ describe('createPackageExports', () => {
257
267
  createFile(path.join(outputDir, 'Button.d.ts')),
258
268
  ]);
259
269
 
260
- const { exports: packageExports } = await createPackageExports({
261
- exports: {
270
+ const { exports: packageExports } = await createPackageExports(
271
+ {
262
272
  './*': './src/*.ts',
263
273
  },
264
- bundles: [{ type: 'cjs', dir: '.' }],
265
- outputDir,
266
- cwd,
267
- addTypes: true,
268
- isFlat: true,
269
- packageType: 'commonjs',
270
- });
274
+ {
275
+ bundles: [{ type: 'cjs', dir: '.' }],
276
+ outputDir,
277
+ cwd,
278
+ addTypes: true,
279
+ isFlat: true,
280
+ packageType: 'commonjs',
281
+ },
282
+ );
271
283
 
272
284
  expect(packageExports['./Button']).toEqual({
273
285
  require: { types: './Button.d.ts', default: './Button.js' },
@@ -275,7 +287,7 @@ describe('createPackageExports', () => {
275
287
  });
276
288
  });
277
289
 
278
- it('expands glob patterns with mui-src object values', async () => {
290
+ it('rewrites source paths inside sibling conditions when expanding globs', async () => {
279
291
  const cwd = await makeTempDir();
280
292
  const outputDir = path.join(cwd, 'build');
281
293
  /**
@@ -288,30 +300,39 @@ describe('createPackageExports', () => {
288
300
 
289
301
  await Promise.all([
290
302
  createFile(path.join(cwd, 'src/Alert.ts')),
291
-
292
- createFile(path.join(outputDir, 'Alert.js')),
293
- createFile(path.join(outputDir, 'Alert.cjs')),
303
+ createFile(path.join(cwd, 'src/node/Alert.ts')),
294
304
  ]);
295
305
 
296
- const { exports: packageExports } = await createPackageExports({
297
- exports: {
298
- './*': { 'mui-src': './src/*.ts' },
306
+ const { exports: packageExports } = await createPackageExports(
307
+ {
308
+ './*': { node: './src/node/*.ts', default: './src/*.ts' },
299
309
  },
300
- bundles,
301
- outputDir,
302
- cwd,
303
- isFlat: true,
304
- packageType: 'module',
305
- });
310
+ {
311
+ bundles,
312
+ outputDir,
313
+ cwd,
314
+ isFlat: true,
315
+ packageType: 'module',
316
+ },
317
+ );
306
318
 
319
+ // Every condition's source path is rewritten (no verbatim `./src/...`),
320
+ // and conditions stay outer with the import/require split at the leaves.
307
321
  expect(packageExports['./Alert']).toEqual({
308
- import: './Alert.js',
309
- require: './Alert.cjs',
310
- default: './Alert.js',
322
+ node: {
323
+ import: './node/Alert.js',
324
+ require: './node/Alert.cjs',
325
+ default: './node/Alert.js',
326
+ },
327
+ default: {
328
+ import: './Alert.js',
329
+ require: './Alert.cjs',
330
+ default: './Alert.js',
331
+ },
311
332
  });
312
333
  });
313
334
 
314
- it('preserves extra conditions from mui-src object values', async () => {
335
+ it('omits a sibling condition for stems its glob does not match', async () => {
315
336
  const cwd = await makeTempDir();
316
337
  const outputDir = path.join(cwd, 'build');
317
338
  /**
@@ -324,29 +345,97 @@ describe('createPackageExports', () => {
324
345
 
325
346
  await Promise.all([
326
347
  createFile(path.join(cwd, 'src/Alert.ts')),
327
-
328
- createFile(path.join(outputDir, 'Alert.js')),
329
- createFile(path.join(outputDir, 'Alert.cjs')),
348
+ createFile(path.join(cwd, 'src/Button.ts')),
349
+ // Only Alert has a node variant.
350
+ createFile(path.join(cwd, 'src/node/Alert.ts')),
330
351
  ]);
331
352
 
332
- const { exports: packageExports } = await createPackageExports({
333
- exports: {
334
- './*': { 'mui-src': './src/*.ts', node: './src/node/*.ts' },
353
+ const { exports: packageExports } = await createPackageExports(
354
+ {
355
+ './*': { node: './src/node/*.ts', default: './src/*.ts' },
335
356
  },
336
- bundles,
337
- outputDir,
338
- cwd,
339
- isFlat: true,
340
- packageType: 'module',
341
- });
357
+ {
358
+ bundles,
359
+ outputDir,
360
+ cwd,
361
+ isFlat: true,
362
+ packageType: 'module',
363
+ },
364
+ );
342
365
 
366
+ // Alert keeps its node condition.
343
367
  expect(packageExports['./Alert']).toEqual({
344
- import: { node: './src/node/*.ts', default: './Alert.js' },
345
- require: { node: './src/node/*.ts', default: './Alert.cjs' },
346
- default: './Alert.js',
368
+ node: {
369
+ import: './node/Alert.js',
370
+ require: './node/Alert.cjs',
371
+ default: './node/Alert.js',
372
+ },
373
+ default: { import: './Alert.js', require: './Alert.cjs', default: './Alert.js' },
374
+ });
375
+ // Button has no node variant, so the node condition is dropped rather than
376
+ // pointing at a non-existent source (which would fail the build).
377
+ expect(packageExports['./Button']).toEqual({
378
+ default: { import: './Button.js', require: './Button.cjs', default: './Button.js' },
379
+ });
380
+ });
381
+
382
+ it('enumerates a wildcard directory pattern', async () => {
383
+ const cwd = await makeTempDir();
384
+ const outputDir = path.join(cwd, 'build');
385
+
386
+ await Promise.all([
387
+ createFile(path.join(cwd, 'src/tabs/script.ts')),
388
+ createFile(path.join(cwd, 'src/menu/script.ts')),
389
+ ]);
390
+
391
+ const { exports: packageExports } = await createPackageExports(
392
+ {
393
+ './*': './src/*/script.ts',
394
+ },
395
+ {
396
+ bundles: [{ type: 'cjs', dir: '.' }],
397
+ outputDir,
398
+ cwd,
399
+ isFlat: true,
400
+ },
401
+ );
402
+
403
+ expect(packageExports['./tabs']).toEqual({
404
+ require: './tabs/script.js',
405
+ default: './tabs/script.js',
406
+ });
407
+ expect(packageExports['./menu']).toEqual({
408
+ require: './menu/script.js',
409
+ default: './menu/script.js',
347
410
  });
348
411
  });
349
412
 
413
+ it('warns and produces nothing when a glob matches no files', async () => {
414
+ const cwd = await makeTempDir();
415
+ const outputDir = path.join(cwd, 'build');
416
+ createFile(path.join(cwd, 'src/.gitkeep'));
417
+
418
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
419
+ try {
420
+ const { exports: packageExports } = await createPackageExports(
421
+ {
422
+ './*': './src/*.ts',
423
+ },
424
+ {
425
+ bundles: [{ type: 'cjs', dir: '.' }],
426
+ outputDir,
427
+ cwd,
428
+ isFlat: true,
429
+ },
430
+ );
431
+
432
+ expect(Object.keys(packageExports)).toEqual(['./package.json']);
433
+ expect(warn).toHaveBeenCalled();
434
+ } finally {
435
+ warn.mockRestore();
436
+ }
437
+ });
438
+
350
439
  it('mixes glob and non-glob exports', async () => {
351
440
  const cwd = await makeTempDir();
352
441
  const outputDir = path.join(cwd, 'build');
@@ -368,17 +457,19 @@ describe('createPackageExports', () => {
368
457
  createFile(path.join(outputDir, 'Chip.cjs')),
369
458
  ]);
370
459
 
371
- const { exports: packageExports } = await createPackageExports({
372
- exports: {
460
+ const { exports: packageExports } = await createPackageExports(
461
+ {
373
462
  '.': './src/index.ts',
374
463
  './*': './src/*.ts',
375
464
  },
376
- bundles,
377
- outputDir,
378
- cwd,
379
- isFlat: true,
380
- packageType: 'module',
381
- });
465
+ {
466
+ bundles,
467
+ outputDir,
468
+ cwd,
469
+ isFlat: true,
470
+ packageType: 'module',
471
+ },
472
+ );
382
473
 
383
474
  // Explicit export still works
384
475
  expect(packageExports['.']).toBeDefined();
@@ -411,16 +502,18 @@ describe('createPackageExports', () => {
411
502
  createFile(path.join(outputDir, 'utils/size.cjs')),
412
503
  ]);
413
504
 
414
- const { exports: packageExports } = await createPackageExports({
415
- exports: {
505
+ const { exports: packageExports } = await createPackageExports(
506
+ {
416
507
  './utils/*': './src/utils/*.ts',
417
508
  },
418
- bundles,
419
- outputDir,
420
- cwd,
421
- isFlat: true,
422
- packageType: 'module',
423
- });
509
+ {
510
+ bundles,
511
+ outputDir,
512
+ cwd,
513
+ isFlat: true,
514
+ packageType: 'module',
515
+ },
516
+ );
424
517
 
425
518
  expect(packageExports['./utils/color']).toEqual({
426
519
  import: './utils/color.js',
@@ -441,15 +534,17 @@ describe('createPackageExports', () => {
441
534
  // Create the src directory but no .ts files in it
442
535
  createFile(path.join(cwd, 'src/.gitkeep'));
443
536
 
444
- const { exports: packageExports } = await createPackageExports({
445
- exports: {
537
+ const { exports: packageExports } = await createPackageExports(
538
+ {
446
539
  './*': './src/*.ts',
447
540
  },
448
- bundles: [{ type: 'cjs', dir: '.' }],
449
- outputDir,
450
- cwd,
451
- isFlat: true,
452
- });
541
+ {
542
+ bundles: [{ type: 'cjs', dir: '.' }],
543
+ outputDir,
544
+ cwd,
545
+ isFlat: true,
546
+ },
547
+ );
453
548
 
454
549
  // Only the default ./package.json entry should be present
455
550
  expect(Object.keys(packageExports)).toEqual(['./package.json']);
@@ -469,15 +564,17 @@ describe('createPackageExports', () => {
469
564
  createFile(path.join(outputDir, 'Mango.js')),
470
565
  ]);
471
566
 
472
- const { exports: packageExports } = await createPackageExports({
473
- exports: {
567
+ const { exports: packageExports } = await createPackageExports(
568
+ {
474
569
  './*': './src/*.ts',
475
570
  },
476
- bundles: [{ type: 'cjs', dir: '.' }],
477
- outputDir,
478
- cwd,
479
- isFlat: true,
480
- });
571
+ {
572
+ bundles: [{ type: 'cjs', dir: '.' }],
573
+ outputDir,
574
+ cwd,
575
+ isFlat: true,
576
+ },
577
+ );
481
578
 
482
579
  const exportKeys = Object.keys(packageExports).filter((k) => k !== './package.json');
483
580
  expect(exportKeys).toEqual(['./Apple', './Mango', './Zebra']);
@@ -507,17 +604,19 @@ describe('createPackageExports', () => {
507
604
  createFile(path.join(outputDir, 'ButtonBase.cjs')),
508
605
  ]);
509
606
 
510
- const { exports: packageExports } = await createPackageExports({
511
- exports: {
607
+ const { exports: packageExports } = await createPackageExports(
608
+ {
512
609
  './*': './src/*.ts',
513
610
  './Button*': null,
514
611
  },
515
- bundles,
516
- outputDir,
517
- cwd,
518
- isFlat: true,
519
- packageType: 'module',
520
- });
612
+ {
613
+ bundles,
614
+ outputDir,
615
+ cwd,
616
+ isFlat: true,
617
+ packageType: 'module',
618
+ },
619
+ );
521
620
 
522
621
  expect(packageExports['./Accordion']).toBeDefined();
523
622
  expect(packageExports['./Button']).toBeUndefined();
@@ -540,16 +639,18 @@ describe('createPackageExports', () => {
540
639
  createFile(path.join(outputDir, 'Button.js')),
541
640
  ]);
542
641
 
543
- const { exports: packageExports } = await createPackageExports({
544
- exports: {
642
+ const { exports: packageExports } = await createPackageExports(
643
+ {
545
644
  './*': './src/*.ts',
546
645
  './Alert*': null,
547
646
  },
548
- bundles: [{ type: 'cjs', dir: '.' }],
549
- outputDir,
550
- cwd,
551
- isFlat: true,
552
- });
647
+ {
648
+ bundles: [{ type: 'cjs', dir: '.' }],
649
+ outputDir,
650
+ cwd,
651
+ isFlat: true,
652
+ },
653
+ );
553
654
 
554
655
  const exportKeys = Object.keys(packageExports).filter((k) => k !== './package.json');
555
656
  expect(exportKeys).toEqual(['./Button']);
@@ -564,16 +665,18 @@ describe('createPackageExports', () => {
564
665
  createFile(path.join(outputDir, 'Button.js')),
565
666
  ]);
566
667
 
567
- const { exports: packageExports } = await createPackageExports({
568
- exports: {
668
+ const { exports: packageExports } = await createPackageExports(
669
+ {
569
670
  './*': './src/*.ts',
570
671
  './internal/*': null,
571
672
  },
572
- bundles: [{ type: 'cjs', dir: '.' }],
573
- outputDir,
574
- cwd,
575
- isFlat: true,
576
- });
673
+ {
674
+ bundles: [{ type: 'cjs', dir: '.' }],
675
+ outputDir,
676
+ cwd,
677
+ isFlat: true,
678
+ },
679
+ );
577
680
 
578
681
  // Button is kept since it doesn't match the negation
579
682
  expect(packageExports['./Button']).toBeDefined();
@@ -581,7 +684,7 @@ describe('createPackageExports', () => {
581
684
  expect(packageExports['./internal/*']).toBeNull();
582
685
  });
583
686
 
584
- it('does not expand glob patterns when isFlat is false', async () => {
687
+ it('does not expand glob patterns when expand is false', async () => {
585
688
  const cwd = await makeTempDir();
586
689
  const outputDir = path.join(cwd, 'build');
587
690
 
@@ -590,15 +693,18 @@ describe('createPackageExports', () => {
590
693
  createFile(path.join(cwd, 'src/TextField.ts')),
591
694
  ]);
592
695
 
593
- const { exports: packageExports } = await createPackageExports({
594
- exports: {
696
+ const { exports: packageExports } = await createPackageExports(
697
+ {
595
698
  './*': './src/*.ts',
596
699
  },
597
- bundles: [{ type: 'cjs', dir: '.' }],
598
- outputDir,
599
- cwd,
600
- isFlat: false,
601
- });
700
+ {
701
+ bundles: [{ type: 'cjs', dir: '.' }],
702
+ outputDir,
703
+ cwd,
704
+ isFlat: false,
705
+ expand: false,
706
+ },
707
+ );
602
708
 
603
709
  // Glob should NOT be expanded to individual files
604
710
  expect(packageExports['./Button']).toBeUndefined();
@@ -619,15 +725,17 @@ describe('createPackageExports', () => {
619
725
  createFile(path.join(outputDir, 'index.js')),
620
726
  ]);
621
727
 
622
- const { exports: packageExports } = await createPackageExports({
623
- exports: {
728
+ const { exports: packageExports } = await createPackageExports(
729
+ {
624
730
  './*': './src/index.ts',
625
731
  },
626
- bundles: [{ type: 'cjs', dir: '.' }],
627
- outputDir,
628
- cwd,
629
- isFlat: true,
630
- });
732
+ {
733
+ bundles: [{ type: 'cjs', dir: '.' }],
734
+ outputDir,
735
+ cwd,
736
+ isFlat: true,
737
+ },
738
+ );
631
739
 
632
740
  // When the value has no *, the glob key is passed through as-is
633
741
  expect(packageExports['./*']).toBeDefined();
@@ -644,17 +752,19 @@ describe('createPackageExports', () => {
644
752
  createFile(path.join(outputDir, 'index.d.ts')),
645
753
  ]);
646
754
 
647
- const { exports: packageExports } = await createPackageExports({
648
- exports: {
755
+ const { exports: packageExports } = await createPackageExports(
756
+ {
649
757
  '.': './src/index.ts',
650
758
  },
651
- bundles: [{ type: 'cjs', dir: '.' }],
652
- outputDir,
653
- cwd,
654
- addTypes: true,
655
- isFlat: true,
656
- packageType: 'commonjs',
657
- });
759
+ {
760
+ bundles: [{ type: 'cjs', dir: '.' }],
761
+ outputDir,
762
+ cwd,
763
+ addTypes: true,
764
+ isFlat: true,
765
+ packageType: 'commonjs',
766
+ },
767
+ );
658
768
 
659
769
  // Single CJS bundle should have both require and default pointing to the same files
660
770
  expect(packageExports['.']).toEqual({
@@ -670,6 +780,265 @@ describe('createPackageExports', () => {
670
780
  });
671
781
  });
672
782
 
783
+ describe('createPackageExports leaf rewriting', () => {
784
+ it('rewrites a standard conditions object (conditions-outer)', async () => {
785
+ const cwd = await makeTempDir();
786
+ const outputDir = path.join(cwd, 'build');
787
+ /**
788
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
789
+ */
790
+ const bundles = [
791
+ { type: 'esm', dir: '.' },
792
+ { type: 'cjs', dir: '.' },
793
+ ];
794
+
795
+ await Promise.all([
796
+ createFile(path.join(cwd, 'src/feature.ts')),
797
+ createFile(path.join(cwd, 'src/node/feature.ts')),
798
+ ]);
799
+
800
+ const { exports: packageExports } = await createPackageExports(
801
+ {
802
+ './feature': { node: './src/node/feature.ts', default: './src/feature.ts' },
803
+ },
804
+ {
805
+ bundles,
806
+ outputDir,
807
+ cwd,
808
+ isFlat: true,
809
+ packageType: 'module',
810
+ },
811
+ );
812
+
813
+ expect(packageExports['./feature']).toEqual({
814
+ node: {
815
+ import: './node/feature.js',
816
+ require: './node/feature.cjs',
817
+ default: './node/feature.js',
818
+ },
819
+ default: {
820
+ import: './feature.js',
821
+ require: './feature.cjs',
822
+ default: './feature.js',
823
+ },
824
+ });
825
+ });
826
+
827
+ it('passes through paths that are not under src/ verbatim', async () => {
828
+ const cwd = await makeTempDir();
829
+ const outputDir = path.join(cwd, 'build');
830
+
831
+ await Promise.all([
832
+ createFile(path.join(outputDir, 'assets/logo.svg')),
833
+ createFile(path.join(outputDir, 'vendor/legacy.js')),
834
+ ]);
835
+
836
+ const { exports: packageExports } = await createPackageExports(
837
+ {
838
+ './logo': './assets/logo.svg',
839
+ './legacy': './vendor/legacy.js',
840
+ },
841
+ {
842
+ bundles: [{ type: 'cjs', dir: '.' }],
843
+ outputDir,
844
+ cwd,
845
+ isFlat: true,
846
+ },
847
+ );
848
+
849
+ // Already present in the published output: emitted unchanged.
850
+ expect(packageExports['./logo']).toBe('./assets/logo.svg');
851
+ expect(packageExports['./legacy']).toBe('./vendor/legacy.js');
852
+ });
853
+
854
+ it('treats src/ without a leading ./ as a source path', async () => {
855
+ const cwd = await makeTempDir();
856
+ const outputDir = path.join(cwd, 'build');
857
+
858
+ await createFile(path.join(cwd, 'src/index.ts'));
859
+
860
+ const { exports: packageExports } = await createPackageExports(
861
+ {
862
+ './entry': 'src/index.ts',
863
+ },
864
+ {
865
+ bundles: [{ type: 'cjs', dir: '.' }],
866
+ outputDir,
867
+ cwd,
868
+ isFlat: true,
869
+ },
870
+ );
871
+
872
+ expect(packageExports['./entry']).toEqual({
873
+ require: './index.js',
874
+ default: './index.js',
875
+ });
876
+ });
877
+
878
+ it('rewrites the prefix of a non-JS asset under src/ without an extension swap', async () => {
879
+ const cwd = await makeTempDir();
880
+ const outputDir = path.join(cwd, 'build');
881
+
882
+ await createFile(path.join(cwd, 'src/theme.css'));
883
+
884
+ const { exports: packageExports } = await createPackageExports(
885
+ {
886
+ './theme.css': './src/theme.css',
887
+ },
888
+ {
889
+ bundles: [{ type: 'cjs', dir: '.' }],
890
+ outputDir,
891
+ cwd,
892
+ isFlat: true,
893
+ },
894
+ );
895
+
896
+ expect(packageExports['./theme.css']).toBe('./theme.css');
897
+ });
898
+
899
+ it('preserves a user-authored default alongside import/require conditions', async () => {
900
+ const cwd = await makeTempDir();
901
+ const outputDir = path.join(cwd, 'build');
902
+ /**
903
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
904
+ */
905
+ const bundles = [
906
+ { type: 'esm', dir: '.' },
907
+ { type: 'cjs', dir: '.' },
908
+ ];
909
+
910
+ await Promise.all([
911
+ createFile(path.join(cwd, 'src/esm.ts')),
912
+ createFile(path.join(cwd, 'src/fallback.ts')),
913
+ ]);
914
+
915
+ const { exports: packageExports } = await createPackageExports(
916
+ {
917
+ './x': { import: './src/esm.ts', default: './src/fallback.ts' },
918
+ },
919
+ {
920
+ bundles,
921
+ outputDir,
922
+ cwd,
923
+ isFlat: true,
924
+ packageType: 'module',
925
+ },
926
+ );
927
+
928
+ // The synthesized default must not clobber the user's `default` branch.
929
+ expect(packageExports['./x']).toEqual({
930
+ import: { import: './esm.js', require: './esm.cjs', default: './esm.js' },
931
+ default: { import: './fallback.js', require: './fallback.cjs', default: './fallback.js' },
932
+ });
933
+ });
934
+ });
935
+
936
+ describe('createPackageExports resolution semantics', () => {
937
+ /**
938
+ * Seeds the worked example with files so the shallow and deep patterns match.
939
+ * @param {string} cwd
940
+ */
941
+ async function seedWorkedExample(cwd) {
942
+ await Promise.all([
943
+ createFile(path.join(cwd, 'src/foo/x.ts')),
944
+ createFile(path.join(cwd, 'src/foo/bar/baz/z.ts')),
945
+ ]);
946
+ }
947
+
948
+ const workedExampleExports = {
949
+ './foo/*': './src/foo/*.ts',
950
+ './foo/bar/*': null,
951
+ './foo/bar/baz/*': './src/foo/bar/baz/*.ts',
952
+ };
953
+
954
+ it('enumerates with Node-faithful, non-cascading negation (expand: true)', async () => {
955
+ const cwd = await makeTempDir();
956
+ const outputDir = path.join(cwd, 'build');
957
+ await seedWorkedExample(cwd);
958
+
959
+ const { exports: packageExports } = await createPackageExports(workedExampleExports, {
960
+ bundles: [{ type: 'cjs', dir: '.' }],
961
+ outputDir,
962
+ cwd,
963
+ isFlat: true,
964
+ });
965
+
966
+ // ./foo/x resolves (matched only by the shallow pattern)
967
+ expect(packageExports['./foo/x']).toEqual({
968
+ require: './foo/x.js',
969
+ default: './foo/x.js',
970
+ });
971
+ // ./foo/bar/baz/z resolves even though it sits under the null'd ./foo/bar/*
972
+ expect(packageExports['./foo/bar/baz/z']).toEqual({
973
+ require: './foo/bar/baz/z.js',
974
+ default: './foo/bar/baz/z.js',
975
+ });
976
+ // nothing whose most-specific match is the null is emitted as a concrete entry
977
+ expect(packageExports['./foo/bar/z']).toBeUndefined();
978
+ // the null pattern is carried through to keep blocking its subtree at runtime
979
+ expect(packageExports['./foo/bar/*']).toBeNull();
980
+ });
981
+
982
+ it('keeps patterns and the null verbatim (expand: false)', async () => {
983
+ const cwd = await makeTempDir();
984
+ const outputDir = path.join(cwd, 'build');
985
+ await seedWorkedExample(cwd);
986
+
987
+ const { exports: packageExports } = await createPackageExports(workedExampleExports, {
988
+ bundles: [{ type: 'cjs', dir: '.' }],
989
+ outputDir,
990
+ cwd,
991
+ isFlat: true,
992
+ expand: false,
993
+ });
994
+
995
+ expect(packageExports['./foo/*']).toEqual({
996
+ require: './foo/*.js',
997
+ default: './foo/*.js',
998
+ });
999
+ expect(packageExports['./foo/bar/baz/*']).toEqual({
1000
+ require: './foo/bar/baz/*.js',
1001
+ default: './foo/bar/baz/*.js',
1002
+ });
1003
+ expect(packageExports['./foo/bar/*']).toBeNull();
1004
+ });
1005
+
1006
+ it('lets an exact key win over a matching pattern', async () => {
1007
+ const cwd = await makeTempDir();
1008
+ const outputDir = path.join(cwd, 'build');
1009
+
1010
+ await Promise.all([
1011
+ createFile(path.join(cwd, 'src/Other.ts')),
1012
+ createFile(path.join(cwd, 'src/override.ts')),
1013
+ ]);
1014
+
1015
+ const { exports: packageExports } = await createPackageExports(
1016
+ {
1017
+ // Exact key collides with what `./*` would expand to, but points elsewhere.
1018
+ './Other': './src/override.ts',
1019
+ './*': './src/*.ts',
1020
+ },
1021
+ {
1022
+ bundles: [{ type: 'cjs', dir: '.' }],
1023
+ outputDir,
1024
+ cwd,
1025
+ isFlat: true,
1026
+ },
1027
+ );
1028
+
1029
+ // The exact key keeps its own target rather than the pattern's stem.
1030
+ expect(packageExports['./Other']).toEqual({
1031
+ require: './override.js',
1032
+ default: './override.js',
1033
+ });
1034
+ // The pattern still expands its other matches.
1035
+ expect(packageExports['./override']).toEqual({
1036
+ require: './override.js',
1037
+ default: './override.js',
1038
+ });
1039
+ });
1040
+ });
1041
+
673
1042
  describe('createPackageBin', () => {
674
1043
  it('prefers the ESM bundle when available', async () => {
675
1044
  const cwd = await makeTempDir();
@@ -683,8 +1052,7 @@ describe('createPackageBin', () => {
683
1052
 
684
1053
  await Promise.all([createFile(path.join(cwd, 'src/cli.ts'))]);
685
1054
 
686
- let bin = await createPackageBin({
687
- bin: './src/cli.ts',
1055
+ let bin = await createPackageBin('./src/cli.ts', {
688
1056
  bundles,
689
1057
  cwd,
690
1058
  isFlat: true,
@@ -693,8 +1061,7 @@ describe('createPackageBin', () => {
693
1061
 
694
1062
  expect(bin).toBe('./cli.js');
695
1063
 
696
- bin = await createPackageBin({
697
- bin: './src/cli.ts',
1064
+ bin = await createPackageBin('./src/cli.ts', {
698
1065
  bundles: [bundles[1]], // only CJS bundle
699
1066
  cwd,
700
1067
  isFlat: true,
@@ -702,8 +1069,7 @@ describe('createPackageBin', () => {
702
1069
 
703
1070
  expect(bin).toBe('./cli.js');
704
1071
 
705
- bin = await createPackageBin({
706
- bin: './src/cli.ts',
1072
+ bin = await createPackageBin('./src/cli.ts', {
707
1073
  bundles, // only CJS bundle
708
1074
  cwd,
709
1075
  isFlat: true,
@@ -713,3 +1079,206 @@ describe('createPackageBin', () => {
713
1079
  expect(bin).toBe('./cli.mjs');
714
1080
  });
715
1081
  });
1082
+
1083
+ describe('createPackageImports', () => {
1084
+ it('returns undefined when there is no imports field', async () => {
1085
+ const cwd = await makeTempDir();
1086
+
1087
+ const imports = await createPackageImports(undefined, {
1088
+ bundles: [{ type: 'esm', dir: '.' }],
1089
+ cwd,
1090
+ isFlat: true,
1091
+ packageType: 'module',
1092
+ });
1093
+
1094
+ expect(imports).toBeUndefined();
1095
+ });
1096
+
1097
+ it('throws when an import key does not start with "#"', async () => {
1098
+ const cwd = await makeTempDir();
1099
+
1100
+ await expect(
1101
+ createPackageImports(
1102
+ /** @type {any} */ ({
1103
+ 'internal/utils': './src/internal/utils.ts',
1104
+ }),
1105
+ {
1106
+ bundles: [{ type: 'esm', dir: '.' }],
1107
+ cwd,
1108
+ isFlat: true,
1109
+ packageType: 'module',
1110
+ },
1111
+ ),
1112
+ ).rejects.toThrow('must start with "#"');
1113
+ });
1114
+
1115
+ it('rewrites internal subpath imports for a dual bundle module package', async () => {
1116
+ const cwd = await makeTempDir();
1117
+ const outputDir = path.join(cwd, 'build');
1118
+ /**
1119
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
1120
+ */
1121
+ const bundles = [
1122
+ { type: 'esm', dir: '.' },
1123
+ { type: 'cjs', dir: '.' },
1124
+ ];
1125
+
1126
+ await Promise.all([
1127
+ createFile(path.join(cwd, 'src/internal/utils.ts')),
1128
+ createFile(path.join(outputDir, 'internal/utils.js')),
1129
+ createFile(path.join(outputDir, 'internal/utils.cjs')),
1130
+ createFile(path.join(outputDir, 'internal/utils.d.ts')),
1131
+ createFile(path.join(outputDir, 'internal/utils.d.cts')),
1132
+ ]);
1133
+
1134
+ const imports = await createPackageImports(
1135
+ {
1136
+ '#internal/utils': './src/internal/utils.ts',
1137
+ },
1138
+ {
1139
+ bundles,
1140
+ cwd,
1141
+ addTypes: true,
1142
+ isFlat: true,
1143
+ packageType: 'module',
1144
+ },
1145
+ );
1146
+
1147
+ expect(imports).toEqual({
1148
+ '#internal/utils': {
1149
+ import: { types: './internal/utils.d.ts', default: './internal/utils.js' },
1150
+ require: { types: './internal/utils.d.cts', default: './internal/utils.cjs' },
1151
+ default: { types: './internal/utils.d.ts', default: './internal/utils.js' },
1152
+ },
1153
+ });
1154
+ });
1155
+
1156
+ it('puts import before require regardless of bundle array order', async () => {
1157
+ const cwd = await makeTempDir();
1158
+ const outputDir = path.join(cwd, 'build');
1159
+
1160
+ await Promise.all([
1161
+ createFile(path.join(cwd, 'src/internal/utils.ts')),
1162
+ createFile(path.join(outputDir, 'internal/utils.js')),
1163
+ createFile(path.join(outputDir, 'internal/utils.cjs')),
1164
+ ]);
1165
+
1166
+ const imports = await createPackageImports(
1167
+ {
1168
+ '#internal/utils': './src/internal/utils.ts',
1169
+ },
1170
+ {
1171
+ // Pass cjs before esm to verify the key order is still 'import' then 'require'
1172
+ bundles: [
1173
+ { type: 'cjs', dir: '.' },
1174
+ { type: 'esm', dir: '.' },
1175
+ ],
1176
+ cwd,
1177
+ isFlat: true,
1178
+ packageType: 'module',
1179
+ },
1180
+ );
1181
+
1182
+ expect(
1183
+ Object.keys(/** @type {Record<string, unknown>} */ (imports?.['#internal/utils'])),
1184
+ ).toEqual(['import', 'require', 'default']);
1185
+ });
1186
+
1187
+ it('passes bare specifiers through unchanged', async () => {
1188
+ const cwd = await makeTempDir();
1189
+
1190
+ const imports = await createPackageImports(
1191
+ {
1192
+ '#error-formatter': '@custom/error-formatter',
1193
+ },
1194
+ {
1195
+ bundles: [
1196
+ { type: 'esm', dir: '.' },
1197
+ { type: 'cjs', dir: '.' },
1198
+ ],
1199
+ cwd,
1200
+ isFlat: true,
1201
+ packageType: 'module',
1202
+ },
1203
+ );
1204
+
1205
+ expect(imports).toEqual({
1206
+ '#error-formatter': '@custom/error-formatter',
1207
+ });
1208
+ });
1209
+
1210
+ it('expands glob patterns in import keys and values', async () => {
1211
+ const cwd = await makeTempDir();
1212
+ const outputDir = path.join(cwd, 'build');
1213
+
1214
+ await Promise.all([
1215
+ createFile(path.join(cwd, 'src/internal/foo.ts')),
1216
+ createFile(path.join(cwd, 'src/internal/bar.ts')),
1217
+ createFile(path.join(outputDir, 'internal/foo.js')),
1218
+ createFile(path.join(outputDir, 'internal/bar.js')),
1219
+ ]);
1220
+
1221
+ const imports = await createPackageImports(
1222
+ {
1223
+ '#internal/*': './src/internal/*.ts',
1224
+ },
1225
+ {
1226
+ bundles: [{ type: 'esm', dir: '.' }],
1227
+ cwd,
1228
+ isFlat: true,
1229
+ packageType: 'module',
1230
+ },
1231
+ );
1232
+
1233
+ expect(imports).toEqual({
1234
+ '#internal/bar': { import: './internal/bar.js', default: './internal/bar.js' },
1235
+ '#internal/foo': { import: './internal/foo.js', default: './internal/foo.js' },
1236
+ });
1237
+ });
1238
+
1239
+ it('rewrites every condition of a conditions object (conditions-outer)', async () => {
1240
+ const cwd = await makeTempDir();
1241
+ /**
1242
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
1243
+ */
1244
+ const bundles = [
1245
+ { type: 'esm', dir: '.' },
1246
+ { type: 'cjs', dir: '.' },
1247
+ ];
1248
+
1249
+ await Promise.all([
1250
+ createFile(path.join(cwd, 'src/internal/utils.ts')),
1251
+ createFile(path.join(cwd, 'src/internal/node/utils.ts')),
1252
+ ]);
1253
+
1254
+ const imports = await createPackageImports(
1255
+ {
1256
+ '#internal/utils': {
1257
+ node: './src/internal/node/utils.ts',
1258
+ default: './src/internal/utils.ts',
1259
+ },
1260
+ },
1261
+ {
1262
+ bundles,
1263
+ cwd,
1264
+ isFlat: true,
1265
+ packageType: 'module',
1266
+ },
1267
+ );
1268
+
1269
+ expect(imports).toEqual({
1270
+ '#internal/utils': {
1271
+ node: {
1272
+ import: './internal/node/utils.js',
1273
+ require: './internal/node/utils.cjs',
1274
+ default: './internal/node/utils.js',
1275
+ },
1276
+ default: {
1277
+ import: './internal/utils.js',
1278
+ require: './internal/utils.cjs',
1279
+ default: './internal/utils.js',
1280
+ },
1281
+ },
1282
+ });
1283
+ });
1284
+ });