@mui/internal-code-infra 0.0.4-canary.6 → 0.0.4-canary.60

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.
Files changed (111) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +19 -8
  3. package/build/babel-config.d.mts +11 -3
  4. package/build/brokenLinksChecker/crawlWorker.d.mts +1 -0
  5. package/build/brokenLinksChecker/index.d.mts +45 -2
  6. package/build/changelog/types.d.ts +1 -1
  7. package/build/cli/cmdArgosPush.d.mts +2 -2
  8. package/build/cli/cmdBuild.d.mts +3 -2
  9. package/build/cli/cmdCopyFiles.d.mts +2 -2
  10. package/build/cli/cmdExtractErrorCodes.d.mts +2 -2
  11. package/build/cli/cmdGenerateChangelog.d.mts +2 -2
  12. package/build/cli/cmdGithubAuth.d.mts +2 -2
  13. package/build/cli/cmdListWorkspaces.d.mts +6 -4
  14. package/build/cli/cmdNetlifyIgnore.d.mts +3 -2
  15. package/build/cli/cmdPublish.d.mts +4 -2
  16. package/build/cli/cmdPublishCanary.d.mts +3 -3
  17. package/build/cli/cmdPublishNewPackage.d.mts +4 -2
  18. package/build/cli/cmdSetVersionOverrides.d.mts +2 -2
  19. package/build/cli/cmdVale.d.mts +46 -0
  20. package/build/cli/cmdValidateBuiltTypes.d.mts +2 -2
  21. package/build/eslint/baseConfig.d.mts +3 -1
  22. package/build/eslint/mui/rules/disallow-react-api-in-server-components.d.mts +2 -2
  23. package/build/eslint/mui/rules/docgen-ignore-before-comment.d.mts +2 -2
  24. package/build/eslint/mui/rules/no-guarded-throw.d.mts +31 -0
  25. package/build/eslint/mui/rules/no-presentation-role.d.mts +5 -0
  26. package/build/eslint/mui/rules/no-restricted-resolved-imports.d.mts +2 -2
  27. package/build/eslint/mui/rules/nodeEnvUtils.d.mts +18 -0
  28. package/build/markdownlint/duplicate-h1.d.mts +1 -1
  29. package/build/markdownlint/git-diff.d.mts +1 -1
  30. package/build/markdownlint/index.d.mts +1 -1
  31. package/build/markdownlint/straight-quotes.d.mts +1 -1
  32. package/build/markdownlint/table-alignment.d.mts +1 -1
  33. package/build/markdownlint/terminal-language.d.mts +1 -1
  34. package/build/remark/config.d.mts +43 -0
  35. package/build/remark/createLintTester.d.mts +10 -0
  36. package/build/remark/firstBlockHeading.d.mts +4 -0
  37. package/build/remark/gitDiff.d.mts +2 -0
  38. package/build/remark/noSpaceInLinks.d.mts +2 -0
  39. package/build/remark/straightQuotes.d.mts +2 -0
  40. package/build/remark/tableAlignment.d.mts +2 -0
  41. package/build/remark/terminalLanguage.d.mts +2 -0
  42. package/build/utils/babel.d.mts +1 -1
  43. package/build/utils/build.d.mts +61 -25
  44. package/build/utils/github.d.mts +1 -1
  45. package/build/utils/pnpm.d.mts +68 -2
  46. package/build/utils/testUtils.d.mts +7 -0
  47. package/build/utils/typescript.d.mts +2 -2
  48. package/package.json +61 -34
  49. package/src/babel-config.mjs +9 -3
  50. package/src/brokenLinksChecker/__fixtures__/static-site/index.html +1 -0
  51. package/src/brokenLinksChecker/__fixtures__/static-site/invalid-html.html +15 -0
  52. package/src/brokenLinksChecker/crawlWorker.mjs +217 -0
  53. package/src/brokenLinksChecker/index.mjs +217 -164
  54. package/src/brokenLinksChecker/index.test.ts +50 -13
  55. package/src/changelog/categorizeCommits.test.ts +5 -5
  56. package/src/changelog/fetchChangelogs.mjs +6 -2
  57. package/src/changelog/parseCommitLabels.test.ts +5 -5
  58. package/src/changelog/renderChangelog.mjs +1 -1
  59. package/src/changelog/types.ts +1 -1
  60. package/src/cli/cmdBuild.mjs +39 -16
  61. package/src/cli/cmdListWorkspaces.mjs +12 -27
  62. package/src/cli/cmdNetlifyIgnore.mjs +34 -93
  63. package/src/cli/cmdPublish.mjs +55 -15
  64. package/src/cli/cmdPublishCanary.mjs +128 -132
  65. package/src/cli/cmdPublishNewPackage.mjs +27 -6
  66. package/src/cli/cmdVale.mjs +513 -0
  67. package/src/cli/cmdVale.test.mjs +644 -0
  68. package/src/cli/index.mjs +2 -0
  69. package/src/cli/packageJson.d.ts +1 -1
  70. package/src/eslint/baseConfig.mjs +45 -20
  71. package/src/eslint/docsConfig.mjs +4 -2
  72. package/src/eslint/jsonConfig.mjs +2 -1
  73. package/src/eslint/mui/config.mjs +24 -4
  74. package/src/eslint/mui/index.mjs +4 -0
  75. package/src/eslint/mui/rules/no-guarded-throw.mjs +115 -0
  76. package/src/eslint/mui/rules/no-guarded-throw.test.mjs +206 -0
  77. package/src/eslint/mui/rules/no-presentation-role.mjs +60 -0
  78. package/src/eslint/mui/rules/no-presentation-role.test.mjs +33 -0
  79. package/src/eslint/mui/rules/nodeEnvUtils.mjs +52 -0
  80. package/src/eslint/mui/rules/require-dev-wrapper.mjs +25 -40
  81. package/src/eslint/testConfig.mjs +2 -1
  82. package/src/estree-typescript.d.ts +1 -1
  83. package/src/remark/config.mjs +157 -0
  84. package/src/remark/createLintTester.mjs +19 -0
  85. package/src/remark/firstBlockHeading.mjs +87 -0
  86. package/src/remark/firstBlockHeading.test.mjs +107 -0
  87. package/src/remark/gitDiff.mjs +43 -0
  88. package/src/remark/gitDiff.test.mjs +45 -0
  89. package/src/remark/noSpaceInLinks.mjs +42 -0
  90. package/src/remark/noSpaceInLinks.test.mjs +22 -0
  91. package/src/remark/straightQuotes.mjs +31 -0
  92. package/src/remark/straightQuotes.test.mjs +25 -0
  93. package/src/remark/tableAlignment.mjs +23 -0
  94. package/src/remark/tableAlignment.test.mjs +28 -0
  95. package/src/remark/terminalLanguage.mjs +19 -0
  96. package/src/remark/terminalLanguage.test.mjs +17 -0
  97. package/src/untyped-plugins.d.ts +11 -11
  98. package/src/utils/build.mjs +621 -256
  99. package/src/utils/build.test.mjs +1064 -485
  100. package/src/utils/git.mjs +16 -7
  101. package/src/utils/pnpm.mjs +192 -3
  102. package/src/utils/pnpm.test.mjs +580 -0
  103. package/src/utils/testUtils.mjs +18 -0
  104. package/src/utils/typescript.test.mjs +249 -272
  105. package/vale/.vale.ini +1 -0
  106. package/vale/styles/MUI/CorrectReferenceAllCases.yml +43 -0
  107. package/vale/styles/MUI/CorrectRererenceCased.yml +14 -0
  108. package/vale/styles/MUI/GoogleLatin.yml +11 -0
  109. package/vale/styles/MUI/MuiBrandName.yml +22 -0
  110. package/vale/styles/MUI/NoBritish.yml +112 -0
  111. package/vale/styles/MUI/NoCompanyName.yml +17 -0
@@ -1,9 +1,9 @@
1
1
  import * as fs from 'node:fs/promises';
2
- import * as os from 'node:os';
3
2
  import * as path from 'node:path';
4
- import { describe, expect, it } from 'vitest';
3
+ import { describe, expect, it, vi } from 'vitest';
5
4
 
6
- import { createPackageBin, createPackageExports } from './build.mjs';
5
+ import { makeTempDir } from './testUtils.mjs';
6
+ import { createPackageBin, createPackageExports, createPackageImports } from './build.mjs';
7
7
 
8
8
  /**
9
9
  * @param {string} filePath
@@ -14,692 +14,1271 @@ async function createFile(filePath, contents = '') {
14
14
  await fs.writeFile(filePath, contents, 'utf8');
15
15
  }
16
16
 
17
- /**
18
- * @param {(cwd: string) => Promise<void>} fn
19
- */
20
- async function withTempDir(fn) {
21
- const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'code-infra-build-test-'));
22
- try {
23
- return await fn(tmpDir);
24
- } finally {
25
- await fs.rm(tmpDir, { recursive: true, force: true });
26
- }
27
- }
28
-
29
17
  describe('createPackageExports', () => {
30
- it('creates exports for a dual bundle module package', async () => {
31
- await withTempDir(async (cwd) => {
32
- const outputDir = path.join(cwd, 'build');
33
- /**
34
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
35
- */
36
- const bundles = [
37
- { type: 'esm', dir: '.' },
38
- { type: 'cjs', dir: '.' },
39
- ];
40
-
41
- await Promise.all([
42
- createFile(path.join(cwd, 'src/index.ts')),
43
- createFile(path.join(cwd, 'src/feature.ts')),
44
-
45
- // Create output files for index
46
- createFile(path.join(outputDir, `index.js`)),
47
- createFile(path.join(outputDir, `index.cjs`)),
48
- createFile(path.join(outputDir, `index.d.ts`)),
49
- createFile(path.join(outputDir, `index.d.cts`)),
50
-
51
- // Create output files for feature
52
- createFile(path.join(outputDir, `feature.js`)),
53
- createFile(path.join(outputDir, `feature.cjs`)),
54
- createFile(path.join(outputDir, `feature.d.ts`)),
55
- createFile(path.join(outputDir, `feature.d.cts`)),
56
- ]);
18
+ it('always puts import before require regardless of bundle array order', async () => {
19
+ const cwd = await makeTempDir();
20
+ const outputDir = path.join(cwd, 'build');
21
+
22
+ await Promise.all([
23
+ createFile(path.join(cwd, 'src/index.ts')),
24
+ createFile(path.join(cwd, 'src/feature.ts')),
25
+ createFile(path.join(outputDir, 'index.js')),
26
+ createFile(path.join(outputDir, 'index.cjs')),
27
+ createFile(path.join(outputDir, 'feature.js')),
28
+ createFile(path.join(outputDir, 'feature.cjs')),
29
+ ]);
30
+
31
+ // Pass cjs before esm to verify the key order is still 'import' then 'require'
32
+ const { exports: packageExports } = await createPackageExports(
33
+ {
34
+ '.': './src/index.ts',
35
+ './feature': './src/feature.ts',
36
+ },
37
+ {
38
+ bundles: [
39
+ { type: 'cjs', dir: '.' },
40
+ { type: 'esm', dir: '.' },
41
+ ],
42
+ outputDir,
43
+ cwd,
44
+ isFlat: true,
45
+ packageType: 'module',
46
+ },
47
+ );
48
+
49
+ expect(Object.keys(/** @type {Record<string, unknown>} */ (packageExports['.']))).toEqual([
50
+ 'import',
51
+ 'require',
52
+ 'default',
53
+ ]);
54
+ expect(
55
+ Object.keys(/** @type {Record<string, unknown>} */ (packageExports['./feature'])),
56
+ ).toEqual(['import', 'require', 'default']);
57
+ });
57
58
 
58
- const {
59
- exports: packageExports,
60
- main,
61
- types,
62
- } = await createPackageExports({
63
- exports: {
64
- '.': './src/index.ts',
65
- './feature': './src/feature.ts',
66
- },
59
+ it('creates exports for a dual bundle module package', async () => {
60
+ const cwd = await makeTempDir();
61
+ const outputDir = path.join(cwd, 'build');
62
+ /**
63
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
64
+ */
65
+ const bundles = [
66
+ { type: 'esm', dir: '.' },
67
+ { type: 'cjs', dir: '.' },
68
+ ];
69
+
70
+ await Promise.all([
71
+ createFile(path.join(cwd, 'src/index.ts')),
72
+ createFile(path.join(cwd, 'src/feature.ts')),
73
+
74
+ // Create output files for index
75
+ createFile(path.join(outputDir, `index.js`)),
76
+ createFile(path.join(outputDir, `index.cjs`)),
77
+ createFile(path.join(outputDir, `index.d.ts`)),
78
+ createFile(path.join(outputDir, `index.d.cts`)),
79
+
80
+ // Create output files for feature
81
+ createFile(path.join(outputDir, `feature.js`)),
82
+ createFile(path.join(outputDir, `feature.cjs`)),
83
+ createFile(path.join(outputDir, `feature.d.ts`)),
84
+ createFile(path.join(outputDir, `feature.d.cts`)),
85
+ ]);
86
+
87
+ const {
88
+ exports: packageExports,
89
+ main,
90
+ types,
91
+ } = await createPackageExports(
92
+ {
93
+ '.': './src/index.ts',
94
+ './feature': './src/feature.ts',
95
+ },
96
+ {
67
97
  bundles,
68
98
  outputDir,
69
99
  cwd,
70
100
  addTypes: true,
71
101
  isFlat: true,
72
102
  packageType: 'module',
73
- });
74
-
75
- expect(main).toBe('./index.cjs');
76
- expect(types).toBe('./index.d.cts');
77
-
78
- expect(packageExports['.']).toEqual({
79
- import: { types: './index.d.ts', default: './index.js' },
80
- require: { types: './index.d.cts', default: './index.cjs' },
81
- default: {
82
- types: './index.d.ts',
83
- default: './index.js',
84
- },
85
- });
86
- expect(packageExports['./feature']).toEqual({
87
- import: { types: './feature.d.ts', default: './feature.js' },
88
- require: { types: './feature.d.cts', default: './feature.cjs' },
89
- default: {
90
- types: './feature.d.ts',
91
- default: './feature.js',
92
- },
93
- });
103
+ },
104
+ );
105
+
106
+ expect(main).toBe('./index.cjs');
107
+ expect(types).toBe('./index.d.cts');
108
+
109
+ expect(packageExports['.']).toEqual({
110
+ import: { types: './index.d.ts', default: './index.js' },
111
+ require: { types: './index.d.cts', default: './index.cjs' },
112
+ default: {
113
+ types: './index.d.ts',
114
+ default: './index.js',
115
+ },
116
+ });
117
+ expect(packageExports['./feature']).toEqual({
118
+ import: { types: './feature.d.ts', default: './feature.js' },
119
+ require: { types: './feature.d.cts', default: './feature.cjs' },
120
+ default: {
121
+ types: './feature.d.ts',
122
+ default: './feature.js',
123
+ },
124
+ });
94
125
 
95
- const {
96
- exports: packageExports2,
97
- main: main2,
98
- types: types2,
99
- } = await createPackageExports({
100
- exports: {
101
- '.': './src/index.ts',
102
- './feature': './src/feature.ts',
103
- },
126
+ const {
127
+ exports: packageExports2,
128
+ main: main2,
129
+ types: types2,
130
+ } = await createPackageExports(
131
+ {
132
+ '.': './src/index.ts',
133
+ './feature': './src/feature.ts',
134
+ },
135
+ {
104
136
  bundles: [bundles[1]], // only CJS bundle
105
137
  outputDir,
106
138
  cwd,
107
139
  addTypes: true,
108
140
  isFlat: true,
109
- });
110
-
111
- expect(main2).toBe('./index.js');
112
- expect(types2).toBe('./index.d.ts');
113
-
114
- expect(packageExports2['.']).toEqual({
115
- require: { types: './index.d.ts', default: './index.js' },
116
- default: {
117
- types: './index.d.ts',
118
- default: './index.js',
119
- },
120
- });
121
- expect(packageExports2['./feature']).toEqual({
122
- require: { types: './feature.d.ts', default: './feature.js' },
123
- default: {
124
- types: './feature.d.ts',
125
- default: './feature.js',
126
- },
127
- });
141
+ },
142
+ );
143
+
144
+ expect(main2).toBe('./index.js');
145
+ expect(types2).toBe('./index.d.ts');
146
+
147
+ expect(packageExports2['.']).toEqual({
148
+ require: { types: './index.d.ts', default: './index.js' },
149
+ default: {
150
+ types: './index.d.ts',
151
+ default: './index.js',
152
+ },
153
+ });
154
+ expect(packageExports2['./feature']).toEqual({
155
+ require: { types: './feature.d.ts', default: './feature.js' },
156
+ default: {
157
+ types: './feature.d.ts',
158
+ default: './feature.js',
159
+ },
128
160
  });
129
161
  });
130
162
 
131
163
  describe('glob expansion', () => {
132
164
  it('expands glob patterns in export keys and values', async () => {
133
- await withTempDir(async (cwd) => {
134
- const outputDir = path.join(cwd, 'build');
135
- /**
136
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
137
- */
138
- const bundles = [
139
- { type: 'esm', dir: '.' },
140
- { type: 'cjs', dir: '.' },
141
- ];
142
-
143
- await Promise.all([
144
- createFile(path.join(cwd, 'src/Button.ts')),
145
- createFile(path.join(cwd, 'src/TextField.ts')),
146
-
147
- // Output files
148
- createFile(path.join(outputDir, 'Button.js')),
149
- createFile(path.join(outputDir, 'Button.cjs')),
150
- createFile(path.join(outputDir, 'Button.d.ts')),
151
- createFile(path.join(outputDir, 'Button.d.cts')),
152
- createFile(path.join(outputDir, 'TextField.js')),
153
- createFile(path.join(outputDir, 'TextField.cjs')),
154
- createFile(path.join(outputDir, 'TextField.d.ts')),
155
- createFile(path.join(outputDir, 'TextField.d.cts')),
156
- ]);
157
-
158
- const { exports: packageExports } = await createPackageExports({
159
- exports: {
160
- './*': './src/*.ts',
161
- },
165
+ const cwd = await makeTempDir();
166
+ const outputDir = path.join(cwd, 'build');
167
+ /**
168
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
169
+ */
170
+ const bundles = [
171
+ { type: 'esm', dir: '.' },
172
+ { type: 'cjs', dir: '.' },
173
+ ];
174
+
175
+ await Promise.all([
176
+ createFile(path.join(cwd, 'src/Button.ts')),
177
+ createFile(path.join(cwd, 'src/TextField.ts')),
178
+
179
+ // Output files
180
+ createFile(path.join(outputDir, 'Button.js')),
181
+ createFile(path.join(outputDir, 'Button.cjs')),
182
+ createFile(path.join(outputDir, 'Button.d.ts')),
183
+ createFile(path.join(outputDir, 'Button.d.cts')),
184
+ createFile(path.join(outputDir, 'TextField.js')),
185
+ createFile(path.join(outputDir, 'TextField.cjs')),
186
+ createFile(path.join(outputDir, 'TextField.d.ts')),
187
+ createFile(path.join(outputDir, 'TextField.d.cts')),
188
+ ]);
189
+
190
+ const { exports: packageExports } = await createPackageExports(
191
+ {
192
+ './*': './src/*.ts',
193
+ },
194
+ {
162
195
  bundles,
163
196
  outputDir,
164
197
  cwd,
165
198
  addTypes: true,
166
199
  isFlat: true,
167
200
  packageType: 'module',
168
- });
169
-
170
- expect(packageExports['./Button']).toEqual({
171
- import: { types: './Button.d.ts', default: './Button.js' },
172
- require: { types: './Button.d.cts', default: './Button.cjs' },
173
- default: { types: './Button.d.ts', default: './Button.js' },
174
- });
175
- expect(packageExports['./TextField']).toEqual({
176
- import: { types: './TextField.d.ts', default: './TextField.js' },
177
- require: { types: './TextField.d.cts', default: './TextField.cjs' },
178
- default: { types: './TextField.d.ts', default: './TextField.js' },
179
- });
180
- // glob key should not appear in the output
181
- expect(packageExports['./*']).toBeUndefined();
201
+ },
202
+ );
203
+
204
+ expect(packageExports['./Button']).toEqual({
205
+ import: { types: './Button.d.ts', default: './Button.js' },
206
+ require: { types: './Button.d.cts', default: './Button.cjs' },
207
+ default: { types: './Button.d.ts', default: './Button.js' },
182
208
  });
209
+ expect(packageExports['./TextField']).toEqual({
210
+ import: { types: './TextField.d.ts', default: './TextField.js' },
211
+ require: { types: './TextField.d.cts', default: './TextField.cjs' },
212
+ default: { types: './TextField.d.ts', default: './TextField.js' },
213
+ });
214
+ // glob key should not appear in the output
215
+ expect(packageExports['./*']).toBeUndefined();
183
216
  });
184
217
 
185
218
  it('expands glob with commonjs package type', async () => {
186
- await withTempDir(async (cwd) => {
187
- const outputDir = path.join(cwd, 'build');
188
- /**
189
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
190
- */
191
- const bundles = [
192
- { type: 'esm', dir: '.' },
193
- { type: 'cjs', dir: '.' },
194
- ];
219
+ const cwd = await makeTempDir();
220
+ const outputDir = path.join(cwd, 'build');
221
+ /**
222
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
223
+ */
224
+ const bundles = [
225
+ { type: 'esm', dir: '.' },
226
+ { type: 'cjs', dir: '.' },
227
+ ];
195
228
 
196
- await Promise.all([
197
- createFile(path.join(cwd, 'src/Button.ts')),
229
+ await Promise.all([
230
+ createFile(path.join(cwd, 'src/Button.ts')),
198
231
 
199
- createFile(path.join(outputDir, 'Button.js')),
200
- createFile(path.join(outputDir, 'Button.mjs')),
201
- createFile(path.join(outputDir, 'Button.d.ts')),
202
- createFile(path.join(outputDir, 'Button.d.mts')),
203
- ]);
232
+ createFile(path.join(outputDir, 'Button.js')),
233
+ createFile(path.join(outputDir, 'Button.mjs')),
234
+ createFile(path.join(outputDir, 'Button.d.ts')),
235
+ createFile(path.join(outputDir, 'Button.d.mts')),
236
+ ]);
204
237
 
205
- const { exports: packageExports } = await createPackageExports({
206
- exports: {
207
- './*': './src/*.ts',
208
- },
238
+ const { exports: packageExports } = await createPackageExports(
239
+ {
240
+ './*': './src/*.ts',
241
+ },
242
+ {
209
243
  bundles,
210
244
  outputDir,
211
245
  cwd,
212
246
  addTypes: true,
213
247
  isFlat: true,
214
248
  packageType: 'commonjs',
215
- });
249
+ },
250
+ );
216
251
 
217
- expect(packageExports['./Button']).toEqual({
218
- import: { types: './Button.d.mts', default: './Button.mjs' },
219
- require: { types: './Button.d.ts', default: './Button.js' },
220
- default: { types: './Button.d.mts', default: './Button.mjs' },
221
- });
252
+ expect(packageExports['./Button']).toEqual({
253
+ import: { types: './Button.d.mts', default: './Button.mjs' },
254
+ require: { types: './Button.d.ts', default: './Button.js' },
255
+ default: { types: './Button.d.mts', default: './Button.mjs' },
222
256
  });
223
257
  });
224
258
 
225
259
  it('expands glob with single CJS bundle', async () => {
226
- await withTempDir(async (cwd) => {
227
- const outputDir = path.join(cwd, 'build');
260
+ const cwd = await makeTempDir();
261
+ const outputDir = path.join(cwd, 'build');
228
262
 
229
- await Promise.all([
230
- createFile(path.join(cwd, 'src/Button.ts')),
263
+ await Promise.all([
264
+ createFile(path.join(cwd, 'src/Button.ts')),
231
265
 
232
- createFile(path.join(outputDir, 'Button.js')),
233
- createFile(path.join(outputDir, 'Button.d.ts')),
234
- ]);
266
+ createFile(path.join(outputDir, 'Button.js')),
267
+ createFile(path.join(outputDir, 'Button.d.ts')),
268
+ ]);
235
269
 
236
- const { exports: packageExports } = await createPackageExports({
237
- exports: {
238
- './*': './src/*.ts',
239
- },
270
+ const { exports: packageExports } = await createPackageExports(
271
+ {
272
+ './*': './src/*.ts',
273
+ },
274
+ {
240
275
  bundles: [{ type: 'cjs', dir: '.' }],
241
276
  outputDir,
242
277
  cwd,
243
278
  addTypes: true,
244
279
  isFlat: true,
245
280
  packageType: 'commonjs',
246
- });
281
+ },
282
+ );
247
283
 
248
- expect(packageExports['./Button']).toEqual({
249
- require: { types: './Button.d.ts', default: './Button.js' },
250
- default: { types: './Button.d.ts', default: './Button.js' },
251
- });
284
+ expect(packageExports['./Button']).toEqual({
285
+ require: { types: './Button.d.ts', default: './Button.js' },
286
+ default: { types: './Button.d.ts', default: './Button.js' },
252
287
  });
253
288
  });
254
289
 
255
- it('expands glob patterns with mui-src object values', async () => {
256
- await withTempDir(async (cwd) => {
257
- const outputDir = path.join(cwd, 'build');
258
- /**
259
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
260
- */
261
- const bundles = [
262
- { type: 'esm', dir: '.' },
263
- { type: 'cjs', dir: '.' },
264
- ];
265
-
266
- await Promise.all([
267
- createFile(path.join(cwd, 'src/Alert.ts')),
290
+ it('rewrites source paths inside sibling conditions when expanding globs', async () => {
291
+ const cwd = await makeTempDir();
292
+ const outputDir = path.join(cwd, 'build');
293
+ /**
294
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
295
+ */
296
+ const bundles = [
297
+ { type: 'esm', dir: '.' },
298
+ { type: 'cjs', dir: '.' },
299
+ ];
268
300
 
269
- createFile(path.join(outputDir, 'Alert.js')),
270
- createFile(path.join(outputDir, 'Alert.cjs')),
271
- ]);
301
+ await Promise.all([
302
+ createFile(path.join(cwd, 'src/Alert.ts')),
303
+ createFile(path.join(cwd, 'src/node/Alert.ts')),
304
+ ]);
272
305
 
273
- const { exports: packageExports } = await createPackageExports({
274
- exports: {
275
- './*': { 'mui-src': './src/*.ts' },
276
- },
306
+ const { exports: packageExports } = await createPackageExports(
307
+ {
308
+ './*': { node: './src/node/*.ts', default: './src/*.ts' },
309
+ },
310
+ {
277
311
  bundles,
278
312
  outputDir,
279
313
  cwd,
280
314
  isFlat: true,
281
315
  packageType: 'module',
282
- });
283
-
284
- expect(packageExports['./Alert']).toEqual({
316
+ },
317
+ );
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.
321
+ expect(packageExports['./Alert']).toEqual({
322
+ node: {
323
+ import: './node/Alert.js',
324
+ require: './node/Alert.cjs',
325
+ default: './node/Alert.js',
326
+ },
327
+ default: {
285
328
  import: './Alert.js',
286
329
  require: './Alert.cjs',
287
330
  default: './Alert.js',
288
- });
331
+ },
289
332
  });
290
333
  });
291
334
 
292
- it('preserves extra conditions from mui-src object values', async () => {
293
- await withTempDir(async (cwd) => {
294
- const outputDir = path.join(cwd, 'build');
295
- /**
296
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
297
- */
298
- const bundles = [
299
- { type: 'esm', dir: '.' },
300
- { type: 'cjs', dir: '.' },
301
- ];
302
-
303
- await Promise.all([
304
- createFile(path.join(cwd, 'src/Alert.ts')),
335
+ it('omits a sibling condition for stems its glob does not match', async () => {
336
+ const cwd = await makeTempDir();
337
+ const outputDir = path.join(cwd, 'build');
338
+ /**
339
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
340
+ */
341
+ const bundles = [
342
+ { type: 'esm', dir: '.' },
343
+ { type: 'cjs', dir: '.' },
344
+ ];
305
345
 
306
- createFile(path.join(outputDir, 'Alert.js')),
307
- createFile(path.join(outputDir, 'Alert.cjs')),
308
- ]);
346
+ await Promise.all([
347
+ createFile(path.join(cwd, 'src/Alert.ts')),
348
+ createFile(path.join(cwd, 'src/Button.ts')),
349
+ // Only Alert has a node variant.
350
+ createFile(path.join(cwd, 'src/node/Alert.ts')),
351
+ ]);
309
352
 
310
- const { exports: packageExports } = await createPackageExports({
311
- exports: {
312
- './*': { 'mui-src': './src/*.ts', node: './src/node/*.ts' },
313
- },
353
+ const { exports: packageExports } = await createPackageExports(
354
+ {
355
+ './*': { node: './src/node/*.ts', default: './src/*.ts' },
356
+ },
357
+ {
314
358
  bundles,
315
359
  outputDir,
316
360
  cwd,
317
361
  isFlat: true,
318
362
  packageType: 'module',
319
- });
320
-
321
- expect(packageExports['./Alert']).toEqual({
322
- import: { node: './src/node/*.ts', default: './Alert.js' },
323
- require: { node: './src/node/*.ts', default: './Alert.cjs' },
324
- default: './Alert.js',
325
- });
363
+ },
364
+ );
365
+
366
+ // Alert keeps its node condition.
367
+ expect(packageExports['./Alert']).toEqual({
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' },
326
379
  });
327
380
  });
328
381
 
329
- it('mixes glob and non-glob exports', async () => {
330
- await withTempDir(async (cwd) => {
331
- const outputDir = path.join(cwd, 'build');
332
- /**
333
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
334
- */
335
- const bundles = [
336
- { type: 'esm', dir: '.' },
337
- { type: 'cjs', dir: '.' },
338
- ];
382
+ it('enumerates a wildcard directory pattern', async () => {
383
+ const cwd = await makeTempDir();
384
+ const outputDir = path.join(cwd, 'build');
339
385
 
340
- await Promise.all([
341
- createFile(path.join(cwd, 'src/index.ts')),
342
- createFile(path.join(cwd, 'src/Chip.ts')),
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',
410
+ });
411
+ });
343
412
 
344
- createFile(path.join(outputDir, 'index.js')),
345
- createFile(path.join(outputDir, 'index.cjs')),
346
- createFile(path.join(outputDir, 'Chip.js')),
347
- createFile(path.join(outputDir, 'Chip.cjs')),
348
- ]);
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'));
349
417
 
350
- const { exports: packageExports } = await createPackageExports({
351
- exports: {
352
- '.': './src/index.ts',
418
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
419
+ try {
420
+ const { exports: packageExports } = await createPackageExports(
421
+ {
353
422
  './*': './src/*.ts',
354
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
+
439
+ it('mixes glob and non-glob exports', async () => {
440
+ const cwd = await makeTempDir();
441
+ const outputDir = path.join(cwd, 'build');
442
+ /**
443
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
444
+ */
445
+ const bundles = [
446
+ { type: 'esm', dir: '.' },
447
+ { type: 'cjs', dir: '.' },
448
+ ];
449
+
450
+ await Promise.all([
451
+ createFile(path.join(cwd, 'src/index.ts')),
452
+ createFile(path.join(cwd, 'src/Chip.ts')),
453
+
454
+ createFile(path.join(outputDir, 'index.js')),
455
+ createFile(path.join(outputDir, 'index.cjs')),
456
+ createFile(path.join(outputDir, 'Chip.js')),
457
+ createFile(path.join(outputDir, 'Chip.cjs')),
458
+ ]);
459
+
460
+ const { exports: packageExports } = await createPackageExports(
461
+ {
462
+ '.': './src/index.ts',
463
+ './*': './src/*.ts',
464
+ },
465
+ {
355
466
  bundles,
356
467
  outputDir,
357
468
  cwd,
358
469
  isFlat: true,
359
470
  packageType: 'module',
360
- });
361
-
362
- // Explicit export still works
363
- expect(packageExports['.']).toBeDefined();
364
- // Glob-expanded export
365
- expect(packageExports['./Chip']).toEqual({
366
- import: './Chip.js',
367
- require: './Chip.cjs',
368
- default: './Chip.js',
369
- });
471
+ },
472
+ );
473
+
474
+ // Explicit export still works
475
+ expect(packageExports['.']).toBeDefined();
476
+ // Glob-expanded export
477
+ expect(packageExports['./Chip']).toEqual({
478
+ import: './Chip.js',
479
+ require: './Chip.cjs',
480
+ default: './Chip.js',
370
481
  });
371
482
  });
372
483
 
373
484
  it('expands glob with subdirectory pattern', async () => {
374
- await withTempDir(async (cwd) => {
375
- const outputDir = path.join(cwd, 'build');
376
- /**
377
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
378
- */
379
- const bundles = [
380
- { type: 'esm', dir: '.' },
381
- { type: 'cjs', dir: '.' },
382
- ];
485
+ const cwd = await makeTempDir();
486
+ const outputDir = path.join(cwd, 'build');
487
+ /**
488
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
489
+ */
490
+ const bundles = [
491
+ { type: 'esm', dir: '.' },
492
+ { type: 'cjs', dir: '.' },
493
+ ];
383
494
 
384
- await Promise.all([
385
- createFile(path.join(cwd, 'src/utils/color.ts')),
386
- createFile(path.join(cwd, 'src/utils/size.ts')),
495
+ await Promise.all([
496
+ createFile(path.join(cwd, 'src/utils/color.ts')),
497
+ createFile(path.join(cwd, 'src/utils/size.ts')),
387
498
 
388
- createFile(path.join(outputDir, 'utils/color.js')),
389
- createFile(path.join(outputDir, 'utils/color.cjs')),
390
- createFile(path.join(outputDir, 'utils/size.js')),
391
- createFile(path.join(outputDir, 'utils/size.cjs')),
392
- ]);
499
+ createFile(path.join(outputDir, 'utils/color.js')),
500
+ createFile(path.join(outputDir, 'utils/color.cjs')),
501
+ createFile(path.join(outputDir, 'utils/size.js')),
502
+ createFile(path.join(outputDir, 'utils/size.cjs')),
503
+ ]);
393
504
 
394
- const { exports: packageExports } = await createPackageExports({
395
- exports: {
396
- './utils/*': './src/utils/*.ts',
397
- },
505
+ const { exports: packageExports } = await createPackageExports(
506
+ {
507
+ './utils/*': './src/utils/*.ts',
508
+ },
509
+ {
398
510
  bundles,
399
511
  outputDir,
400
512
  cwd,
401
513
  isFlat: true,
402
514
  packageType: 'module',
403
- });
404
-
405
- expect(packageExports['./utils/color']).toEqual({
406
- import: './utils/color.js',
407
- require: './utils/color.cjs',
408
- default: './utils/color.js',
409
- });
410
- expect(packageExports['./utils/size']).toEqual({
411
- import: './utils/size.js',
412
- require: './utils/size.cjs',
413
- default: './utils/size.js',
414
- });
515
+ },
516
+ );
517
+
518
+ expect(packageExports['./utils/color']).toEqual({
519
+ import: './utils/color.js',
520
+ require: './utils/color.cjs',
521
+ default: './utils/color.js',
522
+ });
523
+ expect(packageExports['./utils/size']).toEqual({
524
+ import: './utils/size.js',
525
+ require: './utils/size.cjs',
526
+ default: './utils/size.js',
415
527
  });
416
528
  });
417
529
 
418
530
  it('produces no entries when glob matches nothing', async () => {
419
- await withTempDir(async (cwd) => {
420
- const outputDir = path.join(cwd, 'build');
531
+ const cwd = await makeTempDir();
532
+ const outputDir = path.join(cwd, 'build');
421
533
 
422
- // Create the src directory but no .ts files in it
423
- createFile(path.join(cwd, 'src/.gitkeep'));
534
+ // Create the src directory but no .ts files in it
535
+ createFile(path.join(cwd, 'src/.gitkeep'));
424
536
 
425
- const { exports: packageExports } = await createPackageExports({
426
- exports: {
427
- './*': './src/*.ts',
428
- },
537
+ const { exports: packageExports } = await createPackageExports(
538
+ {
539
+ './*': './src/*.ts',
540
+ },
541
+ {
429
542
  bundles: [{ type: 'cjs', dir: '.' }],
430
543
  outputDir,
431
544
  cwd,
432
545
  isFlat: true,
433
- });
546
+ },
547
+ );
434
548
 
435
- // Only the default ./package.json entry should be present
436
- expect(Object.keys(packageExports)).toEqual(['./package.json']);
437
- });
549
+ // Only the default ./package.json entry should be present
550
+ expect(Object.keys(packageExports)).toEqual(['./package.json']);
438
551
  });
439
552
 
440
553
  it('expands globs in sorted order', async () => {
441
- await withTempDir(async (cwd) => {
442
- const outputDir = path.join(cwd, 'build');
554
+ const cwd = await makeTempDir();
555
+ const outputDir = path.join(cwd, 'build');
443
556
 
444
- await Promise.all([
445
- createFile(path.join(cwd, 'src/Zebra.ts')),
446
- createFile(path.join(cwd, 'src/Apple.ts')),
447
- createFile(path.join(cwd, 'src/Mango.ts')),
557
+ await Promise.all([
558
+ createFile(path.join(cwd, 'src/Zebra.ts')),
559
+ createFile(path.join(cwd, 'src/Apple.ts')),
560
+ createFile(path.join(cwd, 'src/Mango.ts')),
448
561
 
449
- createFile(path.join(outputDir, 'Zebra.js')),
450
- createFile(path.join(outputDir, 'Apple.js')),
451
- createFile(path.join(outputDir, 'Mango.js')),
452
- ]);
562
+ createFile(path.join(outputDir, 'Zebra.js')),
563
+ createFile(path.join(outputDir, 'Apple.js')),
564
+ createFile(path.join(outputDir, 'Mango.js')),
565
+ ]);
453
566
 
454
- const { exports: packageExports } = await createPackageExports({
455
- exports: {
456
- './*': './src/*.ts',
457
- },
567
+ const { exports: packageExports } = await createPackageExports(
568
+ {
569
+ './*': './src/*.ts',
570
+ },
571
+ {
458
572
  bundles: [{ type: 'cjs', dir: '.' }],
459
573
  outputDir,
460
574
  cwd,
461
575
  isFlat: true,
462
- });
576
+ },
577
+ );
463
578
 
464
- const exportKeys = Object.keys(packageExports).filter((k) => k !== './package.json');
465
- expect(exportKeys).toEqual(['./Apple', './Mango', './Zebra']);
466
- });
579
+ const exportKeys = Object.keys(packageExports).filter((k) => k !== './package.json');
580
+ expect(exportKeys).toEqual(['./Apple', './Mango', './Zebra']);
467
581
  });
468
582
 
469
583
  it('removes expanded entries matching a null-valued glob (negation)', async () => {
470
- await withTempDir(async (cwd) => {
471
- const outputDir = path.join(cwd, 'build');
472
- /**
473
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
474
- */
475
- const bundles = [
476
- { type: 'esm', dir: '.' },
477
- { type: 'cjs', dir: '.' },
478
- ];
479
-
480
- await Promise.all([
481
- createFile(path.join(cwd, 'src/Accordion.ts')),
482
- createFile(path.join(cwd, 'src/Button.ts')),
483
- createFile(path.join(cwd, 'src/ButtonBase.ts')),
484
-
485
- createFile(path.join(outputDir, 'Accordion.js')),
486
- createFile(path.join(outputDir, 'Accordion.cjs')),
487
- createFile(path.join(outputDir, 'Button.js')),
488
- createFile(path.join(outputDir, 'Button.cjs')),
489
- createFile(path.join(outputDir, 'ButtonBase.js')),
490
- createFile(path.join(outputDir, 'ButtonBase.cjs')),
491
- ]);
492
-
493
- const { exports: packageExports } = await createPackageExports({
494
- exports: {
495
- './*': './src/*.ts',
496
- './Button*': null,
497
- },
584
+ const cwd = await makeTempDir();
585
+ const outputDir = path.join(cwd, 'build');
586
+ /**
587
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
588
+ */
589
+ const bundles = [
590
+ { type: 'esm', dir: '.' },
591
+ { type: 'cjs', dir: '.' },
592
+ ];
593
+
594
+ await Promise.all([
595
+ createFile(path.join(cwd, 'src/Accordion.ts')),
596
+ createFile(path.join(cwd, 'src/Button.ts')),
597
+ createFile(path.join(cwd, 'src/ButtonBase.ts')),
598
+
599
+ createFile(path.join(outputDir, 'Accordion.js')),
600
+ createFile(path.join(outputDir, 'Accordion.cjs')),
601
+ createFile(path.join(outputDir, 'Button.js')),
602
+ createFile(path.join(outputDir, 'Button.cjs')),
603
+ createFile(path.join(outputDir, 'ButtonBase.js')),
604
+ createFile(path.join(outputDir, 'ButtonBase.cjs')),
605
+ ]);
606
+
607
+ const { exports: packageExports } = await createPackageExports(
608
+ {
609
+ './*': './src/*.ts',
610
+ './Button*': null,
611
+ },
612
+ {
498
613
  bundles,
499
614
  outputDir,
500
615
  cwd,
501
616
  isFlat: true,
502
617
  packageType: 'module',
503
- });
618
+ },
619
+ );
504
620
 
505
- expect(packageExports['./Accordion']).toBeDefined();
506
- expect(packageExports['./Button']).toBeUndefined();
507
- expect(packageExports['./ButtonBase']).toBeUndefined();
508
- // The negation glob key itself should not appear
509
- expect(packageExports['./Button*']).toBeUndefined();
510
- });
621
+ expect(packageExports['./Accordion']).toBeDefined();
622
+ expect(packageExports['./Button']).toBeUndefined();
623
+ expect(packageExports['./ButtonBase']).toBeUndefined();
624
+ // The negation glob key itself should not appear
625
+ expect(packageExports['./Button*']).toBeUndefined();
511
626
  });
512
627
 
513
628
  it('negation with null removes only matching keys', async () => {
514
- await withTempDir(async (cwd) => {
515
- const outputDir = path.join(cwd, 'build');
629
+ const cwd = await makeTempDir();
630
+ const outputDir = path.join(cwd, 'build');
516
631
 
517
- await Promise.all([
518
- createFile(path.join(cwd, 'src/Alert.ts')),
519
- createFile(path.join(cwd, 'src/AlertTitle.ts')),
520
- createFile(path.join(cwd, 'src/Button.ts')),
632
+ await Promise.all([
633
+ createFile(path.join(cwd, 'src/Alert.ts')),
634
+ createFile(path.join(cwd, 'src/AlertTitle.ts')),
635
+ createFile(path.join(cwd, 'src/Button.ts')),
521
636
 
522
- createFile(path.join(outputDir, 'Alert.js')),
523
- createFile(path.join(outputDir, 'AlertTitle.js')),
524
- createFile(path.join(outputDir, 'Button.js')),
525
- ]);
637
+ createFile(path.join(outputDir, 'Alert.js')),
638
+ createFile(path.join(outputDir, 'AlertTitle.js')),
639
+ createFile(path.join(outputDir, 'Button.js')),
640
+ ]);
526
641
 
527
- const { exports: packageExports } = await createPackageExports({
528
- exports: {
529
- './*': './src/*.ts',
530
- './Alert*': null,
531
- },
642
+ const { exports: packageExports } = await createPackageExports(
643
+ {
644
+ './*': './src/*.ts',
645
+ './Alert*': null,
646
+ },
647
+ {
532
648
  bundles: [{ type: 'cjs', dir: '.' }],
533
649
  outputDir,
534
650
  cwd,
535
651
  isFlat: true,
536
- });
652
+ },
653
+ );
537
654
 
538
- const exportKeys = Object.keys(packageExports).filter((k) => k !== './package.json');
539
- expect(exportKeys).toEqual(['./Button']);
540
- });
655
+ const exportKeys = Object.keys(packageExports).filter((k) => k !== './package.json');
656
+ expect(exportKeys).toEqual(['./Button']);
541
657
  });
542
658
 
543
659
  it('preserves null glob pattern when no keys match', async () => {
544
- await withTempDir(async (cwd) => {
545
- const outputDir = path.join(cwd, 'build');
660
+ const cwd = await makeTempDir();
661
+ const outputDir = path.join(cwd, 'build');
546
662
 
547
- await Promise.all([
548
- createFile(path.join(cwd, 'src/Button.ts')),
549
- createFile(path.join(outputDir, 'Button.js')),
550
- ]);
663
+ await Promise.all([
664
+ createFile(path.join(cwd, 'src/Button.ts')),
665
+ createFile(path.join(outputDir, 'Button.js')),
666
+ ]);
551
667
 
552
- const { exports: packageExports } = await createPackageExports({
553
- exports: {
554
- './*': './src/*.ts',
555
- './internal/*': null,
556
- },
668
+ const { exports: packageExports } = await createPackageExports(
669
+ {
670
+ './*': './src/*.ts',
671
+ './internal/*': null,
672
+ },
673
+ {
557
674
  bundles: [{ type: 'cjs', dir: '.' }],
558
675
  outputDir,
559
676
  cwd,
560
677
  isFlat: true,
561
- });
678
+ },
679
+ );
562
680
 
563
- // Button is kept since it doesn't match the negation
564
- expect(packageExports['./Button']).toBeDefined();
565
- // The negation pattern is preserved as null since nothing matched it
566
- expect(packageExports['./internal/*']).toBeNull();
567
- });
681
+ // Button is kept since it doesn't match the negation
682
+ expect(packageExports['./Button']).toBeDefined();
683
+ // The negation pattern is preserved as null since nothing matched it
684
+ expect(packageExports['./internal/*']).toBeNull();
568
685
  });
569
686
 
570
- it('does not expand glob patterns when isFlat is false', async () => {
571
- await withTempDir(async (cwd) => {
572
- const outputDir = path.join(cwd, 'build');
687
+ it('does not expand glob patterns when expand is false', async () => {
688
+ const cwd = await makeTempDir();
689
+ const outputDir = path.join(cwd, 'build');
573
690
 
574
- await Promise.all([
575
- createFile(path.join(cwd, 'src/Button.ts')),
576
- createFile(path.join(cwd, 'src/TextField.ts')),
577
- ]);
691
+ await Promise.all([
692
+ createFile(path.join(cwd, 'src/Button.ts')),
693
+ createFile(path.join(cwd, 'src/TextField.ts')),
694
+ ]);
578
695
 
579
- const { exports: packageExports } = await createPackageExports({
580
- exports: {
581
- './*': './src/*.ts',
582
- },
696
+ const { exports: packageExports } = await createPackageExports(
697
+ {
698
+ './*': './src/*.ts',
699
+ },
700
+ {
583
701
  bundles: [{ type: 'cjs', dir: '.' }],
584
702
  outputDir,
585
703
  cwd,
586
704
  isFlat: false,
587
- });
588
-
589
- // Glob should NOT be expanded to individual files
590
- expect(packageExports['./Button']).toBeUndefined();
591
- expect(packageExports['./TextField']).toBeUndefined();
592
- // The raw glob pattern is passed through as-is
593
- expect(packageExports['./*']).toEqual({
594
- require: './*.js',
595
- default: './*.js',
596
- });
705
+ expand: false,
706
+ },
707
+ );
708
+
709
+ // Glob should NOT be expanded to individual files
710
+ expect(packageExports['./Button']).toBeUndefined();
711
+ expect(packageExports['./TextField']).toBeUndefined();
712
+ // The raw glob pattern is passed through as-is
713
+ expect(packageExports['./*']).toEqual({
714
+ require: './*.js',
715
+ default: './*.js',
597
716
  });
598
717
  });
599
718
 
600
719
  it('passes through glob key when value has no wildcard', async () => {
601
- await withTempDir(async (cwd) => {
602
- const outputDir = path.join(cwd, 'build');
720
+ const cwd = await makeTempDir();
721
+ const outputDir = path.join(cwd, 'build');
603
722
 
604
- await Promise.all([
605
- createFile(path.join(cwd, 'src/index.ts')),
606
- createFile(path.join(outputDir, 'index.js')),
607
- ]);
723
+ await Promise.all([
724
+ createFile(path.join(cwd, 'src/index.ts')),
725
+ createFile(path.join(outputDir, 'index.js')),
726
+ ]);
608
727
 
609
- const { exports: packageExports } = await createPackageExports({
610
- exports: {
611
- './*': './src/index.ts',
612
- },
728
+ const { exports: packageExports } = await createPackageExports(
729
+ {
730
+ './*': './src/index.ts',
731
+ },
732
+ {
613
733
  bundles: [{ type: 'cjs', dir: '.' }],
614
734
  outputDir,
615
735
  cwd,
616
736
  isFlat: true,
617
- });
737
+ },
738
+ );
618
739
 
619
- // When the value has no *, the glob key is passed through as-is
620
- expect(packageExports['./*']).toBeDefined();
621
- });
740
+ // When the value has no *, the glob key is passed through as-is
741
+ expect(packageExports['./*']).toBeDefined();
622
742
  });
623
743
  });
624
744
 
625
745
  it('uses require/import and default for single bundle package', async () => {
626
- await withTempDir(async (cwd) => {
627
- const outputDir = path.join(cwd, 'build');
628
-
629
- await Promise.all([
630
- createFile(path.join(cwd, 'src/index.ts')),
631
- createFile(path.join(outputDir, 'index.js')),
632
- createFile(path.join(outputDir, 'index.d.ts')),
633
- ]);
634
-
635
- const { exports: packageExports } = await createPackageExports({
636
- exports: {
637
- '.': './src/index.ts',
638
- },
746
+ const cwd = await makeTempDir();
747
+ const outputDir = path.join(cwd, 'build');
748
+
749
+ await Promise.all([
750
+ createFile(path.join(cwd, 'src/index.ts')),
751
+ createFile(path.join(outputDir, 'index.js')),
752
+ createFile(path.join(outputDir, 'index.d.ts')),
753
+ ]);
754
+
755
+ const { exports: packageExports } = await createPackageExports(
756
+ {
757
+ '.': './src/index.ts',
758
+ },
759
+ {
639
760
  bundles: [{ type: 'cjs', dir: '.' }],
640
761
  outputDir,
641
762
  cwd,
642
763
  addTypes: true,
643
764
  isFlat: true,
644
765
  packageType: 'commonjs',
645
- });
766
+ },
767
+ );
768
+
769
+ // Single CJS bundle should have both require and default pointing to the same files
770
+ expect(packageExports['.']).toEqual({
771
+ require: {
772
+ types: './index.d.ts',
773
+ default: './index.js',
774
+ },
775
+ default: {
776
+ types: './index.d.ts',
777
+ default: './index.js',
778
+ },
779
+ });
780
+ });
781
+ });
646
782
 
647
- // Single CJS bundle should have both require and default pointing to the same files
648
- expect(packageExports['.']).toEqual({
649
- require: {
650
- types: './index.d.ts',
651
- default: './index.js',
652
- },
653
- default: {
654
- types: './index.d.ts',
655
- default: './index.js',
656
- },
657
- });
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',
658
1038
  });
659
1039
  });
660
1040
  });
661
1041
 
662
1042
  describe('createPackageBin', () => {
663
1043
  it('prefers the ESM bundle when available', async () => {
664
- await withTempDir(async (cwd) => {
665
- /**
666
- * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
667
- */
668
- const bundles = [
669
- { type: 'esm', dir: '.' },
670
- { type: 'cjs', dir: '.' },
671
- ];
1044
+ const cwd = await makeTempDir();
1045
+ /**
1046
+ * @type {{ type: import('./build.mjs').BundleType; dir: string }[]}
1047
+ */
1048
+ const bundles = [
1049
+ { type: 'esm', dir: '.' },
1050
+ { type: 'cjs', dir: '.' },
1051
+ ];
1052
+
1053
+ await Promise.all([createFile(path.join(cwd, 'src/cli.ts'))]);
1054
+
1055
+ let bin = await createPackageBin('./src/cli.ts', {
1056
+ bundles,
1057
+ cwd,
1058
+ isFlat: true,
1059
+ packageType: 'module',
1060
+ });
1061
+
1062
+ expect(bin).toBe('./cli.js');
1063
+
1064
+ bin = await createPackageBin('./src/cli.ts', {
1065
+ bundles: [bundles[1]], // only CJS bundle
1066
+ cwd,
1067
+ isFlat: true,
1068
+ });
672
1069
 
673
- await Promise.all([createFile(path.join(cwd, 'src/cli.ts'))]);
1070
+ expect(bin).toBe('./cli.js');
674
1071
 
675
- let bin = await createPackageBin({
676
- bin: './src/cli.ts',
1072
+ bin = await createPackageBin('./src/cli.ts', {
1073
+ bundles, // only CJS bundle
1074
+ cwd,
1075
+ isFlat: true,
1076
+ packageType: 'commonjs',
1077
+ });
1078
+
1079
+ expect(bin).toBe('./cli.mjs');
1080
+ });
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
+ {
677
1139
  bundles,
678
1140
  cwd,
1141
+ addTypes: true,
679
1142
  isFlat: true,
680
1143
  packageType: 'module',
681
- });
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
+ );
682
1181
 
683
- expect(bin).toBe('./cli.js');
1182
+ expect(
1183
+ Object.keys(/** @type {Record<string, unknown>} */ (imports?.['#internal/utils'])),
1184
+ ).toEqual(['import', 'require', 'default']);
1185
+ });
684
1186
 
685
- bin = await createPackageBin({
686
- bin: './src/cli.ts',
687
- bundles: [bundles[1]], // only CJS bundle
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
+ ],
688
1199
  cwd,
689
1200
  isFlat: true,
690
- });
1201
+ packageType: 'module',
1202
+ },
1203
+ );
691
1204
 
692
- expect(bin).toBe('./cli.js');
1205
+ expect(imports).toEqual({
1206
+ '#error-formatter': '@custom/error-formatter',
1207
+ });
1208
+ });
693
1209
 
694
- bin = await createPackageBin({
695
- bin: './src/cli.ts',
696
- bundles, // only CJS bundle
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: '.' }],
697
1227
  cwd,
698
1228
  isFlat: true,
699
- packageType: 'commonjs',
700
- });
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
+ });
701
1238
 
702
- expect(bin).toBe('./cli.mjs');
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
+ },
703
1282
  });
704
1283
  });
705
1284
  });