diff --git a/packages/endo/bin/endo b/packages/endo/bin/endo new file mode 120000 index 0000000000..8237050ebd --- /dev/null +++ b/packages/endo/bin/endo @@ -0,0 +1 @@ +endo.js \ No newline at end of file diff --git a/packages/endo/bin/endo.js b/packages/endo/bin/endo.js new file mode 100755 index 0000000000..da980e130d --- /dev/null +++ b/packages/endo/bin/endo.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node +import fs from "fs"; +import { main } from "../src/cli.js"; + +main(process, { fs: fs.promises }); diff --git a/packages/endo/package.json b/packages/endo/package.json index 04ca0a83b3..8e8505343a 100644 --- a/packages/endo/package.json +++ b/packages/endo/package.json @@ -14,6 +14,9 @@ "require": "./dist/endo.cjs", "browser": "./dist/endo.umd.js" }, + "bin": { + "endo": "./bin/endo.js" + }, "scripts": { "build": "rollup --config rollup.config.js", "clean": "rm -rf dist", diff --git a/packages/endo/src/cli.js b/packages/endo/src/cli.js new file mode 100644 index 0000000000..868546f3a2 --- /dev/null +++ b/packages/endo/src/cli.js @@ -0,0 +1,110 @@ +/* eslint no-shadow: [0] */ +import "./lockdown.js"; +import { writeArchive } from "./main.js"; +import { search } from "./search.js"; +import { compartmentMapForNodeModules } from "./compartmap.js"; + +function usage(message) { + console.error(message); + return 1; +} + +async function noEntryUsage() { + return usage(`expected path to program`); +} + +async function noArchiveUsage() { + return usage(`expected path for archive`); +} + +async function subcommand([arg, ...rest], handlers) { + const keys = Object.keys(handlers); + if (arg === undefined || !keys.includes(arg)) { + return usage(`expected one of ${keys.join(", ")}`); + } + return handlers[arg](rest); +} + +async function parameter(args, handle, usage) { + const [arg, ...rest] = args; + if (arg === undefined) { + return usage(`expected an argument`); + } + if (arg.startsWith("-")) { + return usage(`unexpected flag: ${arg}`); + } + return handle(arg, rest); +} + +async function run(args, { cwd, read, write, stdout }) { + async function compartmap(args) { + async function handleEntry(applicationPath, args) { + if (args.length) { + return usage(`unexpected arguments: ${JSON.stringify(args)}`); + } + const currentLocation = new URL(`${cwd()}/`, "file:///"); + const applicationLocation = new URL(applicationPath, currentLocation); + const { packageLocation } = await search(read, applicationLocation); + const compartmentMap = await compartmentMapForNodeModules( + read, + packageLocation + ); + stdout.write(`${JSON.stringify(compartmentMap, null, 2)}\n`); + return 0; + } + return parameter(args, handleEntry, noEntryUsage); + } + + async function archive(args) { + async function handleArchive(archivePath, args) { + async function handleEntry(applicationPath, args) { + if (args.length) { + return usage(`unexpected arguments: ${JSON.stringify(args)}`); + } + const currentLocation = new URL(`${cwd()}/`, "file:///"); + const archiveLocation = new URL(archivePath, currentLocation); + const applicationLocation = new URL(applicationPath, currentLocation); + await writeArchive(write, read, archiveLocation, applicationLocation); + return 0; + } + return parameter(args, handleEntry, noEntryUsage); + } + return parameter(args, handleArchive, noArchiveUsage); + } + + return subcommand(args, { compartmap, archive }); +} + +export async function main(process, modules) { + const { fs } = modules; + const { cwd, stdout } = process; + + // Filesystem errors often don't have stacks: + + async function read(location) { + try { + return await fs.readFile(new URL(location).pathname); + } catch (error) { + throw new Error(error.message); + } + } + + async function write(location, content) { + try { + return await fs.writeFile(new URL(location).pathname, content); + } catch (error) { + throw new Error(error.message); + } + } + + try { + process.exitCode = await run(process.argv.slice(2), { + read, + write, + cwd, + stdout + }); + } catch (error) { + process.exitCode = usage(error.stack || error.message); + } +} diff --git a/packages/endo/src/compartmap.js b/packages/endo/src/compartmap.js index 01f435a175..6b5f9afc4f 100644 --- a/packages/endo/src/compartmap.js +++ b/packages/endo/src/compartmap.js @@ -102,7 +102,7 @@ const inferParsers = (type, location) => { // that the package exports. const graphPackage = async ( - name, + name = "", readDescriptor, graph, { packageLocation, packageDescriptor }, diff --git a/packages/endo/src/import-archive.js b/packages/endo/src/import-archive.js index bf3957c1fd..b6c9f501c8 100644 --- a/packages/endo/src/import-archive.js +++ b/packages/endo/src/import-archive.js @@ -22,8 +22,8 @@ const makeArchiveImportHookMaker = archive => { return makeImportHook; }; -export const parseArchive = async archiveBytes => { - const archive = await readZip(archiveBytes); +export const parseArchive = async (archiveBytes, archiveLocation) => { + const archive = await readZip(archiveBytes, archiveLocation); const compartmentMapBytes = await archive.read("compartmap.json"); const compartmentMapText = decoder.decode(compartmentMapBytes); @@ -47,12 +47,17 @@ export const parseArchive = async archiveBytes => { return { execute }; }; -export const loadArchive = async (read, archivePath) => { - const archiveBytes = await read(archivePath); - return parseArchive(archiveBytes); +export const loadArchive = async (read, archiveLocation) => { + const archiveBytes = await read(archiveLocation); + return parseArchive(archiveBytes, archiveLocation); }; -export const importArchive = async (read, archivePath, endowments, modules) => { - const archive = await loadArchive(read, archivePath); +export const importArchive = async ( + read, + archiveLocation, + endowments, + modules +) => { + const archive = await loadArchive(read, archiveLocation); return archive.execute(endowments, modules); }; diff --git a/packages/endo/src/zip.js b/packages/endo/src/zip.js index fddc3cc275..022e8d23f5 100644 --- a/packages/endo/src/zip.js +++ b/packages/endo/src/zip.js @@ -2,10 +2,18 @@ import JSZip from "jszip"; -export const readZip = async data => { +export const readZip = async (data, location) => { const zip = new JSZip(); await zip.loadAsync(data); - const read = async path => zip.file(path).async("uint8array"); + const read = async path => { + const file = zip.file(path); + if (file === undefined) { + throw new Error( + `Cannot find file to read ${path} in archive ${location}` + ); + } + return file.async("uint8array"); + }; return { read }; };