-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The compartmentMapForNodeModules function gathers a graph of all the package.json files for the transitive dependencies of one package, then translates this into a "compartment map". A "compartment map" describes a DAG of compartments, linked by arbitrary names. Every entry in the compartment map provides all the information Endo needs to construct a SES Compartment, assuming that the compartment will use a Node.js-compatible resolveHook and an importHook that knows where to find the corresponding package's files, based on their root path. The compartment map shape will be used by the compartment assembler for executing applications directly from the file system, for building archives, and for running applications in archives.
- Loading branch information
Showing
1 changed file
with
193 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
/* eslint no-shadow: 0 */ | ||
|
||
import { inferExports } from "./infer-exports.js"; | ||
|
||
const { create, keys, entries } = Object; | ||
|
||
const decoder = new TextDecoder(); | ||
|
||
const resolve = (rel, abs) => new URL(rel, abs).toString(); | ||
|
||
const basename = location => { | ||
const { pathname } = new URL(location); | ||
const index = pathname.lastIndexOf("/"); | ||
if (index < 0) { | ||
return pathname; | ||
} | ||
return pathname.slice(index + 1); | ||
}; | ||
|
||
const readDescriptor = async (read, packagePath) => { | ||
const descriptorPath = resolve("package.json", packagePath); | ||
const descriptorBytes = await read(descriptorPath).catch(_error => undefined); | ||
if (descriptorBytes === undefined) { | ||
return undefined; | ||
} | ||
const descriptorText = decoder.decode(descriptorBytes); | ||
const descriptor = JSON.parse(descriptorText); | ||
return descriptor; | ||
}; | ||
|
||
const readDescriptorWithMemo = async (memo, read, packagePath) => { | ||
let promise = memo[packagePath]; | ||
if (promise !== undefined) { | ||
return promise; | ||
} | ||
promise = readDescriptor(read, packagePath); | ||
memo[packagePath] = promise; | ||
return promise; | ||
}; | ||
|
||
const findPackage = async (readDescriptor, directory, name) => { | ||
for (;;) { | ||
const packagePath = resolve(`node_modules/${name}/`, directory); | ||
// eslint-disable-next-line no-await-in-loop | ||
const packageDescriptor = await readDescriptor(packagePath); | ||
if (packageDescriptor !== undefined) { | ||
return { packagePath, packageDescriptor }; | ||
} | ||
|
||
const parent = resolve("../", directory); | ||
if (parent === directory) { | ||
return undefined; | ||
} | ||
directory = parent; | ||
|
||
const base = basename(directory); | ||
if (base === "node_modules") { | ||
directory = resolve("../", directory); | ||
if (parent === directory) { | ||
return undefined; | ||
} | ||
directory = parent; | ||
} | ||
} | ||
}; | ||
|
||
const graphPackage = async ( | ||
readDescriptor, | ||
graph, | ||
{ packagePath, packageDescriptor }, | ||
tags | ||
) => { | ||
if (graph[packagePath] !== undefined) { | ||
// Returning the promise here would create a causal cycle and stall recursion. | ||
return undefined; | ||
} | ||
const result = {}; | ||
graph[packagePath] = result; | ||
|
||
const dependencies = []; | ||
const children = []; | ||
for (const name of keys(packageDescriptor.dependencies || {})) { | ||
children.push( | ||
// Mutual recursion ahead: | ||
// eslint-disable-next-line no-use-before-define | ||
gatherDependency( | ||
readDescriptor, | ||
graph, | ||
dependencies, | ||
packagePath, | ||
name, | ||
tags | ||
) | ||
); | ||
} | ||
|
||
const { name, version } = packageDescriptor; | ||
result.label = `${name}@${version}`; | ||
result.dependencies = dependencies; | ||
result.exports = inferExports(packageDescriptor, tags); | ||
|
||
return Promise.all(children); | ||
}; | ||
|
||
const gatherDependency = async ( | ||
readDescriptor, | ||
graph, | ||
dependencies, | ||
packagePath, | ||
name, | ||
tags | ||
) => { | ||
const dependency = await findPackage(readDescriptor, packagePath, name); | ||
if (dependency === undefined) { | ||
throw new Error(`Cannot find dependency ${name} for ${packagePath}`); | ||
} | ||
dependencies.push(dependency.packagePath); | ||
await graphPackage(readDescriptor, graph, dependency, tags); | ||
}; | ||
|
||
const graphPackages = async ( | ||
read, | ||
packagePath, | ||
tags, | ||
mainPackageDescriptor | ||
) => { | ||
const memo = create(null); | ||
const readDescriptor = packagePath => | ||
readDescriptorWithMemo(memo, read, packagePath); | ||
|
||
if (mainPackageDescriptor !== undefined) { | ||
memo[packagePath] = Promise.resolve(mainPackageDescriptor); | ||
} | ||
|
||
const packageDescriptor = await readDescriptor(packagePath); | ||
|
||
tags = new Set(tags || []); | ||
tags.add("import", "endo"); | ||
|
||
if (packageDescriptor === undefined) { | ||
throw new Error( | ||
`Cannot find package.json for application at ${packagePath}` | ||
); | ||
} | ||
const graph = create(null); | ||
await graphPackage( | ||
readDescriptor, | ||
graph, | ||
{ | ||
packagePath, | ||
packageDescriptor | ||
}, | ||
tags | ||
); | ||
return graph; | ||
}; | ||
|
||
const translateGraph = (mainPackagePath, graph) => { | ||
const compartments = {}; | ||
|
||
for (const [packagePath, { label, dependencies }] of entries(graph)) { | ||
const modules = {}; | ||
for (const packagePath of dependencies) { | ||
const { exports } = graph[packagePath]; | ||
for (const [exportName, module] of entries(exports)) { | ||
modules[exportName] = { | ||
compartment: packagePath, | ||
module | ||
}; | ||
} | ||
} | ||
compartments[packagePath] = { | ||
label, | ||
root: packagePath, | ||
modules | ||
}; | ||
} | ||
|
||
return { | ||
main: mainPackagePath, | ||
compartments | ||
}; | ||
}; | ||
|
||
export const compartmentMapForNodeModules = async ( | ||
read, | ||
packagePath, | ||
tags, | ||
packageDescriptor | ||
) => { | ||
const graph = await graphPackages(read, packagePath, tags, packageDescriptor); | ||
return translateGraph(packagePath, graph); | ||
}; |