-
Notifications
You must be signed in to change notification settings - Fork 79
feat(compartment-mapper): Import and export subpath patterns #3048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
boneskull
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No real concerns here. Not even nits, really
| * Package imports field for self-referencing subpath patterns. | ||
| * Keys must start with '#'. | ||
| */ | ||
| imports?: unknown; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(suggestion)
Steal this code for the type defs of exports and imports: https://github.com/sindresorhus/type-fest/blob/850b33c4dd292e0ff8cff039ee167d69be324fce/source/package-json.d.ts#L227-L248
| export type SubpathMapping = | ||
| | Array<[pattern: string, replacement: string]> | ||
| | Record<string, string>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for named tuple elements
| * The imports field provides self-referencing subpath patterns that | ||
| * can be used to create private internal mappings. | ||
| * | ||
| * @param {string} _name - the name of the package (unused, but kept for consistency) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consistency with what?
| } | ||
| for (const [key, value] of entries(imports)) { | ||
| // imports keys must start with '#' | ||
| if (!key.startsWith('#')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(suggestion) warn
| * @param {string} value | ||
| * @returns {boolean} | ||
| */ | ||
| const hasWildcard = (key, value) => key.includes('*') || value.includes('*'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it also important to know which of these two values contains the wildcard(s)?
| * @param {string} segment | ||
| * @returns {boolean} | ||
| */ | ||
| const hasWildcard = segment => segment.includes(WILDCARD); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(could) Refactor and share with infer-exports.js
| return patternSegment === specifierSegment ? '' : null; | ||
| } | ||
|
|
||
| const wildcardIndex = patternSegment.indexOf(WILDCARD); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(could) if we're able to assert here, I'd probably want to assert that wildcardIndex is a non-negative integer
| `Globstar (**) patterns are not supported in pattern: "${pattern}"`, | ||
| ); | ||
| } | ||
| if (replacement.includes(GLOBSTAR)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(could) hasGlobstar()
| t.is(node.value, null); | ||
| t.deepEqual(Object.keys(node.children), []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(could) refactor to t.like(node, {value: null, children: []}) assuming it works the way I think it should
| }); | ||
|
|
||
| test('assertMatchingWildcardCount - throws for mismatched counts', t => { | ||
| const error = t.throws(() => assertMatchingWildcardCount('./*/a/*', './*')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(complaining) t.throws() is weaksauce.
expect(
() => assertMatchingWildcardCount('./*/a/*', './*'),
'to throw',
{
message: expect.it(
'to match', /wildcard count mismatch/i,
'and', 'to match', /2/,
'and', 'to match', /1/
)
}
);(imperative) feel the power of BUPKIS
Closes: #2897
Description
Adds support for wildcard pattern replacement in
package.jsonexportsandimportsfields to the compartment-mapper. This enables patterns like:{ "exports": { "./x/*/y/*/z": "./src/x/*/y/*/z.js" }, "imports": { "#internal/*": "./lib/*.js" } }The implementation uses a prefix-tree-based approach for efficient pattern matching, correctly implementing Node.js semantics where
*matches any string within a single path segment (does not match across/).Key changes:
New files:
src/pattern-replacement.js- Prefix-tree-based pattern matcher withPathPrefixTreeandPathPrefixTreeNodeclassessrc/types/pattern-replacement.ts- Type definitions for pattern matchingtest/pattern-replacement.test.js- 21 unit tests for pattern matchingtest/export-patterns.test.js- 11 integration teststest/fixtures-export-patterns/- Test fixtures with wildcard patternsModified files:
src/infer-exports.js- AddedinferExportsAliasesAndPatterns()to extract wildcard patterns fromexportsandimportsfieldssrc/node-modules.js- Propagates patterns through the compartment graphsrc/link.js- Runtime pattern resolution inmakeModuleMapHook()src/types/compartment-map-schema.ts- Addedpatternsfield toPackageCompartmentDescriptorsrc/types/node-modules.ts- AddedpatternstoNodetypeSecurity Considerations
Patterns are resolved only within the same compartment, consistent with Node.js behavior. Pattern-matched module imports go through the same policy enforcement (
enforcePolicyByModule) as other module resolutions. The prefix-tree approach avoids regex-based matching, eliminating potential ReDoS concerns.Scaling Considerations
The prefix-tree data structure provides O(n) lookup where n is the number of path segments, which is efficient for typical module specifiers. Pattern matching is performed lazily at import time and results are cached via write-back to
moduleDescriptors.Documentation Considerations
Users can now use wildcard patterns in their
package.jsonexportsandimportsfields when using compartment-mapper. The patterns follow Node.js subpath export semantics:*matches any string within a single path segment*does not match across/boundaries**) is explicitly not supported and throws an errorTesting Considerations
loadLocation,importLocation,mapNodeModules,makeArchive,parseArchive,writeArchive,loadArchive,importArchiveCompatibility Considerations
This is a purely additive feature. Existing packages without wildcard patterns are unaffected. The
patternsfield is only added to compartment descriptors when patterns exist (using conditional spread), so existing snapshot tests pass without modification.Upgrade Considerations
No breaking changes. Packages can start using wildcard patterns in
exports/importsimmediately after upgrading to a version with this feature.