@endo/compartment-mapper 2.0.0 → 2.1.0

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 (43) hide show
  1. package/package.json +23 -9
  2. package/src/archive-lite.js +3 -3
  3. package/src/capture-lite.d.ts.map +1 -1
  4. package/src/capture-lite.js +29 -3
  5. package/src/compartment-map.d.ts.map +1 -1
  6. package/src/compartment-map.js +3 -2
  7. package/src/digest.js +1 -1
  8. package/src/generic-graph.d.ts +7 -25
  9. package/src/generic-graph.d.ts.map +1 -1
  10. package/src/generic-graph.js +75 -105
  11. package/src/import-hook.d.ts.map +1 -1
  12. package/src/import-hook.js +4 -3
  13. package/src/infer-exports.d.ts +4 -2
  14. package/src/infer-exports.d.ts.map +1 -1
  15. package/src/infer-exports.js +158 -19
  16. package/src/link.d.ts.map +1 -1
  17. package/src/link.js +61 -3
  18. package/src/node-modules.d.ts.map +1 -1
  19. package/src/node-modules.js +33 -39
  20. package/src/pattern-replacement.d.ts +6 -0
  21. package/src/pattern-replacement.d.ts.map +1 -0
  22. package/src/pattern-replacement.js +198 -0
  23. package/src/types/compartment-map-schema.d.ts +8 -1
  24. package/src/types/compartment-map-schema.d.ts.map +1 -1
  25. package/src/types/compartment-map-schema.ts +9 -0
  26. package/src/types/external.d.ts +79 -55
  27. package/src/types/external.d.ts.map +1 -1
  28. package/src/types/external.ts +104 -62
  29. package/src/types/generic-graph.d.ts +8 -2
  30. package/src/types/generic-graph.d.ts.map +1 -1
  31. package/src/types/generic-graph.ts +7 -2
  32. package/src/types/internal.d.ts +8 -8
  33. package/src/types/internal.d.ts.map +1 -1
  34. package/src/types/internal.ts +9 -8
  35. package/src/types/node-modules.d.ts +45 -8
  36. package/src/types/node-modules.d.ts.map +1 -1
  37. package/src/types/node-modules.ts +56 -15
  38. package/src/types/pattern-replacement.d.ts +62 -0
  39. package/src/types/pattern-replacement.d.ts.map +1 -0
  40. package/src/types/pattern-replacement.ts +70 -0
  41. package/src/types/powers.d.ts +11 -9
  42. package/src/types/powers.d.ts.map +1 -1
  43. package/src/types/powers.ts +11 -10
@@ -10,12 +10,14 @@
10
10
 
11
11
  /**
12
12
  * @import {LanguageForExtension, PackageDescriptor} from './types.js'
13
- * @import {Node} from './types/node-modules.js'
13
+ * @import {LogFn} from './types/external.js'
14
+ * @import {Exports, Imports, Node} from './types/node-modules.js'
15
+ * @import {PatternDescriptor} from './types/pattern-replacement.js'
14
16
  */
15
17
 
16
- import { join, relativize } from './node-module-specifier.js';
18
+ import { relativize } from './node-module-specifier.js';
17
19
 
18
- const { entries, fromEntries, assign } = Object;
20
+ const { entries, fromEntries } = Object;
19
21
  const { isArray } = Array;
20
22
 
21
23
  /**
@@ -60,15 +62,20 @@ function* interpretBrowserField(name, browser, main = 'index.js') {
60
62
 
61
63
  /**
62
64
  * @param {string} name - the name of the referrer package.
63
- * @param {object} exports - the `exports` field from a package.json.
65
+ * @param {Exports} exports - the `exports` field from a package.json.
64
66
  * @param {Set<string>} conditions - build conditions about the target environment
65
67
  * for selecting relevant exports, e.g., "browser" or "node".
66
68
  * @param {LanguageForExtension} types - an object to populate
67
69
  * with any recognized module's type, if implied by a tag.
68
- * @yields {[string, string]}
69
- * @returns {Generator<[string, string]>}
70
+ * @yields {[string, string | null]}
71
+ * @returns {Generator<[string, string | null]>}
70
72
  */
71
73
  function* interpretExports(name, exports, conditions, types) {
74
+ // Null targets are exclusions (Node.js semantics).
75
+ if (exports === null) {
76
+ yield [name, null];
77
+ return;
78
+ }
72
79
  if (isArray(exports)) {
73
80
  for (const section of exports) {
74
81
  const results = [...interpretExports(name, section, conditions, types)];
@@ -94,11 +101,7 @@ function* interpretExports(name, exports, conditions, types) {
94
101
  // eslint-disable-next-line no-continue
95
102
  continue; // or no-op
96
103
  } else if (key.startsWith('./') || key === '.') {
97
- if (name === '.') {
98
- yield* interpretExports(key, value, conditions, types);
99
- } else {
100
- yield* interpretExports(join(name, key), value, conditions, types);
101
- }
104
+ yield* interpretExports(key, value, conditions, types);
102
105
  } else if (conditions.has(key)) {
103
106
  if (types && key === 'import' && typeof value === 'string') {
104
107
  // In this one case, the key "import" has carried a hint that the
@@ -115,6 +118,54 @@ function* interpretExports(name, exports, conditions, types) {
115
118
  }
116
119
  }
117
120
 
121
+ /**
122
+ * Interprets the `imports` field from a package.json file.
123
+ * The imports field provides self-referencing subpath patterns that
124
+ * can be used to create private internal mappings.
125
+ *
126
+ * @param {Imports} imports - the `imports` field from a package.json.
127
+ * @param {Set<string>} conditions - build conditions about the target environment
128
+ * @param {LogFn} log
129
+ * @yields {[string, string | null]}
130
+ * @returns {Generator<[string, string | null]>}
131
+ */
132
+ function* interpretImports(imports, conditions, log) {
133
+ if (Object(imports) !== imports || Array.isArray(imports)) {
134
+ throw Error(
135
+ `Cannot interpret package.json imports property, must be object, got ${imports}`,
136
+ );
137
+ }
138
+ for (const [key, value] of entries(imports)) {
139
+ // imports keys must start with '#'
140
+ if (!key.startsWith('#')) {
141
+ log(`Ignoring invalid imports key "${key}": must start with "#"`);
142
+ // eslint-disable-next-line no-continue
143
+ continue;
144
+ }
145
+ if (value === null) {
146
+ // Null targets are exclusions (Node.js semantics).
147
+ yield [key, null];
148
+ } else if (typeof value === 'string') {
149
+ yield [key, relativize(value)];
150
+ } else if (Object(value) === value && !isArray(value)) {
151
+ // Handle conditional imports
152
+ for (const [condition, target] of entries(value)) {
153
+ if (conditions.has(condition)) {
154
+ if (target === null) {
155
+ yield [key, null];
156
+ } else if (typeof target === 'string') {
157
+ yield [key, relativize(target)];
158
+ }
159
+ // Take only the first matching condition
160
+ break;
161
+ }
162
+ }
163
+ } else {
164
+ log(`Ignoring unsupported imports value for "${key}": ${typeof value}`);
165
+ }
166
+ }
167
+ }
168
+
118
169
  /**
119
170
  * Given an unpacked `package.json`, generate a series of `[name, target]`
120
171
  * pairs to represent what this package exports. `name` is what the
@@ -130,7 +181,7 @@ function* interpretExports(name, exports, conditions, types) {
130
181
  * for selecting relevant exports, e.g., "browser" or "node".
131
182
  * @param {LanguageForExtension} types - an object to populate
132
183
  * with any recognized module's type, if implied by a tag.
133
- * @yields {[string, string]}
184
+ * @yields {[string, string | null]}
134
185
  */
135
186
  export const inferExportsEntries = function* inferExportsEntries(
136
187
  { main, module, exports },
@@ -177,27 +228,115 @@ export const inferExports = (descriptor, conditions, types) =>
177
228
  fromEntries(inferExportsEntries(descriptor, conditions, types));
178
229
 
179
230
  /**
231
+ * Determines if a key or value contains a wildcard pattern.
232
+ *
233
+ * @param {string} key
234
+ * @param {string | null} value
235
+ * @returns {boolean}
236
+ */
237
+ const hasWildcard = (key, value) =>
238
+ key.includes('*') || (value?.includes('*') ?? false);
239
+
240
+ /**
241
+ * Returns the number of `*` characters in a string.
242
+ *
243
+ * @param {string} str
244
+ * @returns {number}
245
+ */
246
+ const countWildcards = str => (str.match(/\*/g) || []).length;
247
+
248
+ /**
249
+ * Validates a wildcard pattern entry and logs warnings for invalid patterns.
250
+ * Returns true if the pattern is valid and should be used.
251
+ *
252
+ * @param {string} key
253
+ * @param {string} value
254
+ * @param {LogFn} log
255
+ * @returns {boolean}
256
+ */
257
+ const validateWildcardPattern = (key, value, log) => {
258
+ const keyCount = countWildcards(key);
259
+ const valueCount = countWildcards(value);
260
+ if (keyCount > 1 || valueCount > 1) {
261
+ log(`Ignoring pattern with multiple wildcards "${key}": "${value}"`);
262
+ return false;
263
+ }
264
+ if (keyCount !== valueCount) {
265
+ log(
266
+ `Ignoring pattern with mismatched wildcard count "${key}" (${keyCount}) vs "${value}" (${valueCount})`,
267
+ );
268
+ return false;
269
+ }
270
+ return true;
271
+ };
272
+
273
+ /**
274
+ * Infers exports, internal aliases, and wildcard patterns from a package descriptor.
275
+ * Extracts wildcard patterns from the `exports` and `imports` fields.
180
276
  *
181
277
  * @param {PackageDescriptor} descriptor
182
278
  * @param {Node['externalAliases']} externalAliases
183
279
  * @param {Node['internalAliases']} internalAliases
280
+ * @param {PatternDescriptor[]} patterns - array to populate with wildcard patterns
184
281
  * @param {Set<string>} conditions
185
282
  * @param {Record<string, string>} types
283
+ * @param {LogFn} log
186
284
  */
187
- export const inferExportsAndAliases = (
285
+ export const inferExportsAliasesAndPatterns = (
188
286
  descriptor,
189
287
  externalAliases,
190
288
  internalAliases,
289
+ patterns,
191
290
  conditions,
192
291
  types,
292
+ log,
193
293
  ) => {
194
- const { name, type, main, module, exports, browser } = descriptor;
294
+ const { name, type, main, module, exports, imports, browser } = descriptor;
295
+
296
+ // Process exports field - separate wildcards from concrete exports.
297
+ for (const [key, value] of inferExportsEntries(
298
+ descriptor,
299
+ conditions,
300
+ types,
301
+ )) {
302
+ if (value === null) {
303
+ // Null targets are exclusions.
304
+ // Only wildcard null targets need to be stored as patterns;
305
+ // concrete null targets are excluded by omission from aliases.
306
+ if (key.includes('*')) {
307
+ patterns.push({ from: key, to: null });
308
+ }
309
+ // eslint-disable-next-line no-continue
310
+ continue;
311
+ }
312
+ if (hasWildcard(key, value)) {
313
+ if (validateWildcardPattern(key, value, log)) {
314
+ patterns.push({ from: key, to: value });
315
+ }
316
+ } else {
317
+ externalAliases[key] = value;
318
+ }
319
+ }
195
320
 
196
- // collect externalAliases from exports and main/module
197
- assign(
198
- externalAliases,
199
- fromEntries(inferExportsEntries(descriptor, conditions, types)),
200
- );
321
+ // Process imports field (package self-referencing).
322
+ if (imports !== undefined) {
323
+ for (const [key, value] of interpretImports(imports, conditions, log)) {
324
+ if (value === null) {
325
+ if (key.includes('*')) {
326
+ patterns.push({ from: key, to: null });
327
+ }
328
+ // eslint-disable-next-line no-continue
329
+ continue;
330
+ }
331
+ if (hasWildcard(key, value)) {
332
+ if (validateWildcardPattern(key, value, log)) {
333
+ patterns.push({ from: key, to: value });
334
+ }
335
+ } else {
336
+ internalAliases[key] = value;
337
+ }
338
+ }
339
+ }
201
340
 
202
341
  // expose default module as package root
203
342
  // may be overwritten by browser field
package/src/link.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["link.js"],"names":[],"mappings":"AA+PO,sEAJI,+BAA+B,GAAC,4BAA4B,WAC5D,WAAW,GACT,UAAU,CAgLtB;AAOM,yCAJI,+BAA+B,WAC/B,WAAW,eAIqB;qDArZjC,YAAY;kDAAZ,YAAY;iCAAZ,YAAY;gCAAZ,YAAY"}
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["link.js"],"names":[],"mappings":"AAyTO,sEAJI,+BAA+B,GAAC,4BAA4B,WAC5D,WAAW,GACT,UAAU,CAgLtB;AAOM,yCAJI,+BAA+B,WAC/B,WAAW,eAIqB;qDAhdjC,YAAY;kDAAZ,YAAY;iCAAZ,YAAY;gCAAZ,YAAY"}
package/src/link.js CHANGED
@@ -29,8 +29,8 @@
29
29
  * FileCompartmentMapDescriptor,
30
30
  * FileCompartmentDescriptor,
31
31
  * FileModuleConfiguration,
32
- * MakeModuleMapHookOptions,
33
32
  * } from './types.js'
33
+ * @import {SubpathReplacer} from './types/pattern-replacement.js'
34
34
  */
35
35
 
36
36
  import { makeMapParsers } from './map-parser.js';
@@ -45,6 +45,7 @@ import {
45
45
  isCompartmentModuleConfiguration,
46
46
  isExitModuleConfiguration,
47
47
  } from './guards.js';
48
+ import { makeMultiSubpathReplacer } from './pattern-replacement.js';
48
49
 
49
50
  const { assign, create, entries, freeze } = Object;
50
51
  const { hasOwnProperty } = Object.prototype;
@@ -101,7 +102,7 @@ const trimModuleSpecifierPrefix = (moduleSpecifier, prefix) => {
101
102
  *
102
103
  * @param {FileCompartmentDescriptor|PackageCompartmentDescriptor} compartmentDescriptor
103
104
  * @param {Record<string, Compartment>} compartments
104
- * @param {string} compartmentName
105
+ * @param {FileUrlString} compartmentName
105
106
  * @param {Record<string, FileModuleConfiguration|CompartmentModuleConfiguration>} moduleDescriptors
106
107
  * @param {Record<string, ScopeDescriptor<FileUrlString>>} scopeDescriptors
107
108
  * @returns {ModuleMapHook | undefined}
@@ -113,6 +114,14 @@ const makeModuleMapHook = (
113
114
  moduleDescriptors,
114
115
  scopeDescriptors,
115
116
  ) => {
117
+ // Build pattern matcher once per compartment if patterns exist.
118
+ const { patterns } = /** @type {Partial<PackageCompartmentDescriptor>} */ (
119
+ compartmentDescriptor
120
+ );
121
+ /** @type {SubpathReplacer | null} */
122
+ const matchPattern =
123
+ patterns && patterns.length > 0 ? makeMultiSubpathReplacer(patterns) : null;
124
+
116
125
  /**
117
126
  * @type {ModuleMapHook}
118
127
  */
@@ -162,6 +171,55 @@ const makeModuleMapHook = (
162
171
  }
163
172
  }
164
173
 
174
+ // Check patterns for wildcard matches (before scopes).
175
+ // Patterns may resolve within the same compartment (internal patterns)
176
+ // or to a foreign compartment (dependency export patterns).
177
+ if (matchPattern) {
178
+ const match = matchPattern(moduleSpecifier);
179
+ if (match !== null) {
180
+ const { result: resolvedPath, compartment: foreignCompartmentName } =
181
+ match;
182
+
183
+ // Null result means the specifier is explicitly excluded.
184
+ if (resolvedPath === null) {
185
+ throw Error(
186
+ `Cannot find module ${q(moduleSpecifier)} — excluded by null target pattern in ${q(compartmentName)}`,
187
+ );
188
+ }
189
+ const targetCompartmentName =
190
+ /** @type {FileUrlString} */
191
+ (foreignCompartmentName || compartmentName);
192
+
193
+ // Write back to moduleDescriptors for caching, archival, and
194
+ // policy enforcement. The write-back must precede the policy
195
+ // check because enforcePolicyByModule verifies the specifier
196
+ // exists in compartmentDescriptor.modules (the same object).
197
+ moduleDescriptors[moduleSpecifier] = {
198
+ retained: true,
199
+ compartment: targetCompartmentName,
200
+ module: resolvedPath,
201
+ __createdBy: 'link-pattern',
202
+ };
203
+
204
+ // Policy enforcement for pattern-matched modules
205
+ enforcePolicyByModule(moduleSpecifier, compartmentDescriptor, {
206
+ exit: false,
207
+ errorHint: `Pattern matched in compartment ${q(compartmentName)}: module specifier ${q(moduleSpecifier)} mapped to ${q(resolvedPath)}`,
208
+ });
209
+
210
+ const targetCompartment = compartments[targetCompartmentName];
211
+ if (targetCompartment === undefined) {
212
+ throw Error(
213
+ `Cannot import module specifier ${q(moduleSpecifier)} from missing compartment ${q(targetCompartmentName)}`,
214
+ );
215
+ }
216
+ return {
217
+ compartment: targetCompartment,
218
+ namespace: resolvedPath,
219
+ };
220
+ }
221
+ }
222
+
165
223
  // Search for a scope that shares a prefix with the requested module
166
224
  // specifier.
167
225
  // This might be better with a trie, but only a benchmark on real-world
@@ -299,7 +357,7 @@ export const link = (
299
357
  });
300
358
 
301
359
  const compartmentDescriptorEntries =
302
- /** @type {[string, PackageCompartmentDescriptor|FileCompartmentDescriptor][]} */ (
360
+ /** @type {[FileUrlString, PackageCompartmentDescriptor|FileCompartmentDescriptor][]} */ (
303
361
  entries(compartmentDescriptors)
304
362
  );
305
363
  for (const [
@@ -1 +1 @@
1
- {"version":3,"file":"node-modules.d.ts","sourceRoot":"","sources":["node-modules.js"],"names":[],"mappings":"AA4LO,mCAHI,MAAM,GACJ,MAAM,CAYlB;AAqlCM,0DARI,MAAM,GAAG,WAAW,aAAa,CAAC,GAAG,gBAAgB,aAAa,CAAC,wBACnE,aAAa,oBACb,GAAG,CAAC,MAAM,CAAC,qBACX,iBAAiB,wBACjB,MAAM,YACN,mCAAmC,GACjC,OAAO,CAAC,+BAA+B,CAAC,CA0HpD;AAaM,2CALI,MAAM,GAAG,WAAW,aAAa,CAAC,GAAG,gBAAgB,aAAa,CAAC,kBACnE,MAAM,2HACN,qBAAqB,GACnB,OAAO,CAAC,+BAA+B,CAAC,CA6CpD;AAhLM,yDARI,MAAM,GAAG,WAAW,aAAa,CAAC,GAAG,gBAAgB,aAAa,CAAC,wBACnE,aAAa,oBACb,GAAG,CAAC,MAAM,CAAC,qBACX,iBAAiB,wBACjB,MAAM,YACN,mCAAmC,GACjC,OAAO,CAAC,+BAA+B,CAAC,CA0HpD;4BA51CS,YAAY;mCAAZ,YAAY;gCAAZ,YAAY;qCAAZ,YAAY;uCAAZ,YAAY;yDAAZ,YAAY;qDAAZ,YAAY;2CAAZ,YAAY"}
1
+ {"version":3,"file":"node-modules.d.ts","sourceRoot":"","sources":["node-modules.js"],"names":[],"mappings":"AA4LO,mCAHI,MAAM,GACJ,MAAM,CAYlB;AA+kCM,0DARI,MAAM,GAAG,WAAW,aAAa,CAAC,GAAG,gBAAgB,aAAa,CAAC,wBACnE,aAAa,oBACb,GAAG,CAAC,MAAM,CAAC,qBACX,iBAAiB,wBACjB,MAAM,YACN,mCAAmC,GACjC,OAAO,CAAC,+BAA+B,CAAC,CA0HpD;AAaM,2CALI,MAAM,GAAG,WAAW,aAAa,CAAC,GAAG,gBAAgB,aAAa,CAAC,kBACnE,MAAM,2HACN,qBAAqB,GACnB,OAAO,CAAC,+BAA+B,CAAC,CA6CpD;AAhLM,yDARI,MAAM,GAAG,WAAW,aAAa,CAAC,GAAG,gBAAgB,aAAa,CAAC,wBACnE,aAAa,oBACb,GAAG,CAAC,MAAM,CAAC,qBACX,iBAAiB,wBACjB,MAAM,YACN,mCAAmC,GACjC,OAAO,CAAC,+BAA+B,CAAC,CA0HpD;4BAt1CS,YAAY;mCAAZ,YAAY;gCAAZ,YAAY;qCAAZ,YAAY;uCAAZ,YAAY;yDAAZ,YAAY;qDAAZ,YAAY;2CAAZ,YAAY"}
@@ -14,7 +14,7 @@
14
14
 
15
15
  /* eslint no-shadow: 0 */
16
16
 
17
- import { inferExportsAndAliases } from './infer-exports.js';
17
+ import { inferExportsAliasesAndPatterns } from './infer-exports.js';
18
18
  import { parseLocatedJson } from './json.js';
19
19
  import { join } from './node-module-specifier.js';
20
20
  import {
@@ -386,37 +386,6 @@ const inferParsers = (descriptor, location, languageOptions) => {
386
386
  return { ...commonjsLanguageForExtension, ...packageLanguageForExtension };
387
387
  };
388
388
 
389
- /**
390
- * This returns the "weight" of a package name, which is used when determining
391
- * the shortest path.
392
- *
393
- * It is an analogue of the `pathCompare` function.
394
- *
395
- * The weight is calculated as follows:
396
- *
397
- * 1. The {@link String.length length} of the package name contributes a fixed
398
- * value of `0x10000` per character. This is because the `pathCompare`
399
- * algorithm first compares strings by length and only evaluates code unit
400
- * values if the lengths of two strings are equal. `0x10000` is one (1)
401
- * greater than the maximum value that {@link String.charCodeAt charCodeAt}
402
- * can return (`0xFFFF`), which guarantees longer strings will have higher
403
- * weights.
404
- * 2. Each character in the package name contributes its UTF-16 code unit value
405
- * (`0x0` thru `0xFFFF`) to the total. This is the same operation used when
406
- * comparing two strings using comparison operators.
407
- * 3. The total weight is the sum of 1. and 2.
408
- *
409
- * @param {string} packageName - Name of package to calculate weight for.
410
- * @returns {number} Numeric weight
411
- */
412
- const calculatePackageWeight = packageName => {
413
- let totalCodeValue = packageName.length * 65536; // each character contributes 65536
414
- for (let i = 0; i < packageName.length; i += 1) {
415
- totalCodeValue += packageName.charCodeAt(i);
416
- }
417
- return totalCodeValue;
418
- };
419
-
420
389
  /**
421
390
  * `graphPackage` and {@link gatherDependency} are mutually recursive functions that
422
391
  * gather the metadata for a package and its transitive dependencies.
@@ -577,13 +546,17 @@ const graphPackage = async (
577
546
  const externalAliases = {};
578
547
  /** @type {Node['internalAliases']} */
579
548
  const internalAliases = {};
549
+ /** @type {Node['patterns']} */
550
+ const patterns = [];
580
551
 
581
- inferExportsAndAliases(
552
+ inferExportsAliasesAndPatterns(
582
553
  packageDescriptor,
583
554
  externalAliases,
584
555
  internalAliases,
556
+ patterns,
585
557
  conditions,
586
558
  types,
559
+ log,
587
560
  );
588
561
 
589
562
  const parsers = inferParsers(
@@ -602,6 +575,7 @@ const graphPackage = async (
602
575
  explicitExports: exportsDescriptor !== undefined,
603
576
  externalAliases,
604
577
  internalAliases,
578
+ patterns,
605
579
  dependencyLocations,
606
580
  types,
607
581
  parsers,
@@ -703,11 +677,7 @@ const gatherDependency = async (
703
677
 
704
678
  dependencyLocations[name] = dependency.packageLocation;
705
679
 
706
- logicalPathGraph.addEdge(
707
- packageLocation,
708
- dependency.packageLocation,
709
- calculatePackageWeight(name),
710
- );
680
+ logicalPathGraph.addEdge(packageLocation, dependency.packageLocation);
711
681
 
712
682
  await graphPackage(
713
683
  name,
@@ -955,6 +925,7 @@ const translateGraph = (
955
925
  label,
956
926
  sourceDirname,
957
927
  internalAliases,
928
+ patterns,
958
929
  parsers,
959
930
  types,
960
931
  packageDescriptor,
@@ -983,7 +954,11 @@ const translateGraph = (
983
954
  * @param {PackageCompartmentDescriptorName} packageLocation
984
955
  */
985
956
  const digestExternalAliases = (dependencyName, packageLocation) => {
986
- const { externalAliases, explicitExports } = graph[packageLocation];
957
+ const {
958
+ externalAliases,
959
+ explicitExports,
960
+ patterns: dependencyPatterns,
961
+ } = graph[packageLocation];
987
962
  for (const exportPath of keys(externalAliases).sort()) {
988
963
  const targetPath = externalAliases[exportPath];
989
964
  // dependency name may be different from package's name,
@@ -996,6 +971,24 @@ const translateGraph = (
996
971
  module: targetPath,
997
972
  };
998
973
  }
974
+ // Propagate export patterns from dependencies.
975
+ // Each dependency pattern like "./features/*.js" -> "./src/features/*.js"
976
+ // becomes "dep/features/*.js" -> "./src/features/*.js" on the dependee,
977
+ // resolving within the dependency's compartment.
978
+ if (dependencyPatterns) {
979
+ for (const { from, to } of dependencyPatterns) {
980
+ // Only propagate export patterns (starting with "./"), not
981
+ // import patterns (starting with "#") which are internal.
982
+ if (from.startsWith('./') || from === '.') {
983
+ const externalFrom = join(dependencyName, from);
984
+ patterns.push({
985
+ from: externalFrom,
986
+ to,
987
+ compartment: packageLocation,
988
+ });
989
+ }
990
+ }
991
+ }
999
992
  // if the exports field is not present, then all modules must be accessible
1000
993
  if (!explicitExports) {
1001
994
  scopes[dependencyName] = {
@@ -1032,6 +1025,7 @@ const translateGraph = (
1032
1025
  sourceDirname,
1033
1026
  modules: moduleDescriptors,
1034
1027
  scopes,
1028
+ ...(patterns.length > 0 ? { patterns } : {}),
1035
1029
  parsers,
1036
1030
  types,
1037
1031
  policy: /** @type {SomePackagePolicy} */ (packagePolicy),
@@ -0,0 +1,6 @@
1
+ export function assertMatchingWildcardCount(pattern: string, replacement: string): void;
2
+ export function makeMultiSubpathReplacer(mapping: PatternDescriptor[] | SubpathMapping): SubpathReplacer;
3
+ import type { PatternDescriptor } from './types/pattern-replacement.js';
4
+ import type { SubpathMapping } from './types/pattern-replacement.js';
5
+ import type { SubpathReplacer } from './types/pattern-replacement.js';
6
+ //# sourceMappingURL=pattern-replacement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-replacement.d.ts","sourceRoot":"","sources":["pattern-replacement.js"],"names":[],"mappings":"AA6BO,qDAJI,MAAM,eACN,MAAM,QAWhB;AAwFM,kDAHI,iBAAiB,EAAE,GAAG,cAAc,GAClC,eAAe,CA0E3B;uCAtLS,gCAAgC;oCAAhC,gCAAgC;qCAAhC,gCAAgC"}