fix(compartment-map): Restore test fixture maker and support for exit modules from archives
MODIFY
packages/compartment-mapper/src/archive.js
packages/compartment-mapper/src/archive.js
@@ -154,7 +154,8 @@ const addSourcesToArchive = async (archive, sources) => {
* @returns {Promise<Uint8Array>}
*/
export const makeArchive = async (powers, moduleLocation, options) => {
- const { moduleTransforms, modules = {}, dev = false } = options || {};
+ const { moduleTransforms, modules: exitModules = {}, dev = false } =
+ options || {};
const { read } = unpackReadPowers(powers);
const {
packageLocation,
@@ -191,15 +192,17 @@ export const makeArchive = async (powers, moduleLocation, options) => {
packageLocation,
sources,
compartments,
+ exitModules,
);
// Induce importHook to record all the necessary modules to import the given module specifier.
const compartment = assemble(compartmentMap, {
resolve,
- modules,
+ modules: exitModules,
makeImportHook,
moduleTransforms,
parserForLanguage,
+ archiveOnly: true,
});
await compartment.load(entryModuleSpecifier);

MODIFY
packages/compartment-mapper/src/assemble.js
packages/compartment-mapper/src/assemble.js
@@ -16,6 +16,29 @@ const { entries, fromEntries, freeze } = Object;
const { hasOwnProperty } = Object.prototype;
const { apply } = Reflect;
+const inertStaticModuleRecord = {
+ imports: [],
+ exports: [],
+ execute() {
+ throw new Error(
+ `Assertion failed: compartment graphs built for archives cannot be initialized`,
+ );
+ },
+};
+
+const inertModuleNamespace = new Compartment(
+ {},
+ {},
+ {
+ resolveHook() {
+ return '';
+ },
+ async importHook() {
+ return inertStaticModuleRecord;
+ },
+ },
+).module('');
+
const defaultCompartment = Compartment;
// q, as in quote, for strings in error messages.
@@ -155,6 +178,7 @@ const trimModuleSpecifierPrefix = (moduleSpecifier, prefix) => {
* @param {Record<string, ModuleDescriptor>} moduleDescriptors
* @param {Record<string, ModuleDescriptor>} scopeDescriptors
* @param {Record<string, string>} exitModules
+ * @param {boolean} archiveOnly
* @returns {ModuleMapHook | undefined}
*/
const makeModuleMapHook = (
@@ -163,6 +187,7 @@ const makeModuleMapHook = (
moduleDescriptors,
scopeDescriptors,
exitModules,
+ archiveOnly,
) => {
/**
* @param {string} moduleSpecifier
@@ -177,10 +202,10 @@ const makeModuleMapHook = (
exit,
} = moduleDescriptor;
if (exit !== undefined) {
- // TODO Currenly, only the entry package can connect to built-in modules.
+ // TODO Currenly, every package can connect to built-in modules.
// Policies should be able to allow third-party modules to exit to
- // built-ins, or have built-ins subverted by modules from specific
- // compartments.
+ // built-ins explicitly, or have built-ins subverted by modules from
+ // specific compartments.
const module = exitModules[exit];
if (module === undefined) {
throw new Error(
@@ -189,8 +214,12 @@ const makeModuleMapHook = (
)}, may be missing from ${compartmentName} package.json`,
);
}
+ if (archiveOnly) {
+ return inertModuleNamespace;
+ } else {
return module;
}
+ }
if (foreignModuleSpecifier !== undefined) {
const foreignCompartment = compartments[foreignCompartmentName];
if (foreignCompartment === undefined) {
@@ -202,6 +231,17 @@ const makeModuleMapHook = (
}
return foreignCompartment.module(foreignModuleSpecifier);
}
+ } else if (has(exitModules, moduleSpecifier)) {
+ // When linking off the filesystem as with `importLocation`,
+ // there isn't a module descriptor for every module.
+ // TODO grant access to built-in modules contingent on a policy in the
+ // application's entry package descriptor.
+ moduleDescriptors[moduleSpecifier] = { exit: moduleSpecifier };
+ if (archiveOnly) {
+ return inertModuleNamespace;
+ } else {
+ return exitModules[moduleSpecifier];
+ }
}
// Search for a scope that shares a prefix with the requested module
@@ -281,6 +321,7 @@ export const link = (
moduleTransforms = {},
__shimTransforms__ = [],
modules: exitModules = {},
+ archiveOnly = false,
Compartment = defaultCompartment,
},
) => {
@@ -318,12 +359,13 @@ export const link = (
modules,
scopes,
exitModules,
+ archiveOnly,
);
const resolveHook = resolve;
resolvers[compartmentName] = resolve;
// TODO also thread powers selectively.
- const compartment = new Compartment(globals, exitModules, {
+ const compartment = new Compartment(globals, undefined, {
resolveHook,
importHook,
moduleMapHook,

MODIFY
packages/compartment-mapper/src/types.js
packages/compartment-mapper/src/types.js
@@ -189,6 +189,7 @@ export const moduleJSDocTypes = true;
* @property {AssembleImportHook} makeImportHook
* @property {ParserForLanguage} parserForLanguage
* @property {ModuleTransforms} [moduleTransforms]
+ * @property {boolean} [archiveOnly]
*/
/**
@@ -235,6 +236,6 @@ export const moduleJSDocTypes = true;
/**
* @typedef {Object} ArchiveOptions
* @property {ModuleTransforms} [moduleTransforms]
- * @property {Record<string, any>} [modules]
+ * @property {Record<string, never>} [modules]
* @property {boolean} [dev]
*/

MODIFY
packages/compartment-mapper/test/app.agar-make.js
packages/compartment-mapper/test/app.agar-make.js
// The archive may need to be regenerated if the test fixture and assertions
// have been changed.
+/* global process */
+
import 'ses';
import fs from 'fs';
import { writeArchive } from '../archive.js';
@@ -20,4 +22,14 @@ const fixture = new URL(
).toString();
const archiveFixture = new URL('app.agar', import.meta.url).toString();
-writeArchive(write, readPowers, archiveFixture, fixture);
+const mitt = err =>
+ process.nextTick(() => {
+ throw err;
+ });
+
+writeArchive(write, readPowers, archiveFixture, fixture, {
+ dev: true,
+ modules: {
+ builtin: true,
+ },
+}).catch(mitt);

MODIFY
packages/compartment-mapper/test/test-main.js
packages/compartment-mapper/test/test-main.js
@@ -187,7 +187,7 @@ test('writeArchive / loadArchive', async t => {
};
await writeArchive(fakeWrite, readPowers, 'app.agar', fixture, {
- modules,
+ modules: { builtin: true },
dev: true,
});
const application = await loadArchive(fakeRead, 'app.agar');

fix(compartment-map): Restore test fixture maker and support for exit modules from archives