From ec0719713cbecfffcc35a434fd3d291a4fb285af Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Fri, 20 Dec 2024 15:37:06 -0800 Subject: [PATCH] feat(compartment-mapper): more renames returned from captureFromMap / makeArchiveCompartmentMap This change isolates the duplicated code in `archive-lite.js` and `capture-lite.js` into a new internal module, `digest.js`. It provides `digestCompartmentMap()`, which is now consumed by both `archive-lite.js` and `capture-lite.js`. Both `makeArchiveCompartmentMap()` and `captureFromMap()` now pass new properties from `digestCompartmentMap()` through to their consumers: `newToOldCompartmentMap` and `oldToNewCompartmentMap`. **The `compartmentRenames` property is now soft-deprecated** (since its naming is ambiguous). The same property is now `newToOldCompartmentMap`; `compartmentRenames` should eventually be removed. _Note_: The duplicated code drifted slightly in `archive-lite.js`; see e3b310d. These changes were kept and put into `digest.js`. --- .../compartment-mapper/src/archive-lite.js | 206 ++------------- .../compartment-mapper/src/capture-lite.js | 196 +-------------- packages/compartment-mapper/src/digest.js | 235 ++++++++++++++++++ .../compartment-mapper/src/types/external.ts | 48 +++- .../test/capture-lite.test.js | 13 +- 5 files changed, 306 insertions(+), 392 deletions(-) create mode 100644 packages/compartment-mapper/src/digest.js diff --git a/packages/compartment-mapper/src/archive-lite.js b/packages/compartment-mapper/src/archive-lite.js index 4822bc4170..f8ffddb17c 100644 --- a/packages/compartment-mapper/src/archive-lite.js +++ b/packages/compartment-mapper/src/archive-lite.js @@ -33,12 +33,11 @@ /** * @import { * ArchiveLiteOptions, + * ArchiveResult, * ArchiveWriter, * CaptureSourceLocationHook, - * CompartmentDescriptor, * CompartmentMapDescriptor, * HashPowers, - * ModuleDescriptor, * ReadFn, * ReadPowers, * Sources, @@ -54,12 +53,8 @@ import { makeImportHookMaker, } from './import-hook.js'; import { unpackReadPowers } from './powers.js'; -import { - assertCompartmentMap, - stringCompare, - pathCompare, -} from './compartment-map.js'; import { detectAttenuators } from './policy.js'; +import { digestCompartmentMap } from './digest.js'; const textEncoder = new TextEncoder(); @@ -72,160 +67,7 @@ const { assign, create, freeze } = Object; */ const resolveLocation = (rel, abs) => new URL(rel, abs).toString(); -const { keys, entries, fromEntries } = Object; - -/** - * We attempt to produce compartment maps that are consistent regardless of - * whether the packages were originally laid out on disk for development or - * production, and other trivia like the fully qualified path of a specific - * installation. - * - * Naming compartments for the self-ascribed name and version of each Node.js - * package is insufficient because they are not guaranteed to be unique. - * Dependencies do not necessarilly come from the npm registry and may be - * for example derived from fully qualified URL's or Github org and project - * names. - * Package managers are also not required to fully deduplicate the hard - * copy of each package even when they are identical resources. - * Duplication is undesirable, but we elect to defer that problem to solutions - * in the package managers, as the alternative would be to consistently hash - * the original sources of the packages themselves, which may not even be - * available much less pristine for us. - * - * So, instead, we use the lexically least path of dependency names, delimited - * by hashes. - * The compartment maps generated by the ./node-modules.js tooling pre-compute - * these traces for our use here. - * We sort the compartments lexically on their self-ascribed name and version, - * and use the lexically least dependency name path as a tie-breaker. - * The dependency path is logical and orthogonal to the package manager's - * actual installation location, so should be orthogonal to the vagaries of the - * package manager's deduplication algorithm. - * - * @param {Record} compartments - * @returns {Record} map from old to new compartment names. - */ -const renameCompartments = compartments => { - /** @type {Record} */ - const compartmentRenames = create(null); - let index = 0; - let prev = ''; - - // The sort below combines two comparators to avoid depending on sort - // stability, which became standard as recently as 2019. - // If that date seems quaint, please accept my regards from the distant past. - // We are very proud of you. - const compartmentsByPath = Object.entries(compartments) - .map(([name, compartment]) => ({ - name, - path: compartment.path, - label: compartment.label, - })) - .sort((a, b) => { - if (a.label === b.label) { - assert(a.path !== undefined && b.path !== undefined); - return pathCompare(a.path, b.path); - } - return stringCompare(a.label, b.label); - }); - - for (const { name, label } of compartmentsByPath) { - if (label === prev) { - compartmentRenames[name] = `${label}-n${index}`; - index += 1; - } else { - compartmentRenames[name] = label; - prev = label; - index = 1; - } - } - return compartmentRenames; -}; - -/** - * @param {Record} compartments - * @param {Sources} sources - * @param {Record} compartmentRenames - */ -const translateCompartmentMap = (compartments, sources, compartmentRenames) => { - const result = create(null); - for (const compartmentName of keys(compartmentRenames)) { - const compartment = compartments[compartmentName]; - const { name, label, retained: compartmentRetained, policy } = compartment; - if (compartmentRetained) { - // rename module compartments - /** @type {Record} */ - const modules = create(null); - const compartmentModules = compartment.modules; - if (compartment.modules) { - for (const name of keys(compartmentModules).sort()) { - const { retained: moduleRetained, ...retainedModule } = - compartmentModules[name]; - if (moduleRetained) { - if (retainedModule.compartment !== undefined) { - modules[name] = { - ...retainedModule, - compartment: compartmentRenames[retainedModule.compartment], - }; - } else { - modules[name] = retainedModule; - } - } - } - } - - // integrate sources into modules - const compartmentSources = sources[compartmentName]; - if (compartmentSources) { - for (const name of keys(compartmentSources).sort()) { - const source = compartmentSources[name]; - const { location, parser, exit, sha512, deferredError } = source; - if (location !== undefined) { - modules[name] = { - location, - parser, - sha512, - }; - } else if (exit !== undefined) { - modules[name] = { - exit, - }; - } else if (deferredError !== undefined) { - modules[name] = { - deferredError, - }; - } - } - } - - result[compartmentRenames[compartmentName]] = { - name, - label, - location: compartmentRenames[compartmentName], - modules, - policy, - // `scopes`, `types`, and `parsers` are not necessary since every - // loadable module is captured in `modules`. - }; - } - } - - return result; -}; - -/** - * @param {Sources} sources - * @param {Record} compartmentRenames - * @returns {Sources} - */ -const renameSources = (sources, compartmentRenames) => { - return fromEntries( - entries(sources).map(([name, compartmentSources]) => [ - compartmentRenames[name], - compartmentSources, - ]), - ); -}; +const { keys } = Object; /** * @param {ArchiveWriter} archive @@ -269,41 +111,23 @@ const captureSourceLocations = async (sources, captureSourceLocation) => { /** * @param {CompartmentMapDescriptor} compartmentMap * @param {Sources} sources - * @returns {{archiveCompartmentMap: CompartmentMapDescriptor, archiveSources: Sources}} + * @returns {ArchiveResult} */ export const makeArchiveCompartmentMap = (compartmentMap, sources) => { const { - compartments, - entry: { compartment: entryCompartmentName, module: entryModuleSpecifier }, - } = compartmentMap; - - const compartmentRenames = renameCompartments(compartments); - const archiveCompartments = translateCompartmentMap( - compartments, - sources, + compartmentMap: archiveCompartmentMap, + sources: archiveSources, + oldToNewCompartmentNames, + newToOldCompartmentNames, + compartmentRenames, + } = digestCompartmentMap(compartmentMap, sources); + return { + archiveCompartmentMap, + archiveSources, + oldToNewCompartmentNames, + newToOldCompartmentNames, compartmentRenames, - ); - const archiveEntryCompartmentName = compartmentRenames[entryCompartmentName]; - const archiveSources = renameSources(sources, compartmentRenames); - - const archiveCompartmentMap = { - // TODO migrate tags to conditions - // /~https://github.com/endojs/endo/issues/2388 - tags: [], - entry: { - compartment: archiveEntryCompartmentName, - module: entryModuleSpecifier, - }, - compartments: archiveCompartments, }; - - // Cross-check: - // We assert that we have constructed a valid compartment map, not because it - // might not be, but to ensure that the assertCompartmentMap function can - // accept all valid compartment maps. - assertCompartmentMap(archiveCompartmentMap); - - return { archiveCompartmentMap, archiveSources }; }; /** diff --git a/packages/compartment-mapper/src/capture-lite.js b/packages/compartment-mapper/src/capture-lite.js index 20da5b58b0..935e5cd461 100644 --- a/packages/compartment-mapper/src/capture-lite.js +++ b/packages/compartment-mapper/src/capture-lite.js @@ -33,20 +33,13 @@ * @import { * CaptureLiteOptions, * CaptureResult, - * CompartmentDescriptor, * CompartmentMapDescriptor, - * ModuleDescriptor, * ReadFn, * ReadPowers, * Sources, * } from './types.js' */ -import { - assertCompartmentMap, - pathCompare, - stringCompare, -} from './compartment-map.js'; import { exitModuleImportHookMaker, makeImportHookMaker, @@ -55,161 +48,12 @@ import { link } from './link.js'; import { resolve } from './node-module-specifier.js'; import { detectAttenuators } from './policy.js'; import { unpackReadPowers } from './powers.js'; +import { digestCompartmentMap } from './digest.js'; -const { freeze, assign, create, fromEntries, entries, keys } = Object; +const { freeze, assign, create } = Object; const defaultCompartment = Compartment; -/** - * We attempt to produce compartment maps that are consistent regardless of - * whether the packages were originally laid out on disk for development or - * production, and other trivia like the fully qualified path of a specific - * installation. - * - * Naming compartments for the self-ascribed name and version of each Node.js - * package is insufficient because they are not guaranteed to be unique. - * Dependencies do not necessarilly come from the npm registry and may be - * for example derived from fully qualified URL's or Github org and project - * names. - * Package managers are also not required to fully deduplicate the hard - * copy of each package even when they are identical resources. - * Duplication is undesirable, but we elect to defer that problem to solutions - * in the package managers, as the alternative would be to consistently hash - * the original sources of the packages themselves, which may not even be - * available much less pristine for us. - * - * So, instead, we use the lexically least path of dependency names, delimited - * by hashes. - * The compartment maps generated by the ./node-modules.js tooling pre-compute - * these traces for our use here. - * We sort the compartments lexically on their self-ascribed name and version, - * and use the lexically least dependency name path as a tie-breaker. - * The dependency path is logical and orthogonal to the package manager's - * actual installation location, so should be orthogonal to the vagaries of the - * package manager's deduplication algorithm. - * - * @param {Record} compartments - * @returns {Record} map from old to new compartment names. - */ -const renameCompartments = compartments => { - /** @type {Record} */ - const compartmentRenames = create(null); - let index = 0; - let prev = ''; - - // The sort below combines two comparators to avoid depending on sort - // stability, which became standard as recently as 2019. - // If that date seems quaint, please accept my regards from the distant past. - // We are very proud of you. - const compartmentsByPath = Object.entries(compartments) - .map(([name, compartment]) => ({ - name, - path: compartment.path, - label: compartment.label, - })) - .sort((a, b) => { - if (a.label === b.label) { - assert(a.path !== undefined && b.path !== undefined); - return pathCompare(a.path, b.path); - } - return stringCompare(a.label, b.label); - }); - - for (const { name, label } of compartmentsByPath) { - if (label === prev) { - compartmentRenames[name] = `${label}-n${index}`; - index += 1; - } else { - compartmentRenames[name] = label; - prev = label; - index = 1; - } - } - return compartmentRenames; -}; - -/** - * @param {Record} compartments - * @param {Sources} sources - * @param {Record} compartmentRenames - */ -const translateCompartmentMap = (compartments, sources, compartmentRenames) => { - const result = create(null); - for (const compartmentName of keys(compartmentRenames)) { - const compartment = compartments[compartmentName]; - const { name, label, retained, policy } = compartment; - if (retained) { - // rename module compartments - /** @type {Record} */ - const modules = create(null); - const compartmentModules = compartment.modules; - if (compartment.modules) { - for (const name of keys(compartmentModules).sort()) { - const module = compartmentModules[name]; - if (module.compartment !== undefined) { - modules[name] = { - ...module, - compartment: compartmentRenames[module.compartment], - }; - } else { - modules[name] = module; - } - } - } - - // integrate sources into modules - const compartmentSources = sources[compartmentName]; - if (compartmentSources) { - for (const name of keys(compartmentSources).sort()) { - const source = compartmentSources[name]; - const { location, parser, exit, sha512, deferredError } = source; - if (location !== undefined) { - modules[name] = { - location, - parser, - sha512, - }; - } else if (exit !== undefined) { - modules[name] = { - exit, - }; - } else if (deferredError !== undefined) { - modules[name] = { - deferredError, - }; - } - } - } - - result[compartmentRenames[compartmentName]] = { - name, - label, - location: compartmentRenames[compartmentName], - modules, - policy, - // `scopes`, `types`, and `parsers` are not necessary since every - // loadable module is captured in `modules`. - }; - } - } - - return result; -}; - -/** - * @param {Sources} sources - * @param {Record} compartmentRenames - * @returns {Sources} - */ -const renameSources = (sources, compartmentRenames) => { - return fromEntries( - entries(sources).map(([name, compartmentSources]) => [ - compartmentRenames[name], - compartmentSources, - ]), - ); -}; - /** * @param {CompartmentMapDescriptor} compartmentMap * @param {Sources} sources @@ -217,40 +61,18 @@ const renameSources = (sources, compartmentRenames) => { */ const captureCompartmentMap = (compartmentMap, sources) => { const { - compartments, - entry: { compartment: entryCompartmentName, module: entryModuleSpecifier }, - } = compartmentMap; - - const compartmentRenames = renameCompartments(compartments); - const captureCompartments = translateCompartmentMap( - compartments, - sources, + compartmentMap: captureCompartmentMap, + sources: captureSources, + newToOldCompartmentNames, compartmentRenames, - ); - const captureEntryCompartmentName = compartmentRenames[entryCompartmentName]; - const captureSources = renameSources(sources, compartmentRenames); - - const captureCompartmentMap = { - // TODO graceful migration from tags to conditions - // /~https://github.com/endojs/endo/issues/2388 - tags: [], - entry: { - compartment: captureEntryCompartmentName, - module: entryModuleSpecifier, - }, - compartments: captureCompartments, - }; - - // Cross-check: - // We assert that we have constructed a valid compartment map, not because it - // might not be, but to ensure that the assertCompartmentMap function can - // accept all valid compartment maps. - assertCompartmentMap(captureCompartmentMap); - + oldToNewCompartmentNames, + } = digestCompartmentMap(compartmentMap, sources); return { captureCompartmentMap, captureSources, compartmentRenames, + newToOldCompartmentNames, + oldToNewCompartmentNames, }; }; diff --git a/packages/compartment-mapper/src/digest.js b/packages/compartment-mapper/src/digest.js new file mode 100644 index 0000000000..63f1a7f1b3 --- /dev/null +++ b/packages/compartment-mapper/src/digest.js @@ -0,0 +1,235 @@ +/* eslint-disable no-shadow */ +/** + * Provides {@link digestCompartmentMap} which creates a digest of a compartment + * map suitable for archival or further inspection. + * + * @module + */ + +/** + * @import { + * CompartmentDescriptor, + * CompartmentMapDescriptor, + * DigestResult, + * ModuleDescriptor, + * Sources, + * } from './types.js' + */ + +import { + assertCompartmentMap, + pathCompare, + stringCompare, +} from './compartment-map.js'; + +const { create, fromEntries, entries, keys } = Object; + +/** + * We attempt to produce compartment maps that are consistent regardless of + * whether the packages were originally laid out on disk for development or + * production, and other trivia like the fully qualified path of a specific + * installation. + * + * Naming compartments for the self-ascribed name and version of each Node.js + * package is insufficient because they are not guaranteed to be unique. + * Dependencies do not necessarilly come from the npm registry and may be + * for example derived from fully qualified URL's or Github org and project + * names. + * Package managers are also not required to fully deduplicate the hard + * copy of each package even when they are identical resources. + * Duplication is undesirable, but we elect to defer that problem to solutions + * in the package managers, as the alternative would be to consistently hash + * the original sources of the packages themselves, which may not even be + * available much less pristine for us. + * + * So, instead, we use the lexically least path of dependency names, delimited + * by hashes. + * The compartment maps generated by the ./node-modules.js tooling pre-compute + * these traces for our use here. + * We sort the compartments lexically on their self-ascribed name and version, + * and use the lexically least dependency name path as a tie-breaker. + * The dependency path is logical and orthogonal to the package manager's + * actual installation location, so should be orthogonal to the vagaries of the + * package manager's deduplication algorithm. + * + * @param {Record} compartments + * @returns {Record} map from old to new compartment names. + */ +const renameCompartments = compartments => { + /** @type {Record} */ + const compartmentRenames = create(null); + let index = 0; + let prev = ''; + + // The sort below combines two comparators to avoid depending on sort + // stability, which became standard as recently as 2019. + // If that date seems quaint, please accept my regards from the distant past. + // We are very proud of you. + const compartmentsByPath = Object.entries(compartments) + .map(([name, compartment]) => ({ + name, + path: compartment.path, + label: compartment.label, + })) + .sort((a, b) => { + if (a.label === b.label) { + assert(a.path !== undefined && b.path !== undefined); + return pathCompare(a.path, b.path); + } + return stringCompare(a.label, b.label); + }); + + for (const { name, label } of compartmentsByPath) { + if (label === prev) { + compartmentRenames[name] = `${label}-n${index}`; + index += 1; + } else { + compartmentRenames[name] = label; + prev = label; + index = 1; + } + } + return compartmentRenames; +}; + +/** + * @param {Record} compartments + * @param {Sources} sources + * @param {Record} compartmentRenames + */ +const translateCompartmentMap = (compartments, sources, compartmentRenames) => { + const result = create(null); + for (const compartmentName of keys(compartmentRenames)) { + const compartment = compartments[compartmentName]; + const { name, label, retained: compartmentRetained, policy } = compartment; + if (compartmentRetained) { + // rename module compartments + /** @type {Record} */ + const modules = create(null); + const compartmentModules = compartment.modules; + if (compartment.modules) { + for (const name of keys(compartmentModules).sort()) { + const { retained: moduleRetained, ...retainedModule } = + compartmentModules[name]; + if (moduleRetained) { + if (retainedModule.compartment !== undefined) { + modules[name] = { + ...retainedModule, + compartment: compartmentRenames[retainedModule.compartment], + }; + } else { + modules[name] = retainedModule; + } + } + } + } + + // integrate sources into modules + const compartmentSources = sources[compartmentName]; + if (compartmentSources) { + for (const name of keys(compartmentSources).sort()) { + const source = compartmentSources[name]; + const { location, parser, exit, sha512, deferredError } = source; + if (location !== undefined) { + modules[name] = { + location, + parser, + sha512, + }; + } else if (exit !== undefined) { + modules[name] = { + exit, + }; + } else if (deferredError !== undefined) { + modules[name] = { + deferredError, + }; + } + } + } + + result[compartmentRenames[compartmentName]] = { + name, + label, + location: compartmentRenames[compartmentName], + modules, + policy, + // `scopes`, `types`, and `parsers` are not necessary since every + // loadable module is captured in `modules`. + }; + } + } + + return result; +}; + +/** + * @param {Sources} sources + * @param {Record} compartmentRenames + * @returns {Sources} + */ +const renameSources = (sources, compartmentRenames) => { + return fromEntries( + entries(sources).map(([name, compartmentSources]) => [ + compartmentRenames[name], + compartmentSources, + ]), + ); +}; + +/** + * @param {CompartmentMapDescriptor} compartmentMap + * @param {Sources} sources + * @returns {DigestResult} + */ +export const digestCompartmentMap = (compartmentMap, sources) => { + const { + compartments, + entry: { compartment: entryCompartmentName, module: entryModuleSpecifier }, + } = compartmentMap; + + const oldToNewCompartmentNames = renameCompartments(compartments); + const digestCompartments = translateCompartmentMap( + compartments, + sources, + oldToNewCompartmentNames, + ); + const digestEntryCompartmentName = + oldToNewCompartmentNames[entryCompartmentName]; + const digestSources = renameSources(sources, oldToNewCompartmentNames); + + const digestCompartmentMap = { + // TODO graceful migration from tags to conditions + // /~https://github.com/endojs/endo/issues/2388 + tags: [], + entry: { + compartment: digestEntryCompartmentName, + module: entryModuleSpecifier, + }, + compartments: digestCompartments, + }; + + // Cross-check: + // We assert that we have constructed a valid compartment map, not because it + // might not be, but to ensure that the assertCompartmentMap function can + // accept all valid compartment maps. + assertCompartmentMap(digestCompartmentMap); + + const newToOldCompartmentNames = fromEntries( + entries(oldToNewCompartmentNames).map(([oldName, newName]) => [ + newName, + oldName, + ]), + ); + + /** @type {DigestResult} */ + const digestResult = { + compartmentMap: digestCompartmentMap, + sources: digestSources, + oldToNewCompartmentNames, + newToOldCompartmentNames, + compartmentRenames: newToOldCompartmentNames, + }; + + return digestResult; +}; diff --git a/packages/compartment-mapper/src/types/external.ts b/packages/compartment-mapper/src/types/external.ts index c4568d11e3..9b87f3e325 100644 --- a/packages/compartment-mapper/src/types/external.ts +++ b/packages/compartment-mapper/src/types/external.ts @@ -230,12 +230,52 @@ type LinkingOptions = ParserForLanguageOption & // //////////////////////////////////////////////////////////////////////////////// /** - * The result of `captureFromMap`. + * Result of `digestCompartmentMap()` */ -export type CaptureResult = { - captureCompartmentMap: CompartmentMapDescriptor; - captureSources: Sources; +export interface DigestResult { + /** + * Normalized `CompartmentMapDescriptor` + */ + compartmentMap: CompartmentMapDescriptor; + + /** + * Sources found in the `CompartmentMapDescriptor` + */ + sources: Sources; + + /** + * A record of renamed {@link CompartmentDescriptor CompartmentDescriptors} + * from _new_ to _original_ name + */ + newToOldCompartmentNames: Record; + + /** + * A record of renamed {@link CompartmentDescriptor CompartmentDescriptors} + * from _original_ to _new_ name + */ + oldToNewCompartmentNames: Record; + + /** + * Alias for `newToOldCompartmentNames` + * @deprecated Use {@link newToOldCompartmentNames} instead. + */ compartmentRenames: Record; +} + +/** + * The result of `captureFromMap`. + */ +export type CaptureResult = Omit & { + captureCompartmentMap: DigestResult['compartmentMap']; + captureSources: DigestResult['sources']; +}; + +/** + * The result of `makeArchiveCompartmentMap` + */ +export type ArchiveResult = Omit & { + archiveCompartmentMap: DigestResult['compartmentMap']; + archiveSources: DigestResult['sources']; }; /** diff --git a/packages/compartment-mapper/test/capture-lite.test.js b/packages/compartment-mapper/test/capture-lite.test.js index 75d4180e83..90a8543c0f 100644 --- a/packages/compartment-mapper/test/capture-lite.test.js +++ b/packages/compartment-mapper/test/capture-lite.test.js @@ -8,7 +8,7 @@ import { mapNodeModules } from '../src/node-modules.js'; import { makeReadPowers } from '../src/node-powers.js'; import { defaultParserForLanguage } from '../src/import-parsers.js'; -const { keys, entries, fromEntries } = Object; +const { keys } = Object; test('captureFromMap() should resolve with a CaptureResult', async t => { t.plan(5); @@ -26,10 +26,6 @@ test('captureFromMap() should resolve with a CaptureResult', async t => { parserForLanguage: defaultParserForLanguage, }); - const renames = fromEntries( - entries(compartmentRenames).map(([filepath, id]) => [id, filepath]), - ); - t.deepEqual( keys(captureSources).sort(), ['bundle', 'bundle-dep-v0.0.0'], @@ -37,7 +33,7 @@ test('captureFromMap() should resolve with a CaptureResult', async t => { ); t.deepEqual( - keys(renames).sort(), + keys(compartmentRenames).sort(), ['bundle', 'bundle-dep-v0.0.0'], 'compartmentRenames must contain same compartment names as in captureCompartmentMap', ); @@ -82,14 +78,11 @@ test('captureFromMap() should round-trip sources based on parsers', async t => { }, ); - const renames = fromEntries( - entries(compartmentRenames).map(([filepath, id]) => [id, filepath]), - ); const decoder = new TextDecoder(); // the actual source depends on the value of `parserForLanguage` above const actual = decoder.decode(captureSources.bundle['./icando.cjs'].bytes); const expected = await fs.promises.readFile( - path.join(url.fileURLToPath(renames.bundle), 'icando.cjs'), + path.join(url.fileURLToPath(compartmentRenames.bundle), 'icando.cjs'), 'utf-8', ); t.is(actual, expected, 'Source code should not be pre-compiled');