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