feat(compartment-mapper): implement require.resolve as an external configuration item
MODIFY
packages/compartment-mapper/src/archive.js
packages/compartment-mapper/src/archive.js
@@ -253,6 +253,7 @@ const digestLocation = async (powers, moduleLocation, options) => {
modules: exitModules = {},
dev = false,
captureSourceLocation = undefined,
+ requireResolve = null,
} = options || {};
const { read, computeSha512 } = unpackReadPowers(powers);
const {
@@ -292,6 +293,7 @@ const digestLocation = async (powers, moduleLocation, options) => {
const makeImportHook = makeImportHookMaker(
read,
packageLocation,
+ requireResolve,
sources,
compartments,
exitModules,

MODIFY
packages/compartment-mapper/src/bundle.js
packages/compartment-mapper/src/bundle.js
@@ -129,10 +129,11 @@ const sortedModules = (
* @param {string} moduleLocation
* @param {Object} [options]
* @param {ModuleTransforms} [options.moduleTransforms]
+ * @param {Function} [options.requireResolve]
* @returns {Promise<string>}
*/
export const makeBundle = async (read, moduleLocation, options) => {
- const { moduleTransforms } = options || {};
+ const { moduleTransforms, requireResolve = null } = options || {};
const {
packageLocation,
packageDescriptorText,
@@ -165,6 +166,7 @@ export const makeBundle = async (read, moduleLocation, options) => {
const makeImportHook = makeImportHookMaker(
read,
packageLocation,
+ requireResolve,
sources,
compartments,
);

MODIFY
packages/compartment-mapper/src/import-hook.js
packages/compartment-mapper/src/import-hook.js
@@ -48,6 +48,7 @@ function getImportsFromRecord(record) {
/**
* @param {ReadFn|ReadPowers} readPowers
* @param {string} baseLocation
+ * @param {Function|null} [requireResolve]
* @param {Sources} sources
* @param {Record<string, CompartmentDescriptor>} compartments
* @param {Record<string, any>} exitModules
@@ -57,6 +58,7 @@ function getImportsFromRecord(record) {
export const makeImportHookMaker = (
readPowers,
baseLocation,
+ requireResolve = null,
sources = Object.create(null),
compartments = Object.create(null),
exitModules = Object.create(null),
@@ -169,12 +171,6 @@ export const makeImportHookMaker = (
const moduleBytes = await read(moduleLocation).catch(
_error => undefined,
);
- const requireResolve = specifier => {
- console.warn('Warning: Getting correct result form require.resolve in Endo is only possible if the package was resolved earlier.')
- return packageSources[specifier]
- ? packageSources[specifier].sourceLocation
- : null;
- };
if (moduleBytes !== undefined) {
// eslint-disable-next-line no-await-in-loop
const envelope = await parse(
@@ -182,8 +178,10 @@ export const makeImportHookMaker = (
candidateSpecifier,
moduleLocation,
packageLocation,
+ {
readPowers,
requireResolve,
+ },
);
const {
parser,

MODIFY
packages/compartment-mapper/src/import.js
packages/compartment-mapper/src/import.js
@@ -37,7 +37,8 @@ export const parserForLanguage = {
* @returns {Promise<Application>}
*/
export const loadLocation = async (readPowers, moduleLocation, options) => {
- const { moduleTransforms = {}, dev = false } = options || {};
+ const { moduleTransforms = {}, dev = false, requireResolve = null } =
+ options || {};
const { read } = unpackReadPowers(readPowers);
@@ -73,7 +74,11 @@ export const loadLocation = async (readPowers, moduleLocation, options) => {
__shimTransforms__,
Compartment,
} = options;
- const makeImportHook = makeImportHookMaker(readPowers, packageLocation);
+ const makeImportHook = makeImportHookMaker(
+ readPowers,
+ packageLocation,
+ requireResolve,
+ );
const { compartment } = link(compartmentMap, {
makeImportHook,
parserForLanguage,

MODIFY
packages/compartment-mapper/src/link.js
packages/compartment-mapper/src/link.js
@@ -77,7 +77,7 @@ const makeExtensionParser = (
parserForLanguage,
transforms,
) => {
- return async (bytes, specifier, location, packageLocation, readPowers, requireResolve) => {
+ return async (bytes, specifier, location, packageLocation, options) => {
let language;
if (has(languageForModuleSpecifier, specifier)) {
language = languageForModuleSpecifier[specifier];
@@ -106,7 +106,7 @@ const makeExtensionParser = (
);
}
const { parse } = parserForLanguage[language];
- return parse(bytes, specifier, location, packageLocation, readPowers, requireResolve);
+ return parse(bytes, specifier, location, packageLocation, options);
};
};

MODIFY
packages/compartment-mapper/src/parse-cjs-shared-export-wrapper.js
packages/compartment-mapper/src/parse-cjs-shared-export-wrapper.js
@@ -64,9 +64,12 @@ export const getModulePaths = (readPowers, location) => {
* ModuleEnvironmentRecord wrapper
* Creates shared export processing primitives to be used both Location and Archive usecases of cjs
*
- * @param {Object} moduleEnvironmentRecord
- * @param {Compartment} compartment
- * @param {Record<string, string>} resolvedImports
+ * @param {Object} in
+ * @param {Object} in.moduleEnvironmentRecord
+ * @param {Compartment} in.compartment
+ * @param {Record<string, string>} in.resolvedImports
+ * @param {string} in.location
+ * @param {Function} [in.requireResolve]
* @returns {{
* module: { exports: any },
* moduleExports: any,
@@ -74,7 +77,13 @@ export const getModulePaths = (readPowers, location) => {
* require: Function,
* }}
*/
-export const wrap = (moduleEnvironmentRecord, compartment, resolvedImports, requireResolve) => {
+export const wrap = ({
+ moduleEnvironmentRecord,
+ compartment,
+ resolvedImports,
+ location,
+ requireResolve,
+}) => {
// This initial default value makes things like exports.hasOwnProperty() work in cjs.
moduleEnvironmentRecord.default = create(
compartment.globalThis.Object.prototype,
@@ -132,9 +141,19 @@ export const wrap = (moduleEnvironmentRecord, compartment, resolvedImports, requ
return namespace;
}
};
- require.resolve = freeze(requireResolve)
+ if (requireResolve) {
+ require.resolve = freeze((specifier, options) =>
+ requireResolve(location, specifier, options),
+ );
+ } else {
+ require.resolve = freeze(specifier => {
+ const error = Error(`Cannot find module '${specifier}'`);
+ defineProperty(error, 'code', { value: 'MODULE_NOT_FOUND' });
+ throw error;
+ });
+ }
- freeze(require)
+ freeze(require);
const afterExecute = () => {
const exportsHaveBeenOverwritten = finalExports !== originalExports;

MODIFY
packages/compartment-mapper/src/parse-cjs.js
packages/compartment-mapper/src/parse-cjs.js
@@ -13,8 +13,7 @@ export const parseCjs = async (
_specifier,
location,
_packageLocation,
- readPowers,
- requireResolve,
+ { readPowers, requireResolve } = {},
) => {
const source = textDecoder.decode(bytes);
@@ -39,12 +38,13 @@ export const parseCjs = async (
`(function (require, exports, module, __filename, __dirname) { ${source} //*/\n})\n//# sourceURL=${location}`,
);
- const { require, moduleExports, module, afterExecute } = wrap(
+ const { require, moduleExports, module, afterExecute } = wrap({
moduleEnvironmentRecord,
compartment,
resolvedImports,
+ location,
requireResolve,
- );
+ });
functor(require, moduleExports, module, filename, dirname);

MODIFY
packages/compartment-mapper/src/parse-pre-cjs.js
packages/compartment-mapper/src/parse-pre-cjs.js
@@ -11,7 +11,7 @@ export const parsePreCjs = async (
_specifier,
location,
_packageLocation,
- readPowers,
+ { readPowers, requireResolve } = {},
) => {
const text = textDecoder.decode(bytes);
const { source, imports, exports, reexports } = parseLocatedJson(
@@ -29,11 +29,13 @@ export const parsePreCjs = async (
const execute = (moduleEnvironmentRecord, compartment, resolvedImports) => {
const functor = compartment.evaluate(source);
- const { require, moduleExports, module, afterExecute } = wrap(
+ const { require, moduleExports, module, afterExecute } = wrap({
moduleEnvironmentRecord,
compartment,
resolvedImports,
- );
+ requireResolve,
+ location,
+ });
functor(require, moduleExports, module, filename, dirname);

MODIFY
packages/compartment-mapper/src/types.js
packages/compartment-mapper/src/types.js
@@ -197,7 +197,9 @@ export {};
* @param {string} specifier
* @param {string} location
* @param {string} packageLocation
- * @param {ReadFn | ReadPowers} [readPowers]
+ * @param {object} [options]
+ * @param {ReadFn | ReadPowers} [options.readPowers]
+ * @param {Function} [options.requireResolve]
* @returns {Promise<{
* bytes: Uint8Array,
* parser: Language,
@@ -308,4 +310,5 @@ export {};
* @property {Record<string, any>} [modules]
* @property {boolean} [dev]
* @property {CaptureSourceLocationHook} [captureSourceLocation]
+ * @property {Function} [requireResolve]
*/

MODIFY
packages/compartment-mapper/test/fixtures-cjs-compat/node_modules/app/index.js
packages/compartment-mapper/test/fixtures-cjs-compat/node_modules/app/index.js
@@ -30,25 +30,11 @@ function assertInteropNameCollisions({ moduleReference, expect, name }) {
What we expected: ${expect}`);
}
}
+module.exports.results = {
+ requireResolvePaths
+};
module.exports.assertions = {
- requireResolve() {
- // process._rawDebug(requireResolvePaths)
- const assertion = [
- "Cannot find module '.'",
- '/skipped/fixtures-cjs-compat/node_modules/require-resolve/package.json',
- '/skipped/fixtures-cjs-compat/node_modules/require-resolve/nested/file.js',
- '/skipped/fixtures-cjs-compat/node_modules/require-resolve/nested/file.js.map',
- '/skipped/fixtures-cjs-compat/node_modules/app/index.js',
- '/skipped/fixtures-cjs-compat/node_modules/nested-export/callBound.js'
- ]
- assertion.forEach((expected, i) => {
- const actual = requireResolvePaths[i];
- if (actual !== expected) {
- throw Error(`Expected require.resolve to return \n"${expected}"\nbut got \n"${actual}"`);
- }
- })
- },
packageNestedFile() {
if(!nestedFile.isOk){
throw Error('Expected nested-file/nested to have been resolved based on path')

MODIFY
packages/compartment-mapper/test/fixtures-cjs-compat/node_modules/require-resolve/main.js
packages/compartment-mapper/test/fixtures-cjs-compat/node_modules/require-resolve/main.js
@@ -17,4 +17,3 @@ module.exports = [
adaptToAssertions(()=>require.resolve("fs")),
adaptToAssertions(()=>require.resolve("nested-export/callBound")),
]
\ No newline at end of file
-process._rawDebug(module.exports)
\ No newline at end of file

MODIFY
packages/compartment-mapper/test/scaffold.js
packages/compartment-mapper/test/scaffold.js
import 'ses';
import fs from 'fs';
import crypto from 'crypto';
+import { createRequire } from 'module';
import url from 'url';
import { ZipReader, ZipWriter } from '@endo/zip';
import {
@@ -87,6 +88,8 @@ export function scaffold(
const application = await loadLocation(readPowers, fixture, {
dev: true,
+ requireResolve: (from, specifier, options) =>
+ createRequire(from).resolve(specifier, options),
});
const { namespace } = await application.import({
globals,
@@ -107,6 +110,8 @@ export function scaffold(
modules,
Compartment,
dev: true,
+ requireResolve: (from, specifier, options) =>
+ createRequire(from).resolve(specifier, options),
});
return namespace;
});

MODIFY
packages/compartment-mapper/test/test-cjs-compat.js
packages/compartment-mapper/test/test-cjs-compat.js
@@ -15,8 +15,8 @@ const fixtureDirname = new URL(
import.meta.url,
).toString();
-const assertFixture = (t, { namespace }) => {
- const { assertions } = namespace;
+const assertFixture = (t, { namespace, testCategoryHint }) => {
+ const { assertions, results } = namespace;
assertions.packageExportsShenanigans();
assertions.packageWithDefaultField();
@@ -25,12 +25,36 @@ const assertFixture = (t, { namespace }) => {
assertions.moduleWithCycle();
assertions.defaultChangesAfterExec();
assertions.packageNestedFile();
- assertions.requireResolve();
+ if (testCategoryHint === 'Location') {
+ t.deepEqual(results.requireResolvePaths, [
+ "Cannot find module '.'",
+ '/skipped/fixtures-cjs-compat/node_modules/require-resolve/package.json',
+ '/skipped/fixtures-cjs-compat/node_modules/require-resolve/nested/index.js',
+ '/skipped/fixtures-cjs-compat/node_modules/require-resolve/nested/file.js',
+ '/skipped/fixtures-cjs-compat/node_modules/require-resolve/nested/file.js.map',
+ "Cannot find module './nested/file.missing'",
+ '/skipped/fixtures-cjs-compat/node_modules/app/index.js',
+ 'fs',
+ '/skipped/fixtures-cjs-compat/node_modules/nested-export/callBound.js',
+ ]);
+ } else {
+ t.deepEqual(results.requireResolvePaths, [
+ "Cannot find module '.'",
+ "Cannot find module './package.json'",
+ "Cannot find module './nested'",
+ "Cannot find module './nested/file.js'",
+ "Cannot find module './nested/file.js.map'",
+ "Cannot find module './nested/file.missing'",
+ "Cannot find module 'app'",
+ "Cannot find module 'fs'",
+ "Cannot find module 'nested-export/callBound'",
+ ]);
+ }
t.pass();
};
-const fixtureAssertionCount = 1;
+const fixtureAssertionCount = 2;
scaffold(
'fixtures-cjs-compat',

feat(compartment-mapper): implement require.resolve as an external configuration item