@esmx/core 3.0.0-rc.59 → 3.0.0-rc.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.
@@ -1,798 +1,797 @@
1
1
  import path from 'node:path';
2
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { describe, expect, it } from 'vitest';
3
3
  import {
4
4
  type ModuleConfig,
5
- type ModuleConfigExportExports,
6
- type ModuleConfigExportObject,
7
- type ParsedModuleConfig,
8
- type ParsedModuleConfigExport,
9
- parseModuleConfig
5
+ addPackageExportsToScopes,
6
+ createDefaultExports,
7
+ getEnvironmentExports,
8
+ getEnvironmentImports,
9
+ getEnvironmentScopes,
10
+ getEnvironments,
11
+ getLinks,
12
+ parseModuleConfig,
13
+ parsedExportValue,
14
+ processExportArray,
15
+ processObjectExport,
16
+ processStringExport,
17
+ resolveExportFile
10
18
  } from './module-config';
11
19
 
12
- describe('module-config', () => {
13
- const testModuleName = 'test-module';
14
- const testRoot = '/test/root';
15
-
20
+ describe('Module Config Parser', () => {
16
21
  describe('parseModuleConfig', () => {
17
- it('should parse empty configuration with defaults', () => {
18
- // Arrange
19
- const config: ModuleConfig = {};
22
+ it('should parse basic module config with name and root', () => {
23
+ const result = parseModuleConfig('test-module', '/test/root');
24
+ expect(result.name).toBe('test-module');
25
+ expect(result.root).toBe('/test/root');
26
+ expect(result.links).toBeDefined();
27
+ expect(result.environments).toBeDefined();
28
+ });
20
29
 
21
- // Act
22
- const result = parseModuleConfig(testModuleName, testRoot, config);
30
+ it('should handle empty config object', () => {
31
+ const result = parseModuleConfig('test-module', '/test/root', {});
32
+ expect(result.name).toBe('test-module');
33
+ expect(result.root).toBe('/test/root');
34
+ });
23
35
 
24
- // Assert
25
- expect(result.name).toBe(testModuleName);
26
- expect(result.root).toBe(testRoot);
27
- expect(result.imports).toEqual({});
28
- expect(result.links).toHaveProperty(testModuleName);
29
- expect(result.exports).toHaveProperty('src/entry.client');
30
- expect(result.exports).toHaveProperty('src/entry.server');
36
+ it('should handle undefined config parameter', () => {
37
+ const result = parseModuleConfig(
38
+ 'test-module',
39
+ '/test/root',
40
+ undefined
41
+ );
42
+ expect(result.name).toBe('test-module');
43
+ expect(result.root).toBe('/test/root');
31
44
  });
32
45
 
33
- it('should parse configuration without config parameter', () => {
34
- // Arrange & Act
35
- const result = parseModuleConfig(testModuleName, testRoot);
46
+ it('should process links configuration', () => {
47
+ const config: ModuleConfig = {
48
+ links: {
49
+ 'custom-link': '/custom/path'
50
+ }
51
+ };
52
+ const result = parseModuleConfig(
53
+ 'test-module',
54
+ '/test/root',
55
+ config
56
+ );
57
+ expect(result.links['custom-link']).toBeDefined();
58
+ });
36
59
 
37
- // Assert
38
- expect(result.name).toBe(testModuleName);
39
- expect(result.root).toBe(testRoot);
40
- expect(result.imports).toEqual({});
60
+ it('should generate client and server environments', () => {
61
+ const result = parseModuleConfig('test-module', '/test/root');
62
+ expect(result.environments.client).toBeDefined();
63
+ expect(result.environments.server).toBeDefined();
41
64
  });
42
65
 
43
- it('should parse complete configuration', () => {
44
- // Arrange
66
+ it('should handle absolute and relative paths in links', () => {
45
67
  const config: ModuleConfig = {
46
68
  links: {
47
- 'shared-lib': '../shared-lib/dist',
48
- 'api-utils': '/absolute/path/api-utils/dist'
49
- },
50
- imports: {
51
- axios: 'shared-lib/axios',
52
- lodash: 'shared-lib/lodash'
53
- },
54
- exports: {
55
- axios: 'axios',
56
- 'src/utils/format': './src/utils/format.ts',
57
- 'custom-api': './src/api/custom.ts'
69
+ absolute: '/absolute/path',
70
+ relative: 'relative/path'
58
71
  }
59
72
  };
73
+ const result = parseModuleConfig(
74
+ 'test-module',
75
+ '/test/root',
76
+ config
77
+ );
78
+ expect(result.links.absolute.root).toBe('/absolute/path');
79
+ expect(result.links.relative.root).toBe('relative/path');
80
+ });
60
81
 
61
- // Act
62
- const result = parseModuleConfig(testModuleName, testRoot, config);
63
-
64
- // Assert
65
- expect(result.name).toBe(testModuleName);
66
- expect(result.root).toBe(testRoot);
67
- expect(result.imports).toEqual(config.imports);
68
- expect(result.links).toHaveProperty('shared-lib');
69
- expect(result.links).toHaveProperty('api-utils');
70
- expect(result.exports).toHaveProperty('axios');
71
- expect(result.exports).toHaveProperty('src/utils/format');
72
- expect(result.exports).toHaveProperty('custom-api');
82
+ it('should maintain type safety across transformations', () => {
83
+ const result = parseModuleConfig('test-module', '/test/root');
84
+ expect(typeof result.name).toBe('string');
85
+ expect(typeof result.root).toBe('string');
86
+ expect(typeof result.links).toBe('object');
87
+ expect(typeof result.environments).toBe('object');
73
88
  });
74
89
  });
75
90
 
76
- describe('links processing', () => {
77
- it('should create self-link with default dist path', () => {
78
- // Arrange
79
- const config: ModuleConfig = {};
80
-
81
- // Act
82
- const result = parseModuleConfig(testModuleName, testRoot, config);
83
-
84
- // Assert
85
- const selfLink = result.links[testModuleName];
86
- expect(selfLink).toBeDefined();
87
- expect(selfLink.name).toBe(testModuleName);
88
- expect(selfLink.root).toBe(path.resolve(testRoot, 'dist'));
89
- expect(selfLink.client).toBe(path.resolve(testRoot, 'dist/client'));
90
- expect(selfLink.server).toBe(path.resolve(testRoot, 'dist/server'));
91
- expect(selfLink.clientManifestJson).toBe(
92
- path.resolve(testRoot, 'dist/client/manifest.json')
93
- );
94
- expect(selfLink.serverManifestJson).toBe(
95
- path.resolve(testRoot, 'dist/server/manifest.json')
96
- );
91
+ describe('getLinks', () => {
92
+ it('should create default link for module name', () => {
93
+ const result = getLinks('test-module', '/test/root', {});
94
+ expect(result['test-module']).toBeDefined();
95
+ expect(result['test-module'].name).toBe('test-module');
97
96
  });
98
97
 
99
- it('should process relative path links', () => {
100
- // Arrange
98
+ it('should process custom links configuration', () => {
101
99
  const config: ModuleConfig = {
102
100
  links: {
103
- 'shared-lib': '../shared-lib/dist'
101
+ 'custom-link': '/custom/path'
104
102
  }
105
103
  };
104
+ const result = getLinks('test-module', '/test/root', config);
105
+ expect(result['custom-link']).toBeDefined();
106
+ expect(result['custom-link'].name).toBe('custom-link');
107
+ });
106
108
 
107
- // Act
108
- const result = parseModuleConfig(testModuleName, testRoot, config);
109
-
110
- // Assert
111
- const sharedLibLink = result.links['shared-lib'];
112
- expect(sharedLibLink.name).toBe('shared-lib');
113
- expect(sharedLibLink.root).toBe('../shared-lib/dist');
114
- expect(sharedLibLink.client).toBe(
115
- path.resolve(testRoot, '../shared-lib/dist/client')
116
- );
117
- expect(sharedLibLink.server).toBe(
118
- path.resolve(testRoot, '../shared-lib/dist/server')
119
- );
109
+ it('should handle empty links object', () => {
110
+ const result = getLinks('test-module', '/test/root', {});
111
+ expect(Object.keys(result)).toHaveLength(1);
112
+ expect(result['test-module']).toBeDefined();
120
113
  });
121
114
 
122
- it('should process absolute path links', () => {
123
- // Arrange
124
- const absolutePath = '/absolute/path/api-utils/dist';
115
+ it('should resolve absolute paths correctly', () => {
125
116
  const config: ModuleConfig = {
126
117
  links: {
127
- 'api-utils': absolutePath
118
+ absolute: '/absolute/path'
128
119
  }
129
120
  };
130
-
131
- // Act
132
- const result = parseModuleConfig(testModuleName, testRoot, config);
133
-
134
- // Assert
135
- const apiUtilsLink = result.links['api-utils'];
136
- expect(apiUtilsLink.name).toBe('api-utils');
137
- expect(apiUtilsLink.root).toBe(absolutePath);
138
- expect(apiUtilsLink.client).toBe(
139
- path.resolve(absolutePath, 'client')
140
- );
141
- expect(apiUtilsLink.server).toBe(
142
- path.resolve(absolutePath, 'server')
143
- );
121
+ const result = getLinks('test-module', '/test/root', config);
122
+ expect(result.absolute.root).toBe('/absolute/path');
144
123
  });
145
124
 
146
- it('should handle multiple links', () => {
147
- // Arrange
125
+ it('should resolve relative paths from root', () => {
148
126
  const config: ModuleConfig = {
149
127
  links: {
150
- lib1: '../lib1/dist',
151
- lib2: '/absolute/lib2/dist',
152
- lib3: './relative/lib3/dist'
128
+ relative: 'relative/path'
153
129
  }
154
130
  };
131
+ const result = getLinks('test-module', '/test/root', config);
132
+ expect(result.relative.root).toBe('relative/path');
133
+ });
155
134
 
156
- // Act
157
- const result = parseModuleConfig(testModuleName, testRoot, config);
135
+ it('should generate client and server manifest paths', () => {
136
+ const result = getLinks('test-module', '/test/root', {});
137
+ const link = result['test-module'];
138
+ const expectedRoot = path.resolve('/test/root', 'dist');
139
+ expect(link.client).toBe(path.resolve(expectedRoot, 'client'));
140
+ expect(link.clientManifestJson).toBe(
141
+ path.resolve(expectedRoot, 'client/manifest.json')
142
+ );
143
+ expect(link.server).toBe(path.resolve(expectedRoot, 'server'));
144
+ expect(link.serverManifestJson).toBe(
145
+ path.resolve(expectedRoot, 'server/manifest.json')
146
+ );
147
+ });
148
+
149
+ it('should handle Windows path separators', () => {
150
+ const result = getLinks('test-module', 'C:\\test\\root', {});
151
+ const link = result['test-module'];
152
+ expect(link.client).toMatch(/[/\\]/);
153
+ expect(link.server).toMatch(/[/\\]/);
154
+ });
155
+
156
+ it('should handle Unix path separators', () => {
157
+ const result = getLinks('test-module', '/test/root', {});
158
+ const link = result['test-module'];
159
+ const expectedRoot = path.resolve('/test/root', 'dist');
160
+ expect(link.client).toBe(path.resolve(expectedRoot, 'client'));
161
+ expect(link.server).toBe(path.resolve(expectedRoot, 'server'));
162
+ });
158
163
 
159
- // Assert
160
- expect(Object.keys(result.links)).toHaveLength(4); // 3 + self-link
161
- expect(result.links).toHaveProperty('lib1');
162
- expect(result.links).toHaveProperty('lib2');
163
- expect(result.links).toHaveProperty('lib3');
164
- expect(result.links).toHaveProperty(testModuleName);
164
+ it('should handle paths with special characters', () => {
165
+ const result = getLinks('test-module', '/test/root@1.0.0', {});
166
+ const link = result['test-module'];
167
+ expect(link.root).toBe(path.resolve('/test/root@1.0.0', 'dist'));
168
+ });
169
+
170
+ it('should handle paths with spaces', () => {
171
+ const result = getLinks(
172
+ 'test-module',
173
+ '/test/root with spaces',
174
+ {}
175
+ );
176
+ const link = result['test-module'];
177
+ expect(link.root).toBe(
178
+ path.resolve('/test/root with spaces', 'dist')
179
+ );
165
180
  });
166
181
  });
167
182
 
168
- describe('exports processing', () => {
169
- describe('default exports', () => {
170
- it('should add default entry exports', () => {
171
- // Arrange
172
- const config: ModuleConfig = {};
173
-
174
- // Act
175
- const result = parseModuleConfig(
176
- testModuleName,
177
- testRoot,
178
- config
179
- );
183
+ describe('Environment Import Functions', () => {
184
+ describe('getEnvironmentImports', () => {
185
+ it('should filter imports by environment', () => {
186
+ const imports = {
187
+ react: 'react',
188
+ vue: {
189
+ client: 'vue',
190
+ server: 'vue/server'
191
+ }
192
+ };
193
+ const clientResult = getEnvironmentImports('client', imports);
194
+ const serverResult = getEnvironmentImports('server', imports);
180
195
 
181
- // Assert
182
- expect(result.exports['src/entry.client']).toEqual({
183
- name: 'src/entry.client',
184
- rewrite: true,
185
- entryPoints: {
186
- client: './src/entry.client',
187
- server: false
196
+ expect(clientResult.react).toBe('react');
197
+ expect(clientResult.vue).toBe('vue');
198
+ expect(serverResult.vue).toBe('vue/server');
199
+ });
200
+
201
+ it('should handle string import values', () => {
202
+ const imports = {
203
+ react: 'react'
204
+ };
205
+ const result = getEnvironmentImports('client', imports);
206
+ expect(result.react).toBe('react');
207
+ });
208
+
209
+ it('should handle object import values with matching environment', () => {
210
+ const imports = {
211
+ vue: {
212
+ client: 'vue',
213
+ server: 'vue/server'
188
214
  }
189
- });
190
-
191
- expect(result.exports['src/entry.server']).toEqual({
192
- name: 'src/entry.server',
193
- rewrite: true,
194
- entryPoints: {
195
- client: false,
196
- server: './src/entry.server'
215
+ };
216
+ const result = getEnvironmentImports('client', imports);
217
+ expect(result.vue).toBe('vue');
218
+ });
219
+
220
+ it('should skip imports when environment value is undefined', () => {
221
+ const imports = {
222
+ vue: {
223
+ client: 'vue',
224
+ server: 'vue/server'
197
225
  }
198
- });
226
+ };
227
+ const result = getEnvironmentImports('server', imports);
228
+ expect(result.vue).toBe('vue/server');
229
+ });
230
+
231
+ it('should handle empty imports object', () => {
232
+ const result = getEnvironmentImports('client', {});
233
+ expect(Object.keys(result)).toHaveLength(0);
199
234
  });
200
235
  });
201
236
 
202
- describe('array format', () => {
203
- it('should process npm: prefix exports', () => {
204
- // Arrange
205
- const config: ModuleConfig = {
206
- exports: ['npm:axios', 'npm:lodash']
237
+ describe('getEnvironmentScopes', () => {
238
+ it('should process scoped imports per environment', () => {
239
+ const scopes = {
240
+ utils: {
241
+ lodash: 'lodash',
242
+ moment: {
243
+ client: 'moment',
244
+ server: 'moment/server'
245
+ }
246
+ }
207
247
  };
248
+ const result = getEnvironmentScopes('client', scopes);
249
+ expect(result.utils.lodash).toBe('lodash');
250
+ expect(result.utils.moment).toBe('moment');
251
+ });
208
252
 
209
- // Act
210
- const result = parseModuleConfig(
211
- testModuleName,
212
- testRoot,
213
- config
214
- );
253
+ it('should handle empty scopes object', () => {
254
+ const result = getEnvironmentScopes('client', {});
255
+ expect(Object.keys(result)).toHaveLength(0);
256
+ });
257
+ });
215
258
 
216
- // Assert
217
- expect(result.exports.axios).toEqual({
218
- name: 'axios',
219
- rewrite: false,
220
- entryPoints: {
221
- client: 'axios',
222
- server: 'axios'
223
- }
224
- });
225
-
226
- expect(result.exports.lodash).toEqual({
227
- name: 'lodash',
228
- rewrite: false,
229
- entryPoints: {
230
- client: 'lodash',
231
- server: 'lodash'
259
+ describe('getEnvironments', () => {
260
+ it('should combine imports, exports and scopes', () => {
261
+ const config: ModuleConfig = {
262
+ imports: {
263
+ react: 'react'
264
+ },
265
+ scopes: {
266
+ utils: {
267
+ lodash: 'lodash'
268
+ }
232
269
  }
233
- });
270
+ };
271
+ const result = getEnvironments(config, 'client', 'test-module');
272
+ expect(result.imports.react).toBe('react');
273
+ expect(result.scopes.utils.lodash).toBe('lodash');
274
+ expect(result.exports).toBeDefined();
234
275
  });
235
276
 
236
- it('should process root: prefix exports with file extensions', () => {
237
- // Arrange
277
+ it('should preserve import mapping types', () => {
238
278
  const config: ModuleConfig = {
239
- exports: [
240
- 'root:src/utils/format.ts',
241
- 'root:src/components/Button.jsx',
242
- 'root:src/api/client.js'
243
- ]
279
+ imports: {
280
+ react: 'react'
281
+ }
244
282
  };
283
+ const result = getEnvironments(config, 'client', 'test-module');
284
+ expect(typeof result.imports).toBe('object');
285
+ expect(typeof result.exports).toBe('object');
286
+ expect(typeof result.scopes).toBe('object');
287
+ });
245
288
 
246
- // Act
247
- const result = parseModuleConfig(
248
- testModuleName,
249
- testRoot,
250
- config
251
- );
252
-
253
- // Assert
254
- expect(result.exports['src/utils/format']).toEqual({
255
- name: 'src/utils/format',
256
- rewrite: true,
257
- entryPoints: {
258
- client: './src/utils/format',
259
- server: './src/utils/format'
260
- }
261
- });
262
-
263
- expect(result.exports['src/components/Button']).toEqual({
264
- name: 'src/components/Button',
265
- rewrite: true,
266
- entryPoints: {
267
- client: './src/components/Button',
268
- server: './src/components/Button'
269
- }
270
- });
271
-
272
- expect(result.exports['src/api/client']).toEqual({
273
- name: 'src/api/client',
274
- rewrite: true,
275
- entryPoints: {
276
- client: './src/api/client',
277
- server: './src/api/client'
278
- }
279
- });
280
- });
281
-
282
- it('should handle all supported file extensions', () => {
283
- // Arrange
284
- const extensions = [
285
- 'js',
286
- 'mjs',
287
- 'cjs',
288
- 'jsx',
289
- 'mjsx',
290
- 'cjsx',
291
- 'ts',
292
- 'mts',
293
- 'cts',
294
- 'tsx',
295
- 'mtsx',
296
- 'ctsx'
297
- ];
289
+ it('should add pkg exports to scopes with empty string key', () => {
298
290
  const config: ModuleConfig = {
299
- exports: extensions.map((ext) => `root:src/test.${ext}`)
291
+ exports: ['pkg:lodash', 'pkg:react', './src/component']
300
292
  };
301
-
302
- // Act
303
- const result = parseModuleConfig(
304
- testModuleName,
305
- testRoot,
306
- config
307
- );
308
-
309
- // Assert
310
- extensions.forEach((ext) => {
311
- expect(result.exports['src/test']).toBeDefined();
312
- });
293
+ const result = getEnvironments(config, 'client', 'test-module');
294
+ const emptyScope = result.scopes[''];
295
+ expect(emptyScope).toBeDefined();
296
+ expect(emptyScope.lodash).toBe('test-module/lodash');
297
+ expect(emptyScope.react).toBe('test-module/react');
298
+ expect(emptyScope['./src/component']).toBeUndefined();
313
299
  });
314
300
 
315
- it('should handle object exports in array', () => {
316
- // Arrange
301
+ it('should not override existing empty string scope', () => {
317
302
  const config: ModuleConfig = {
318
- exports: [
319
- 'npm:axios',
320
- {
321
- 'custom-api': './src/api/custom.ts',
322
- utils: {
323
- input: './src/utils/index.ts',
324
- rewrite: true
325
- }
303
+ exports: ['pkg:lodash'],
304
+ scopes: {
305
+ '': {
306
+ existing: 'existing-value'
326
307
  }
327
- ]
308
+ }
328
309
  };
310
+ const result = getEnvironments(config, 'client', 'test-module');
311
+ const emptyScope = result.scopes[''];
312
+ expect(emptyScope).toBeDefined();
313
+ expect(emptyScope.existing).toBe('existing-value');
314
+ expect(emptyScope.lodash).toBe('test-module/lodash');
315
+ });
316
+ });
329
317
 
330
- // Act
331
- const result = parseModuleConfig(
332
- testModuleName,
333
- testRoot,
334
- config
318
+ describe('addPackageExportsToScopes', () => {
319
+ it('should create scopes for pkg exports', () => {
320
+ const exports = {
321
+ lodash: { name: 'lodash', file: 'lodash', pkg: true },
322
+ react: { name: 'react', file: 'react', pkg: true },
323
+ './src/component': {
324
+ name: './src/component',
325
+ file: './src/component',
326
+ pkg: false
327
+ }
328
+ };
329
+ const scopes: Record<string, Record<string, string>> = {};
330
+ const result = addPackageExportsToScopes(
331
+ exports,
332
+ scopes,
333
+ 'test-module'
335
334
  );
335
+ const emptyScope = result[''];
336
+ expect(emptyScope).toBeDefined();
337
+ expect(emptyScope.lodash).toBe('test-module/lodash');
338
+ expect(emptyScope.react).toBe('test-module/react');
339
+ expect(emptyScope['./src/component']).toBeUndefined();
340
+ });
336
341
 
337
- // Assert
338
- expect(result.exports['custom-api']).toEqual({
339
- name: 'custom-api',
340
- rewrite: true,
341
- entryPoints: {
342
- client: './src/api/custom.ts',
343
- server: './src/api/custom.ts'
344
- }
345
- });
346
-
347
- expect(result.exports.utils).toEqual({
348
- name: 'utils',
349
- rewrite: true,
350
- entryPoints: {
351
- client: './src/utils/index.ts',
352
- server: './src/utils/index.ts'
342
+ it('should not override existing empty string scope', () => {
343
+ const exports = {
344
+ lodash: { name: 'lodash', file: 'lodash', pkg: true }
345
+ };
346
+ const scopes = {
347
+ '': {
348
+ existing: 'existing-value'
353
349
  }
354
- });
350
+ };
351
+ const result = addPackageExportsToScopes(
352
+ exports,
353
+ scopes,
354
+ 'test-module'
355
+ );
356
+ const emptyScope = result[''];
357
+ expect(emptyScope.existing).toBe('existing-value');
358
+ expect(emptyScope.lodash).toBe('test-module/lodash');
355
359
  });
356
360
 
357
- it('should handle invalid export strings', () => {
358
- // Arrange
359
- const consoleSpy = vi
360
- .spyOn(console, 'error')
361
- .mockImplementation(() => {});
362
- const config: ModuleConfig = {
363
- exports: ['invalid-export', 'another-invalid']
364
- };
361
+ it('should handle empty exports', () => {
362
+ const exports = {};
363
+ const scopes: Record<string, Record<string, string>> = {};
364
+ const result = addPackageExportsToScopes(
365
+ exports,
366
+ scopes,
367
+ 'test-module'
368
+ );
369
+ expect(Object.keys(result)).toHaveLength(0);
370
+ });
371
+ });
372
+ });
365
373
 
366
- // Act
367
- parseModuleConfig(testModuleName, testRoot, config);
374
+ describe('Export Processing Functions', () => {
375
+ describe('createDefaultExports', () => {
376
+ it('should generate client default exports', () => {
377
+ const result = createDefaultExports('client');
378
+ expect(result['src/entry.client'].file).toBe(
379
+ './src/entry.client'
380
+ );
381
+ expect(result['src/entry.server'].file).toBe('');
382
+ });
368
383
 
369
- // Assert
370
- expect(consoleSpy).toHaveBeenCalledWith(
371
- 'Invalid module export: invalid-export'
384
+ it('should generate server default exports', () => {
385
+ const result = createDefaultExports('server');
386
+ expect(result['src/entry.client'].file).toBe('');
387
+ expect(result['src/entry.server'].file).toBe(
388
+ './src/entry.server'
372
389
  );
373
- expect(consoleSpy).toHaveBeenCalledWith(
374
- 'Invalid module export: another-invalid'
390
+ });
391
+
392
+ it('should handle client environment switch case', () => {
393
+ const result = createDefaultExports('client');
394
+ expect(result['src/entry.client'].file).toBe(
395
+ './src/entry.client'
375
396
  );
397
+ expect(result['src/entry.server'].file).toBe('');
398
+ });
376
399
 
377
- consoleSpy.mockRestore();
400
+ it('should handle server environment switch case', () => {
401
+ const result = createDefaultExports('server');
402
+ expect(result['src/entry.client'].file).toBe('');
403
+ expect(result['src/entry.server'].file).toBe(
404
+ './src/entry.server'
405
+ );
378
406
  });
379
407
  });
380
408
 
381
- describe('object format', () => {
382
- it('should process simple string mappings', () => {
383
- // Arrange
384
- const config: ModuleConfig = {
385
- exports: {
386
- axios: 'axios',
387
- utils: './src/utils/index.ts'
409
+ describe('processStringExport', () => {
410
+ it('should parse simple string export', () => {
411
+ const result = processStringExport('./src/component');
412
+ expect(result['./src/component']).toBeDefined();
413
+ expect(result['./src/component'].file).toBe('./src/component');
414
+ });
415
+ });
416
+
417
+ describe('processObjectExport', () => {
418
+ it('should handle environment-specific exports', () => {
419
+ const exportObject = {
420
+ './src/component': {
421
+ client: './src/component.client',
422
+ server: './src/component.server'
388
423
  }
389
424
  };
390
-
391
- // Act
392
- const result = parseModuleConfig(
393
- testModuleName,
394
- testRoot,
395
- config
425
+ const result = processObjectExport(exportObject, 'client');
426
+ expect(result['./src/component'].file).toBe(
427
+ './src/component.client'
396
428
  );
397
-
398
- // Assert
399
- expect(result.exports.axios).toEqual({
400
- name: 'axios',
401
- rewrite: true,
402
- entryPoints: {
403
- client: 'axios',
404
- server: 'axios'
405
- }
406
- });
407
-
408
- expect(result.exports.utils).toEqual({
409
- name: 'utils',
410
- rewrite: true,
411
- entryPoints: {
412
- client: './src/utils/index.ts',
413
- server: './src/utils/index.ts'
414
- }
415
- });
416
429
  });
417
430
 
418
- it('should process complete export objects', () => {
419
- // Arrange
420
- const config: ModuleConfig = {
421
- exports: {
422
- storage: {
423
- entryPoints: {
424
- client: './src/storage/indexedDB.ts',
425
- server: './src/storage/filesystem.ts'
426
- },
427
- rewrite: true
428
- },
429
- 'npm-package': {
430
- input: 'some-package',
431
- rewrite: false
432
- }
431
+ it('should process mixed string and object exports', () => {
432
+ const exportObject = {
433
+ './src/utils': './src/utils',
434
+ './src/component': {
435
+ client: './src/component.client',
436
+ server: './src/component.server'
433
437
  }
434
438
  };
435
-
436
- // Act
437
- const result = parseModuleConfig(
438
- testModuleName,
439
- testRoot,
440
- config
439
+ const result = processObjectExport(exportObject, 'client');
440
+ expect(result['./src/utils'].file).toBe('./src/utils');
441
+ expect(result['./src/component'].file).toBe(
442
+ './src/component.client'
441
443
  );
444
+ });
442
445
 
443
- // Assert
444
- expect(result.exports.storage).toEqual({
445
- name: 'storage',
446
- rewrite: true,
447
- entryPoints: {
448
- client: './src/storage/indexedDB.ts',
449
- server: './src/storage/filesystem.ts'
450
- }
451
- });
452
-
453
- expect(result.exports['npm-package']).toEqual({
454
- name: 'npm-package',
455
- rewrite: false,
456
- entryPoints: {
457
- client: 'some-package',
458
- server: 'some-package'
459
- }
460
- });
446
+ it('should handle string config values in export object', () => {
447
+ const exportObject = {
448
+ './src/utils': './src/utils'
449
+ };
450
+ const result = processObjectExport(exportObject, 'client');
451
+ expect(result['./src/utils'].file).toBe('./src/utils');
461
452
  });
462
453
 
463
- it('should handle entryPoints with false values', () => {
464
- // Arrange
465
- const config: ModuleConfig = {
466
- exports: {
467
- 'client-only': {
468
- entryPoints: {
469
- client: './src/client-feature.ts',
470
- server: false
471
- }
472
- },
473
- 'server-only': {
474
- entryPoints: {
475
- client: false,
476
- server: './src/server-feature.ts'
477
- }
478
- }
454
+ it('should handle object config values in export object', () => {
455
+ const exportObject = {
456
+ './src/component': {
457
+ client: './src/component.client',
458
+ server: './src/component.server'
479
459
  }
480
460
  };
481
-
482
- // Act
483
- const result = parseModuleConfig(
484
- testModuleName,
485
- testRoot,
486
- config
461
+ const result = processObjectExport(exportObject, 'client');
462
+ expect(result['./src/component'].file).toBe(
463
+ './src/component.client'
487
464
  );
465
+ });
488
466
 
489
- // Assert
490
- expect(result.exports['client-only']).toEqual({
491
- name: 'client-only',
492
- rewrite: true,
493
- entryPoints: {
494
- client: './src/client-feature.ts',
495
- server: false
496
- }
497
- });
498
-
499
- expect(result.exports['server-only']).toEqual({
500
- name: 'server-only',
501
- rewrite: true,
502
- entryPoints: {
503
- client: false,
504
- server: './src/server-feature.ts'
505
- }
506
- });
467
+ it('should handle empty export object', () => {
468
+ const result = processObjectExport({}, 'client');
469
+ expect(Object.keys(result)).toHaveLength(0);
507
470
  });
508
471
  });
509
472
 
510
- describe('mixed configurations', () => {
511
- it('should handle complex mixed export configuration', () => {
512
- // Arrange
513
- const config: ModuleConfig = {
514
- exports: {
515
- // Simple string mapping
516
- simple: './src/simple.ts',
517
-
518
- // Complete object with entryPoints
519
- complex: {
520
- entryPoints: {
521
- client: './src/complex.client.ts',
522
- server: './src/complex.server.ts'
523
- },
524
- rewrite: false
525
- },
526
-
527
- // Object with just input
528
- 'with-input': {
529
- input: './src/with-input.ts'
530
- },
531
-
532
- // Object with just rewrite
533
- 'with-rewrite': {
534
- rewrite: false
535
- }
536
- }
537
- };
538
-
539
- // Act
540
- const result = parseModuleConfig(
541
- testModuleName,
542
- testRoot,
543
- config
473
+ describe('resolveExportFile', () => {
474
+ it('should handle string config', () => {
475
+ const result = resolveExportFile(
476
+ './src/component',
477
+ 'client',
478
+ 'component'
544
479
  );
480
+ expect(result).toBe('./src/component');
481
+ });
545
482
 
546
- // Assert
547
- expect(result.exports.simple).toEqual({
548
- name: 'simple',
549
- rewrite: true,
550
- entryPoints: {
551
- client: './src/simple.ts',
552
- server: './src/simple.ts'
553
- }
554
- });
555
-
556
- expect(result.exports.complex).toEqual({
557
- name: 'complex',
558
- rewrite: false,
559
- entryPoints: {
560
- client: './src/complex.client.ts',
561
- server: './src/complex.server.ts'
562
- }
563
- });
564
-
565
- expect(result.exports['with-input']).toEqual({
566
- name: 'with-input',
567
- rewrite: true,
568
- entryPoints: {
569
- client: './src/with-input.ts',
570
- server: './src/with-input.ts'
571
- }
572
- });
573
-
574
- expect(result.exports['with-rewrite']).toEqual({
575
- name: 'with-rewrite',
576
- rewrite: false,
577
- entryPoints: {
578
- client: 'with-rewrite',
579
- server: 'with-rewrite'
580
- }
581
- });
483
+ it('should return string config directly', () => {
484
+ const result = resolveExportFile(
485
+ './src/component',
486
+ 'client',
487
+ 'component'
488
+ );
489
+ expect(result).toBe('./src/component');
582
490
  });
583
- });
584
491
 
585
- describe('default value handling', () => {
586
- it('should use default rewrite value of true', () => {
587
- // Arrange
588
- const config: ModuleConfig = {
589
- exports: {
590
- 'test-export': {
591
- input: './src/test.ts'
592
- // rewrite not specified, should default to true
593
- }
594
- }
492
+ it('should resolve environment-specific paths', () => {
493
+ const config = {
494
+ client: './src/component.client',
495
+ server: './src/component.server'
595
496
  };
497
+ const result = resolveExportFile(config, 'client', 'component');
498
+ expect(result).toBe('./src/component.client');
499
+ });
596
500
 
597
- // Act
598
- const result = parseModuleConfig(
599
- testModuleName,
600
- testRoot,
601
- config
602
- );
501
+ it('should return empty string when value is false', () => {
502
+ const config = {
503
+ client: false as const,
504
+ server: './src/component.server'
505
+ };
506
+ const result = resolveExportFile(config, 'client', 'component');
507
+ expect(result).toBe('');
508
+ });
603
509
 
604
- // Assert
605
- expect(result.exports['test-export'].rewrite).toBe(true);
510
+ it('should return name when value is empty string', () => {
511
+ const config = {
512
+ client: '',
513
+ server: './src/component.server'
514
+ };
515
+ const result = resolveExportFile(config, 'client', 'component');
516
+ expect(result).toBe('component');
606
517
  });
607
518
 
608
- it('should use export name as fallback for input paths', () => {
609
- // Arrange
610
- const config: ModuleConfig = {
611
- exports: {
612
- 'fallback-test': {
613
- // No input or entryPoints specified
614
- rewrite: false
615
- }
616
- }
519
+ it("should return environment value when it's a string", () => {
520
+ const config = {
521
+ client: './src/component.client',
522
+ server: './src/component.server'
617
523
  };
524
+ const result = resolveExportFile(config, 'client', 'component');
525
+ expect(result).toBe('./src/component.client');
526
+ });
618
527
 
619
- // Act
620
- const result = parseModuleConfig(
621
- testModuleName,
622
- testRoot,
623
- config
624
- );
528
+ it('should return name when environment value is undefined', () => {
529
+ const config = {
530
+ client: './src/component.client'
531
+ } as any;
532
+ const result = resolveExportFile(config, 'server', 'component');
533
+ expect(result).toBe('component');
534
+ });
625
535
 
626
- // Assert
627
- expect(result.exports['fallback-test'].entryPoints).toEqual({
628
- client: 'fallback-test',
629
- server: 'fallback-test'
630
- });
536
+ it('should handle invalid config types gracefully', () => {
537
+ const result = resolveExportFile(
538
+ {} as any,
539
+ 'client',
540
+ 'component'
541
+ );
542
+ expect(result).toBe('component');
631
543
  });
632
544
  });
633
- });
634
545
 
635
- describe('imports processing', () => {
636
- it('should pass through imports configuration unchanged', () => {
637
- // Arrange
638
- const imports = {
639
- axios: 'shared-lib/axios',
640
- lodash: 'shared-lib/lodash',
641
- 'custom-lib': 'api-utils/custom'
642
- };
643
- const config: ModuleConfig = { imports };
546
+ describe('processExportArray', () => {
547
+ it('should combine multiple export configurations', () => {
548
+ const exportArray = ['./src/component1', './src/component2'];
549
+ const result = processExportArray(exportArray, 'client');
550
+ expect(result['./src/component1']).toBeDefined();
551
+ expect(result['./src/component2']).toBeDefined();
552
+ });
644
553
 
645
- // Act
646
- const result = parseModuleConfig(testModuleName, testRoot, config);
554
+ it('should handle string items in export array', () => {
555
+ const exportArray = ['./src/component'];
556
+ const result = processExportArray(exportArray, 'client');
557
+ expect(result['./src/component']).toBeDefined();
558
+ });
647
559
 
648
- // Assert
649
- expect(result.imports).toEqual(imports);
560
+ it('should handle object items in export array', () => {
561
+ const exportArray = [
562
+ {
563
+ './src/component': './src/component'
564
+ }
565
+ ];
566
+ const result = processExportArray(exportArray, 'client');
567
+ expect(result['./src/component']).toBeDefined();
568
+ });
569
+
570
+ it('should handle empty export array', () => {
571
+ const result = processExportArray([], 'client');
572
+ expect(Object.keys(result)).toHaveLength(0);
573
+ });
650
574
  });
651
575
 
652
- it('should handle empty imports', () => {
653
- // Arrange
654
- const config: ModuleConfig = {};
576
+ describe('getEnvironmentExports', () => {
577
+ it('should merge default and user exports', () => {
578
+ const config: ModuleConfig = {
579
+ exports: ['./src/custom']
580
+ };
581
+ const result = getEnvironmentExports(config, 'client');
582
+ expect(result['src/entry.client']).toBeDefined();
583
+ expect(result['./src/custom']).toBeDefined();
584
+ });
655
585
 
656
- // Act
657
- const result = parseModuleConfig(testModuleName, testRoot, config);
586
+ it('should handle config without exports property', () => {
587
+ const result = getEnvironmentExports({}, 'client');
588
+ expect(result['src/entry.client']).toBeDefined();
589
+ });
658
590
 
659
- // Assert
660
- expect(result.imports).toEqual({});
591
+ it('should handle config with exports property', () => {
592
+ const config: ModuleConfig = {
593
+ exports: ['./src/custom']
594
+ };
595
+ const result = getEnvironmentExports(config, 'client');
596
+ expect(result['./src/custom']).toBeDefined();
597
+ });
661
598
  });
599
+ });
662
600
 
663
- it('should handle undefined imports', () => {
664
- // Arrange
665
- const config: ModuleConfig = {
666
- links: { test: './test' },
667
- exports: ['npm:axios']
668
- // imports intentionally omitted
669
- };
601
+ describe('parsedExportValue', () => {
602
+ it('should handle pkg: prefixed exports', () => {
603
+ const result = parsedExportValue('pkg:lodash');
604
+ expect(result.name).toBe('lodash');
605
+ expect(result.pkg).toBe(true);
606
+ expect(result.file).toBe('lodash');
607
+ });
670
608
 
671
- // Act
672
- const result = parseModuleConfig(testModuleName, testRoot, config);
609
+ it('should handle root: prefixed exports', () => {
610
+ const result = parsedExportValue('root:src/component.tsx');
611
+ expect(result.name).toBe('src/component');
612
+ expect(result.pkg).toBe(false);
613
+ expect(result.file).toBe('./src/component.tsx');
614
+ });
673
615
 
674
- // Assert
675
- expect(result.imports).toEqual({});
616
+ it('should process regular file exports', () => {
617
+ const result = parsedExportValue('./src/component');
618
+ expect(result.name).toBe('./src/component');
619
+ expect(result.pkg).toBe(false);
620
+ expect(result.file).toBe('./src/component');
676
621
  });
677
- });
678
622
 
679
- describe('edge cases', () => {
680
- it('should handle empty exports array', () => {
681
- // Arrange
682
- const config: ModuleConfig = {
683
- exports: []
684
- };
623
+ it('should handle pkg: prefixed values correctly', () => {
624
+ const result = parsedExportValue('pkg:@scope/package');
625
+ expect(result.name).toBe('@scope/package');
626
+ expect(result.pkg).toBe(true);
627
+ expect(result.file).toBe('@scope/package');
628
+ });
685
629
 
686
- // Act
687
- const result = parseModuleConfig(testModuleName, testRoot, config);
630
+ it('should handle root: prefixed values with extension removal', () => {
631
+ const result = parsedExportValue('root:src/component.tsx');
632
+ expect(result.name).toBe('src/component');
633
+ expect(result.pkg).toBe(false);
634
+ expect(result.file).toBe('./src/component.tsx');
635
+ });
688
636
 
689
- // Assert
690
- // Should still have default exports
691
- expect(result.exports).toHaveProperty('src/entry.client');
692
- expect(result.exports).toHaveProperty('src/entry.server');
693
- expect(Object.keys(result.exports)).toHaveLength(2);
637
+ it('should handle root: prefixed values without extensions', () => {
638
+ const result = parsedExportValue('root:src/utils');
639
+ expect(result.name).toBe('src/utils');
640
+ expect(result.pkg).toBe(false);
641
+ expect(result.file).toBe('./src/utils');
694
642
  });
695
643
 
696
- it('should handle empty exports object', () => {
697
- // Arrange
698
- const config: ModuleConfig = {
699
- exports: {}
700
- };
644
+ it('should handle regular values without prefixes', () => {
645
+ const result = parsedExportValue('./src/component.tsx');
646
+ expect(result.name).toBe('./src/component.tsx');
647
+ expect(result.pkg).toBe(false);
648
+ expect(result.file).toBe('./src/component.tsx');
649
+ });
701
650
 
702
- // Act
703
- const result = parseModuleConfig(testModuleName, testRoot, config);
651
+ it('should preserve file extensions for non-root exports', () => {
652
+ const result = parsedExportValue('./src/component.tsx');
653
+ expect(result.file).toBe('./src/component.tsx');
654
+ });
704
655
 
705
- // Assert
706
- // Should still have default exports
707
- expect(result.exports).toHaveProperty('src/entry.client');
708
- expect(result.exports).toHaveProperty('src/entry.server');
709
- expect(Object.keys(result.exports)).toHaveLength(2);
656
+ it('should handle malformed pkg: values', () => {
657
+ const result = parsedExportValue('pkg:');
658
+ expect(result.name).toBe('');
659
+ expect(result.pkg).toBe(true);
660
+ expect(result.file).toBe('');
710
661
  });
711
662
 
712
- it('should handle null and undefined values gracefully', () => {
713
- // Arrange
714
- const config: ModuleConfig = {
715
- links: undefined,
716
- imports: undefined,
717
- exports: undefined
718
- };
663
+ it('should handle malformed root: values', () => {
664
+ const result = parsedExportValue('root:');
665
+ expect(result.name).toBe('');
666
+ expect(result.pkg).toBe(false);
667
+ expect(result.file).toBe('./');
668
+ });
719
669
 
720
- // Act
721
- const result = parseModuleConfig(testModuleName, testRoot, config);
670
+ it('should handle file extensions correctly in root: prefix', () => {
671
+ const result = parsedExportValue('root:src/component.tsx');
672
+ expect(result.name).toBe('src/component');
673
+ expect(result.file).toBe('./src/component.tsx');
674
+ });
722
675
 
723
- // Assert
724
- expect(result.links).toHaveProperty(testModuleName);
725
- expect(result.imports).toEqual({});
726
- expect(result.exports).toHaveProperty('src/entry.client');
727
- expect(result.exports).toHaveProperty('src/entry.server');
676
+ it('should handle nested paths in pkg: prefix', () => {
677
+ const result = parsedExportValue('pkg:@scope/package/subpath');
678
+ expect(result.name).toBe('@scope/package/subpath');
679
+ expect(result.pkg).toBe(true);
680
+ expect(result.file).toBe('@scope/package/subpath');
728
681
  });
682
+ });
729
683
 
730
- it('should handle special characters in module names and paths', () => {
731
- // Arrange
732
- const specialModuleName = 'test-module_with.special@chars';
684
+ describe('Integration Tests', () => {
685
+ it('should work end-to-end with complex configuration', () => {
733
686
  const config: ModuleConfig = {
734
687
  links: {
735
- 'special-lib@1.0.0': '../special-lib/dist'
688
+ shared: '/shared/modules'
736
689
  },
737
- exports: {
738
- 'special_export-name': './src/special.ts'
739
- }
690
+ imports: {
691
+ react: 'react',
692
+ vue: {
693
+ client: 'vue',
694
+ server: 'vue/server'
695
+ }
696
+ },
697
+ scopes: {
698
+ utils: {
699
+ lodash: 'lodash'
700
+ }
701
+ },
702
+ exports: [
703
+ './src/component',
704
+ {
705
+ './src/utils': {
706
+ client: './src/utils.client',
707
+ server: './src/utils.server'
708
+ }
709
+ }
710
+ ]
740
711
  };
741
-
742
- // Act
743
712
  const result = parseModuleConfig(
744
- specialModuleName,
745
- testRoot,
713
+ 'test-module',
714
+ '/test/root',
746
715
  config
747
716
  );
748
-
749
- // Assert
750
- expect(result.name).toBe(specialModuleName);
751
- expect(result.links).toHaveProperty('special-lib@1.0.0');
752
- expect(result.exports).toHaveProperty('special_export-name');
717
+ expect(result.name).toBe('test-module');
718
+ expect(result.links.shared).toBeDefined();
719
+ expect(result.environments.client.imports.react).toBe('react');
720
+ expect(result.environments.client.imports.vue).toBe('vue');
721
+ expect(result.environments.server.imports.vue).toBe('vue/server');
753
722
  });
754
- });
755
723
 
756
- describe('type safety', () => {
757
- it('should maintain type safety for ParsedModuleConfig', () => {
758
- // Arrange
724
+ it('should correctly filter environment-specific exports', () => {
759
725
  const config: ModuleConfig = {
760
- links: { test: './test' },
761
- imports: { axios: 'test/axios' },
762
- exports: ['npm:lodash']
726
+ exports: [
727
+ {
728
+ './src/component': {
729
+ client: './src/component.client',
730
+ server: './src/component.server'
731
+ }
732
+ }
733
+ ]
763
734
  };
764
-
765
- // Act
766
- const result: ParsedModuleConfig = parseModuleConfig(
767
- testModuleName,
768
- testRoot,
735
+ const clientResult = parseModuleConfig(
736
+ 'test-module',
737
+ '/test/root',
738
+ config
739
+ );
740
+ const serverResult = parseModuleConfig(
741
+ 'test-module',
742
+ '/test/root',
769
743
  config
770
744
  );
771
745
 
772
- // Assert
773
- expect(typeof result.name).toBe('string');
774
- expect(typeof result.root).toBe('string');
775
- expect(typeof result.links).toBe('object');
776
- expect(typeof result.imports).toBe('object');
777
- expect(typeof result.exports).toBe('object');
746
+ expect(
747
+ clientResult.environments.client.exports['./src/component'].file
748
+ ).toBe('./src/component.client');
749
+ expect(
750
+ serverResult.environments.server.exports['./src/component'].file
751
+ ).toBe('./src/component.server');
752
+ });
753
+
754
+ it('should maintain cross-environment consistency', () => {
755
+ const result = parseModuleConfig('test-module', '/test/root');
756
+ expect(
757
+ result.environments.client.exports['src/entry.client'].file
758
+ ).toBe('./src/entry.client');
759
+ expect(
760
+ result.environments.server.exports['src/entry.server'].file
761
+ ).toBe('./src/entry.server');
778
762
  });
779
763
 
780
- it('should maintain type safety for ParsedModuleConfigExport', () => {
781
- // Arrange
764
+ it('should work with path resolution across different environments', () => {
782
765
  const config: ModuleConfig = {
783
- exports: ['npm:axios']
766
+ links: {
767
+ relative: 'relative/path'
768
+ }
784
769
  };
770
+ const result = parseModuleConfig(
771
+ 'test-module',
772
+ '/test/root',
773
+ config
774
+ );
775
+ expect(result.links.relative.root).toBe('relative/path');
776
+ });
777
+ });
785
778
 
786
- // Act
787
- const result = parseModuleConfig(testModuleName, testRoot, config);
779
+ describe('Error Handling', () => {
780
+ it('should handle invalid config types gracefully in resolveExportFile', () => {
781
+ const result = resolveExportFile({} as any, 'client', 'component');
782
+ expect(result).toBe('component');
783
+ });
784
+
785
+ it('should handle malformed pkg: values in parsedExportValue', () => {
786
+ const result = parsedExportValue('pkg:');
787
+ expect(result.name).toBe('');
788
+ expect(result.pkg).toBe(true);
789
+ });
788
790
 
789
- // Assert
790
- const exportConfig: ParsedModuleConfigExport = result.exports.axios;
791
- expect(typeof exportConfig.name).toBe('string');
792
- expect(typeof exportConfig.rewrite).toBe('boolean');
793
- expect(typeof exportConfig.entryPoints).toBe('object');
794
- expect(typeof exportConfig.entryPoints.client).toBe('string');
795
- expect(typeof exportConfig.entryPoints.server).toBe('string');
791
+ it('should handle malformed root: values in parsedExportValue', () => {
792
+ const result = parsedExportValue('root:');
793
+ expect(result.name).toBe('');
794
+ expect(result.pkg).toBe(false);
796
795
  });
797
796
  });
798
797
  });