feat(compartment-mapper): Follow symbolic links
MODIFY
packages/compartment-mapper/NEWS.md
packages/compartment-mapper/NEWS.md
@@ -2,6 +2,11 @@ User-visible changes to the compartment mapper:
# Next release
+- Follows Node.js packages through symbolic links, but requires an additional
+ read power to do this. Some functions now accept `{ read, canonical }` powers
+ in places only `read` was accepted previously, specifically
+ `compartmentMapFromNodeModules`, `makeArchive`, `writeArchive`, and
+ `loadLocation`.
- Adds an `"@endo/compartment-mapper/node-powers.js"` entry point utility
module that exports `makeNodeReadPowers` and `makeNodeWritePowers` that adapt
the Node.js `fs` module to the promise and URL oriented interfaces expected

MODIFY
packages/compartment-mapper/src/archive.js
packages/compartment-mapper/src/archive.js
@@ -11,6 +11,7 @@ import { parseJson } from './parse-json.js';
import { parseArchiveCjs } from './parse-archive-cjs.js';
import { parseArchiveMjs } from './parse-archive-mjs.js';
import { parseLocatedJson } from './json.js';
+import { unpackReadPowers } from './powers.js';
const textEncoder = new TextEncoder();
@@ -137,14 +138,15 @@ const addSourcesToArchive = async (archive, sources) => {
};
/**
- * @param {ReadFn} read
+ * @param {ReadFn | ReadPowers} powers
* @param {string} moduleLocation
* @param {Object} [options]
* @param {ModuleTransforms} [options.moduleTransforms]
* @returns {Promise<Uint8Array>}
*/
-export const makeArchive = async (read, moduleLocation, options) => {
+export const makeArchive = async (powers, moduleLocation, options) => {
const { moduleTransforms } = options || {};
+ const { read } = unpackReadPowers(powers);
const {
packageLocation,
packageDescriptorText,
@@ -160,7 +162,7 @@ export const makeArchive = async (read, moduleLocation, options) => {
packageDescriptorLocation,
);
const compartmentMap = await compartmentMapForNodeModules(
- read,
+ powers,
packageLocation,
tags,
packageDescriptor,
@@ -224,18 +226,18 @@ export const makeArchive = async (read, moduleLocation, options) => {
/**
* @param {WriteFn} write
- * @param {ReadFn} read
+ * @param {ReadFn | ReadPowers} readPowers
* @param {string} archiveLocation
* @param {string} moduleLocation
* @param {ArchiveOptions} [options]
*/
export const writeArchive = async (
write,
- read,
+ readPowers,
archiveLocation,
moduleLocation,
options,
) => {
- const archiveBytes = await makeArchive(read, moduleLocation, options);
+ const archiveBytes = await makeArchive(readPowers, moduleLocation, options);
await write(archiveLocation, archiveBytes);
};

MODIFY
packages/compartment-mapper/src/import.js
packages/compartment-mapper/src/import.js
@@ -10,6 +10,7 @@ import { parseJson } from './parse-json.js';
import { parseCjs } from './parse-cjs.js';
import { parseMjs } from './parse-mjs.js';
import { parseLocatedJson } from './json.js';
+import { unpackReadPowers } from './powers.js';
/** @type {Record<string, ParseFn>} */
export const parserForLanguage = {
@@ -19,14 +20,16 @@ export const parserForLanguage = {
};
/**
- * @param {ReadFn} read
+ * @param {ReadFn | ReadPowers} readPowers
* @param {string} moduleLocation
* @param {ArchiveOptions} [options]
* @returns {Promise<Application>}
*/
-export const loadLocation = async (read, moduleLocation, options) => {
+export const loadLocation = async (readPowers, moduleLocation, options) => {
const { moduleTransforms = {} } = options || {};
+ const { read } = unpackReadPowers(readPowers);
+
const {
packageLocation,
packageDescriptorText,
@@ -41,7 +44,7 @@ export const loadLocation = async (read, moduleLocation, options) => {
/** @type {Set<string>} */
const tags = new Set();
const compartmentMap = await compartmentMapForNodeModules(
- read,
+ readPowers,
packageLocation,
tags,
packageDescriptor,

MODIFY
packages/compartment-mapper/src/node-modules.js
packages/compartment-mapper/src/node-modules.js
import { inferExports } from './infer-exports.js';
import { parseLocatedJson } from './json.js';
+import { unpackReadPowers } from './powers.js';
const { create, keys, values } = Object;
@@ -102,6 +103,7 @@ const readDescriptorWithMemo = async (memo, read, packageLocation) => {
* find it efficiently.
*
* @param {ReadDescriptorFn} readDescriptor
+ * @param {CanonicalFn} canonical
* @param {string} directory
* @param {string} name
* @returns {Promise<{
@@ -109,9 +111,13 @@ const readDescriptorWithMemo = async (memo, read, packageLocation) => {
* packageDescriptor: Object,
* } | undefined>}
*/
-const findPackage = async (readDescriptor, directory, name) => {
+const findPackage = async (readDescriptor, canonical, directory, name) => {
for (;;) {
- const packageLocation = resolveLocation(`node_modules/${name}/`, directory);
+ // eslint-disable-next-line no-await-in-loop
+ const packageLocation = await canonical(
+ resolveLocation(`node_modules/${name}/`, directory),
+ );
+
// eslint-disable-next-line no-await-in-loop
const packageDescriptor = await readDescriptor(packageLocation);
if (packageDescriptor !== undefined) {
@@ -191,6 +197,7 @@ const inferParsers = (descriptor, location) => {
*
* @param {string} name
* @param {ReadDescriptorFn} readDescriptor
+ * @param {CanonicalFn} canonical
* @param {Graph} graph
* @param {Object} packageDetails
* @param {string} packageDetails.packageLocation
@@ -201,6 +208,7 @@ const inferParsers = (descriptor, location) => {
const graphPackage = async (
name,
readDescriptor,
+ canonical,
graph,
{ packageLocation, packageDescriptor },
tags,
@@ -228,6 +236,7 @@ const graphPackage = async (
// eslint-disable-next-line no-use-before-define
gatherDependency(
readDescriptor,
+ canonical,
graph,
dependencies,
packageLocation,
@@ -256,6 +265,7 @@ const graphPackage = async (
/**
* @param {ReadDescriptorFn} readDescriptor
+ * @param {CanonicalFn} canonical
* @param {Graph} graph - the partially build graph.
* @param {Record<string, string>} dependencies
* @param {string} packageLocation - location of the package of interest.
@@ -264,18 +274,24 @@ const graphPackage = async (
*/
const gatherDependency = async (
readDescriptor,
+ canonical,
graph,
dependencies,
packageLocation,
name,
tags,
) => {
- const dependency = await findPackage(readDescriptor, packageLocation, name);
+ const dependency = await findPackage(
+ readDescriptor,
+ canonical,
+ packageLocation,
+ name,
+ );
if (dependency === undefined) {
throw new Error(`Cannot find dependency ${name} for ${packageLocation}`);
}
dependencies[name] = dependency.packageLocation;
- await graphPackage(name, readDescriptor, graph, dependency, tags);
+ await graphPackage(name, readDescriptor, canonical, graph, dependency, tags);
};
/**
@@ -288,6 +304,7 @@ const gatherDependency = async (
* The URLs in dependencies will all exist as other keys of graph.
*
* @param {ReadFn} read
+ * @param {CanonicalFn} canonical
* @param {string} packageLocation - location of the main package.
* @param {Set<string>} tags
* @param {Object} mainPackageDescriptor - the parsed contents of the main
@@ -295,6 +312,7 @@ const gatherDependency = async (
*/
const graphPackages = async (
read,
+ canonical,
packageLocation,
tags,
mainPackageDescriptor,
@@ -325,6 +343,7 @@ const graphPackages = async (
await graphPackage(
packageDescriptor.name,
readDescriptor,
+ canonical,
graph,
{
packageLocation,
@@ -407,7 +426,7 @@ const translateGraph = (
};
/**
- * @param {ReadFn} read
+ * @param {ReadFn | ReadPowers} readPowers
* @param {string} packageLocation
* @param {Set<string>} tags
* @param {Object} packageDescriptor
@@ -415,14 +434,16 @@ const translateGraph = (
* @returns {Promise<CompartmentMapDescriptor>}
*/
export const compartmentMapForNodeModules = async (
- read,
+ readPowers,
packageLocation,
tags,
packageDescriptor,
moduleSpecifier,
) => {
+ const { read, canonical } = unpackReadPowers(readPowers);
const graph = await graphPackages(
read,
+ canonical,
packageLocation,
tags,
packageDescriptor,

ADD
packages/compartment-mapper/src/powers.js
+/** @type {CanonicalFn} */
+const canonicalShim = async path => path;
+
+/**
+ * @param {ReadFn | ReadPowers} powers
+ * @returns {ReadPowers}
+ */
+export const unpackReadPowers = powers => {
+ if (typeof powers === 'function') {
+ return {
+ read: powers,
+ canonical: canonicalShim,
+ };
+ }
+ return powers;
+};

feat(compartment-mapper): Follow symbolic links