Skip to content

Commit

Permalink
Merge pull request #1652 from embroider-build/feature-reverse-exports
Browse files Browse the repository at this point in the history
create new @embroider/reverse-exports package
  • Loading branch information
mansona authored Nov 20, 2023
2 parents 0ebfb9d + af0a18b commit 9606cac
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
/packages/vite/index.d.ts
/packages/vite/**/*.js
/packages/vite/**/*.d.ts
/packages/reverse-exports/**/*.js
/packages/reverse-exports/**/*.d.ts


# unconventional js
Expand Down
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
/packages/vite/index.mjs
/packages/vite/**/*.js
/packages/vite/**/*.d.ts
/packages/reverse-exports/**/*.js
/packages/reverse-exports/**/*.d.ts

# unconventional js
/blueprints/*/files/
Expand Down
7 changes: 7 additions & 0 deletions packages/reverse-exports/.gitignore
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
4 changes: 4 additions & 0 deletions packages/reverse-exports/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['<rootDir>/tests/**/*.test.js'],
};
19 changes: 19 additions & 0 deletions packages/reverse-exports/package.json
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"
}
}
116 changes: 116 additions & 0 deletions packages/reverse-exports/src/index.ts
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);
}
Loading

0 comments on commit 9606cac

Please sign in to comment.