-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1652 from embroider-build/feature-reverse-exports
create new @embroider/reverse-exports package
- Loading branch information
Showing
8 changed files
with
425 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/node_modules | ||
/src/**/*.js | ||
/src/**/*.d.ts | ||
/src/**/*.map | ||
/tests/**/*.js | ||
/tests/**/*.d.ts | ||
/tests/**/*.map |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module.exports = { | ||
testEnvironment: 'node', | ||
testMatch: ['<rootDir>/tests/**/*.test.js'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "@embroider/reverse-exports", | ||
"version": "0.0.0", | ||
"description": "", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": "jest" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"devDependencies": { | ||
"@types/minimatch": "^3.0.4" | ||
}, | ||
"dependencies": { | ||
"minimatch": "^3.0.4", | ||
"resolve.exports": "^2.0.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { posix } from 'path'; | ||
import minimatch from 'minimatch'; | ||
import { exports as resolveExports } from 'resolve.exports'; | ||
|
||
type Exports = string | string[] | { [key: string]: Exports }; | ||
|
||
/** | ||
* An util to find a string value in a nested JSON-like structure. | ||
* | ||
* Receives an object (a netsted JSON-like structure) and a matcher callback | ||
* that is tested against each string value. | ||
* | ||
* When a value is found, returns an object containing a `value` and a `key`. | ||
* The key is one of the parent keys of the found value — the one that starts | ||
* with `.`. | ||
* | ||
* When a value is not found, returns `undefined`. | ||
*/ | ||
export function _findPathRecursively( | ||
exportsObj: Exports, | ||
matcher: (path: string) => boolean, | ||
key = '.' | ||
): { key: string; value: Exports } | undefined { | ||
if (typeof exportsObj === 'string') { | ||
return matcher(exportsObj) ? { key, value: exportsObj } : undefined; | ||
} | ||
|
||
if (Array.isArray(exportsObj)) { | ||
const value = exportsObj.find(path => matcher(path)); | ||
|
||
if (value) { | ||
return { key, value }; | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
|
||
if (typeof exportsObj === 'object') { | ||
let result: { key: string; value: Exports } | undefined = undefined; | ||
|
||
for (const candidateKey in exportsObj) { | ||
if (!exportsObj.hasOwnProperty(candidateKey)) { | ||
return; | ||
} | ||
|
||
const candidate = _findPathRecursively(exportsObj[candidateKey], matcher, key); | ||
|
||
if (candidate) { | ||
result = { | ||
key: candidateKey, | ||
value: candidate.value, | ||
}; | ||
|
||
break; | ||
} | ||
} | ||
|
||
if (result) { | ||
if (result.key.startsWith('./')) { | ||
if (key !== '.') { | ||
throw new Error(`exportsObj contains doubly nested path keys: "${key}" and "${result.key}"`); | ||
} | ||
|
||
return { key: result.key, value: result.value }; | ||
} else { | ||
return { key, value: result.value }; | ||
} | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
|
||
throw new Error(`Unexpected type of obj: ${typeof exportsObj}`); | ||
} | ||
|
||
export default function reversePackageExports( | ||
{ exports: exportsObj, name }: { exports?: Exports; name: string }, | ||
relativePath: string | ||
): string { | ||
if (!exportsObj) { | ||
return posix.join(name, relativePath); | ||
} | ||
|
||
const maybeKeyValuePair = _findPathRecursively(exportsObj, candidate => { | ||
// miminatch does not treat directories as full of content without glob | ||
if (candidate.endsWith('/')) { | ||
candidate += '**'; | ||
} | ||
|
||
return minimatch(relativePath, candidate); | ||
}); | ||
|
||
if (!maybeKeyValuePair) { | ||
throw new Error( | ||
`You tried to reverse exports for the file \`${relativePath}\` in package \`${name}\` but it does not match any of the exports rules defined in package.json. This means it should not be possible to access directly.` | ||
); | ||
} | ||
|
||
const { key, value } = maybeKeyValuePair; | ||
|
||
if (typeof value !== 'string') { | ||
throw new Error('Expected value to be a string'); | ||
} | ||
|
||
const maybeResolvedPaths = resolveExports({ name, exports: { [value]: key } }, relativePath); | ||
|
||
if (!maybeResolvedPaths) { | ||
throw new Error( | ||
`Bug Discovered! \`_findPathRecursively()\` must always return a string value but instead it found a ${typeof value}. Please report this as an issue to https://github.com/embroider-build/embroider/issues/new` | ||
); | ||
} | ||
|
||
const [resolvedPath] = maybeResolvedPaths; | ||
|
||
return resolvedPath.replace(/^./, name); | ||
} |
Oops, something went wrong.