diff --git a/packages/addon-shim/package.json b/packages/addon-shim/package.json index f80863a59..23cab53e8 100644 --- a/packages/addon-shim/package.json +++ b/packages/addon-shim/package.json @@ -19,9 +19,11 @@ "dependencies": { "@embroider/shared-internals": "workspace:^", "broccoli-funnel": "^3.0.8", + "common-ancestor-path": "^1.0.1", "semver": "^7.3.8" }, "devDependencies": { + "@types/common-ancestor-path": "^1.0.2", "@types/semver": "^7.3.6", "broccoli-node-api": "^1.7.0", "typescript": "^5.1.6", diff --git a/packages/addon-shim/src/index.ts b/packages/addon-shim/src/index.ts index 05038a4fe..663285729 100644 --- a/packages/addon-shim/src/index.ts +++ b/packages/addon-shim/src/index.ts @@ -1,13 +1,13 @@ -import { resolve, relative, isAbsolute } from 'path'; -import { readFileSync } from 'fs'; import { - AddonMeta, AddonInstance, - isDeepAddonInstance, + AddonMeta, PackageInfo, + isDeepAddonInstance, } from '@embroider/shared-internals'; import buildFunnel from 'broccoli-funnel'; -import type { Node } from 'broccoli-node-api'; +import commonAncestorPath from 'common-ancestor-path'; +import { readFileSync } from 'fs'; +import { dirname, isAbsolute, join, normalize, relative, resolve } from 'path'; import { satisfies } from 'semver'; export interface ShimOptions { @@ -29,15 +29,46 @@ export function addonV1Shim(directory: string, options: ShimOptions = {}) { let meta = addonMeta(pkg); let disabled = false; - const rootTrees = new WeakMap(); - function rootTree(addonInstance: AddonInstance): Node { - let tree = rootTrees.get(addonInstance); - if (!tree) { - tree = addonInstance.treeGenerator(directory); - rootTrees.set(addonInstance, tree); + function treeFor( + addonInstance: AddonInstance, + resourceMap: Record, + // default expectation is for resourceMap to map from interior to exterior, swap if needed + swapInteriorExterior = false + ) { + const absoluteInteriorPaths = Object[ + swapInteriorExterior ? 'values' : 'keys' + ](resourceMap).map((internalPath) => join(directory, internalPath)); + + if (absoluteInteriorPaths.length === 0) { + return; } - return tree; + + const ancestorPath = + commonAncestorPath(...absoluteInteriorPaths.map(dirname)) ?? directory; + const ancestorPathRel = relative(directory, ancestorPath); + const ancestorTree = addonInstance.treeGenerator(ancestorPath); + const relativeInteriorPaths = absoluteInteriorPaths.map((absPath) => + relative(ancestorPath, absPath) + ); + + return buildFunnel(ancestorTree, { + files: relativeInteriorPaths, + getDestinationPath(relativePath: string): string { + for (let [a, b] of Object.entries(resourceMap)) { + const interiorName = swapInteriorExterior ? b : a; + const exteriorName = swapInteriorExterior ? a : b; + if (join(ancestorPathRel, relativePath) === normalize(interiorName)) { + return exteriorName; + } + } + throw new Error( + `bug in addonV1Shim, no match for ${relativePath} in ${JSON.stringify( + resourceMap + )}` + ); + }, + }); } return { @@ -72,22 +103,7 @@ export function addonV1Shim(directory: string, options: ShimOptions = {}) { } let maybeAppJS = meta['app-js']; if (maybeAppJS) { - const appJS = maybeAppJS; - return buildFunnel(rootTree(this), { - files: Object.values(appJS), - getDestinationPath(relativePath: string): string { - for (let [exteriorName, interiorName] of Object.entries(appJS)) { - if (relativePath === interiorName) { - return exteriorName; - } - } - throw new Error( - `bug in addonV1Shim, no match for ${relativePath} in ${JSON.stringify( - appJS - )}` - ); - }, - }); + return treeFor(this, maybeAppJS, true); } }, @@ -104,22 +120,7 @@ export function addonV1Shim(directory: string, options: ShimOptions = {}) { } let maybeAssets = meta['public-assets']; if (maybeAssets) { - const assets = maybeAssets; - return buildFunnel(rootTree(this), { - files: Object.keys(assets), - getDestinationPath(relativePath: string): string { - for (let [interiorName, exteriorName] of Object.entries(assets)) { - if (relativePath === interiorName) { - return exteriorName; - } - } - throw new Error( - `bug in addonV1Shim, no match for ${relativePath} in ${JSON.stringify( - assets - )}` - ); - }, - }); + return treeFor(this, maybeAssets); } }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c86c0356..b318e6e06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,10 +117,16 @@ importers: broccoli-funnel: specifier: ^3.0.8 version: 3.0.8 + common-ancestor-path: + specifier: ^1.0.1 + version: 1.0.1 semver: specifier: ^7.3.8 version: 7.6.1 devDependencies: + '@types/common-ancestor-path': + specifier: ^1.0.2 + version: 1.0.2 '@types/semver': specifier: ^7.3.6 version: 7.5.8 @@ -6499,6 +6505,10 @@ packages: /@types/chai@4.3.16: resolution: {integrity: sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==} + /@types/common-ancestor-path@1.0.2: + resolution: {integrity: sha512-8llyULydTb7nM9yfiW78n6id3cet+qnATPV3R44yIywxgBaa8QXFSM9QTMf4OH64QOB45BlgZ3/oL4mmFLztQw==} + dev: true + /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: @@ -9764,6 +9774,10 @@ packages: engines: {node: '>= 12'} dev: true + /common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + dev: false + /common-path-prefix@3.0.0: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} dev: false