Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion constraints.pro
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,6 @@ gen_enforced_field(WorkspaceCwd, 'scripts.changelog:update', CorrectChangelogUpd

% All non-root packages must have the same "test" script.
gen_enforced_field(WorkspaceCwd, 'scripts.test', 'vitest run --config vitest.config.ts') :-
WorkspaceCwd \= 'packages/shims',
WorkspaceCwd \= 'packages/test-utils',
WorkspaceCwd \= '.'.

Expand Down
2 changes: 1 addition & 1 deletion packages/extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable import-x/no-unassigned-import */
import './dev-console.js';
import './endoify.mjs';
import './endoify.js';
/* eslint-enable import-x/no-unassigned-import */

import type { ExtensionMessage } from './shared.js';
Expand Down
47 changes: 47 additions & 0 deletions packages/extension/src/endoify.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import './endoify.js';
// eslint-disable-next-line n/no-extraneous-import
import type { HandledPromiseConstructor } from '@endo/eventual-send';
import { describe, expect, it } from 'vitest';

describe('endoified', () => {
it('calls lockdown', () => {
expect(Object.isFrozen(Array.prototype)).toBe(true); // Due to `lockdown()`, and therefore `ses`
});

it('loads eventual-send', () => {
expect(typeof HandledPromise).not.toBe('undefined'); // Due to eventual send
});
});

describe(`endoify`, () => {
const assertions = [
(): boolean => typeof globalThis === 'object',
(): boolean => typeof lockdown === 'function',
(): boolean => typeof repairIntrinsics === 'function',
(): boolean => typeof Compartment === 'function',
(): boolean => typeof assert === 'function',
(): boolean => typeof HandledPromise === 'function',
(): boolean => typeof harden === 'function',
(): boolean => typeof getStackString === 'function',
(): boolean => {
try {
return !Object.assign(harden({ a: 1 }), { b: 2 });
} catch {
return true;
}
},
];

for (const assertion of assertions) {
it(`asserts ${String(assertion).replace(/^.*?=>\s*/u, '')}`, () => {
expect(assertion()).toBe(true);
});
}
});

declare global {
// eslint-disable-next-line no-var
var getStackString: (error: Error) => string;
// eslint-disable-next-line no-var
var HandledPromise: HandledPromiseConstructor;
}
3 changes: 3 additions & 0 deletions packages/extension/src/endoify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* eslint-disable import-x/extensions */
/* eslint-disable import-x/no-unassigned-import */
import '@ocap/shims/endoify';
1 change: 1 addition & 0 deletions packages/extension/src/iframe-manager.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './endoify.js';
import * as snapsUtils from '@metamask/snaps-utils';
import { delay, makePromiseKitMock } from '@ocap/test-utils';
import { vi, describe, it, expect } from 'vitest';
Expand Down
1 change: 1 addition & 0 deletions packages/extension/src/shared.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './endoify.js';
import { delay } from '@ocap/test-utils';
import { vi, describe, it, expect } from 'vitest';

Expand Down
35 changes: 23 additions & 12 deletions packages/extension/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ const projectRoot = './src';
* Module specifiers that will be ignored by Rollup if imported, and therefore
* not transformed. **Only applies to JavaScript and TypeScript files.**
*/
const externalModules: readonly string[] = [
'./dev-console.js',
'./endoify.mjs',
];
const externalModules: readonly string[] = ['./dev-console.js', './endoify.js'];

/**
* Files that need to be statically copied to the destination directory.
Expand All @@ -28,10 +25,7 @@ const staticCopyTargets: readonly string[] = [
'manifest.json',
// External modules
'dev-console.js',
'../../shims/dist/endoify.mjs',
// Dependencies of external modules
'../../shims/dist/eventual-send.mjs',
'../../../node_modules/ses/dist/ses.mjs',
'../../shims/dist/endoify.js',
];

// https://vitejs.dev/config/
Expand All @@ -56,6 +50,15 @@ export default defineConfig({
},
},

resolve: {
alias: [
{
find: '@ocap/shims/endoify',
replacement: './endoify.js',
},
],
},

plugins: [
endoifyHtmlFilesPlugin(),
viteStaticCopy({
Expand All @@ -73,25 +76,33 @@ export default defineConfig({
* @returns The Vite plugin.
*/
function endoifyHtmlFilesPlugin(): Plugin {
const endoifyElement = '<script src="endoify.mjs" type="module"></script>';
const endoifyElement = '<script src="endoify.js" type="module"></script>';

return {
name: 'externalize-plugin',
async transformIndexHtml(htmlString): Promise<string> {
if (htmlString.includes('endoify.mjs')) {
const htmlDoc = loadHtml(htmlString);

if (htmlDoc('script[src="endoify.ts"]').length > 0) {
throw new Error(
`HTML document should not reference "endoify.ts" directly:\n${htmlString}`,
);
}

if (htmlDoc('script[src="endoify.js"]').length > 0) {
throw new Error(
`HTML document already references endoify script:\n${htmlString}`,
);
}

const htmlDoc = loadHtml(htmlString);
if (htmlDoc('head').length !== 1 || htmlDoc('head script').length < 1) {
if (htmlDoc('head').length !== 1 || htmlDoc('head > script').length < 1) {
throw new Error(
`Expected HTML document with a single <head> containing at least one <script>. Received:\n${htmlString}`,
);
}

htmlDoc(endoifyElement).insertBefore('head:first script:first');

return await prettierFormat(htmlDoc.html(), {
parser: 'html',
tabWidth: 2,
Expand Down
20 changes: 15 additions & 5 deletions packages/extension/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
// eslint-disable-next-line spaced-comment
/// <reference types="vitest" />

import path from 'path';
import { defineConfig, mergeConfig } from 'vitest/config';

import viteConfig from './vite.config.js';
import { getDefaultConfig } from '../../vitest.config.packages.js';

const defaultConfig = getDefaultConfig();
// @ts-expect-error We can and will delete this.
delete defaultConfig.test.coverage.thresholds;

export default mergeConfig(
const config = mergeConfig(
viteConfig,
mergeConfig(
defaultConfig,
defineConfig({
test: {
setupFiles: '../test-utils/src/env/mock-endo.ts',
pool: 'vmThreads',
alias: [
{
find: '@ocap/shims/endoify',
replacement: path.resolve('../shims/src/endoify.js'),
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
customResolver: (id) => ({ external: true, id }),
},
],
},
}),
),
);

delete config.test.coverage.thresholds;

export default config;
2 changes: 1 addition & 1 deletion packages/shims/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = {

overrides: [
{
files: ['src/**/*.mjs'],
files: ['src/**/*.js'],
globals: { lockdown: 'readonly' },
rules: {
'import-x/extensions': 'off',
Expand Down
7 changes: 3 additions & 4 deletions packages/shims/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
"sideEffects": false,
"type": "module",
"exports": {
"./apply-lockdown": "./dist/apply-lockdown.mjs",
"./endoify": "./dist/endoify.mjs",
"./eventual-send": "./dist/eventual-send.mjs",
"./endoify": "./dist/endoify.js",
"./package.json": "./package.json"
},
"main": "./dist/index.cjs",
Expand All @@ -26,7 +24,7 @@
"changelog:validate": "../../scripts/validate-changelog.sh @ocap/shims",
"clean": "rimraf --glob ./dist './*.tsbuildinfo'",
"publish:preview": "yarn npm publish --tag preview",
"test": "yarn build && vitest run --config vitest.config.ts --passWithNoTests",
"test": "vitest run --config vitest.config.ts",
"test:clean": "yarn test --no-cache --coverage.clean",
"test:dev": "yarn test --coverage false",
"test:verbose": "yarn test --reporter verbose",
Expand All @@ -42,6 +40,7 @@
"@metamask/auto-changelog": "^3.4.4",
"deepmerge": "^4.3.1",
"rimraf": "^6.0.1",
"typescript": "~5.5.4",
"vite": "^5.3.5",
"vitest": "^2.0.5"
},
Expand Down
68 changes: 29 additions & 39 deletions packages/shims/scripts/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,37 @@ import 'ses';
import '@endo/lockdown/commit.js';

import bundleSource from '@endo/bundle-source';
import { createReadStream, createWriteStream } from 'fs';
import { mkdir } from 'fs/promises';
import path from 'path';
import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { rimraf } from 'rimraf';
import { Readable } from 'stream';
import { fileURLToPath } from 'url';

import endoScriptIdentifierTransformPlugin from './helpers/rollup-plugin-endo-script-identifier-transform.js';

console.log('Bundling shims...');

const rootDir = fileURLToPath(new URL('..', import.meta.url));
const src = path.resolve(rootDir, 'src');
const dist = path.resolve(rootDir, 'dist');

await mkdir(dist, { recursive: true });
await rimraf(`${dist}/*`, { glob: true });

/**
* Bundles the target file as endoScript and returns the content as a readable stream.
*
* @param {string} specifier - Import path to the file to bundle, e.g. `'@endo/eventual-send/shim.js'`.
* @returns {Promise<Readable>} The bundled file contents as a Readable stream.
*/
const createEndoBundleReadStream = async (specifier) => {
const filePath = fileURLToPath(import.meta.resolve(specifier));
const { source: bundle } = await bundleSource(filePath, {
format: 'endoScript',
});
return Readable.from(bundle);
};

const sources = {
ses: createReadStream(
path.resolve(rootDir, '../../node_modules/ses/dist/ses.mjs'),
),
eventualSend: await createEndoBundleReadStream('@endo/eventual-send/shim.js'),
shim: createReadStream(path.resolve(src, 'endoify.mjs')),
};

const target = createWriteStream(path.resolve(dist, 'endoify.mjs'));

sources.ses.pipe(target, { end: false });
sources.ses.on('end', () => sources.eventualSend.pipe(target, { end: false }));
sources.eventualSend.on('end', () => sources.shim.pipe(target, { end: true }));
sources.shim.on('end', () => console.log('Success!'));
const srcDir = path.resolve(rootDir, 'src');
const distDir = path.resolve(rootDir, 'dist');

await mkdir(distDir, { recursive: true });
await rimraf(`${distDir}/*`, { glob: true });

for (const [name, specifier] of Object.entries({
endoify: path.resolve(srcDir, 'endoify.js'),
})) {
const outputPath = path.resolve(distDir, `${name}.js`);
const sourcePath = fileURLToPath(import.meta.resolve(specifier));

let { source } = await bundleSource(sourcePath, { format: 'endoScript' });

if (!process.argv.includes('--without-rollup-transform')) {
source = endoScriptIdentifierTransformPlugin({
scopedRoot: path.resolve(rootDir, '../..'),
}).transform(source, specifier).code;
}
Comment on lines +29 to +33
Copy link
Contributor Author

@SMotaal SMotaal Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be better to opt into the transform than to opt out.

Suggested change
if (!process.argv.includes('--without-rollup-transform')) {
source = endoScriptIdentifierTransformPlugin({
scopedRoot: path.resolve(rootDir, '../..'),
}).transform(source, specifier).code;
}
if (process.argv.includes('--with-rollup-transform')) {
source = endoScriptIdentifierTransformPlugin({
scopedRoot: path.resolve(rootDir, '../..'),
}).transform(source, specifier).code;
}

I'm in favour of keeping it in for now. The transform is deliberate, speedy and includes opt-in options for validation and debugging for if and when they would be needed.

Some of the cons is that this is still an extra step, and that it uses regular expressions instead of AST to rewrite the bundled sources, which I am arguing against myself.

Ideally, we could upstream the replacements to @endojs/endo and avoiding needing this step all together.

Either way, the flag needs to be renamed to --without-rollup-parser-optimizations or --with-rollup-parser-optimizations or something more reflective of what is actually taking place 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I drafted endojs/endo#2436 today to see if this can be upstreamed!


await writeFile(outputPath, source);
}

console.log('Success!');
Loading