From b299916a67d7ab2583d98dfbe663b4d01c536e44 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Thu, 30 Jul 2020 19:18:34 -0700 Subject: [PATCH 1/8] feat(endo): Add command line --- packages/endo/bin/endo | 1 + packages/endo/bin/endo.js | 5 ++ packages/endo/src/cli.js | 130 ++++++++++++++++++++++++++++ packages/endo/src/compartmap.js | 2 +- packages/endo/src/import-archive.js | 19 ++-- packages/endo/src/zip.js | 12 ++- 6 files changed, 159 insertions(+), 10 deletions(-) create mode 120000 packages/endo/bin/endo create mode 100755 packages/endo/bin/endo.js create mode 100644 packages/endo/src/cli.js 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/src/cli.js b/packages/endo/src/cli.js new file mode 100644 index 0000000000..327bbc47e8 --- /dev/null +++ b/packages/endo/src/cli.js @@ -0,0 +1,130 @@ +/* global harden */ + +import "./lockdown.js"; +import { loadLocation, writeArchive, loadArchive } from "./main.js"; +import { search } from "./search.js"; +import { compartmentMapForNodeModules } from "./compartmap.js"; + +const iterate = sequence => sequence[Symbol.iterator](); + +function usage(message) { + console.error(message); + return 1; +} + +async function execute(application) { + const endowments = harden({ + console: { + log(...args) { + console.log(...args); + } + } + }); + + await application.execute(endowments); + return 0; +} + +async function executeLocation(applicationPath, { cwd, read }) { + const currentLocation = new URL(`${cwd()}/`, "file:///"); + const applicationLocation = new URL(applicationPath, currentLocation); + const application = await loadLocation(read, applicationLocation); + return execute(application); +} + +async function archive(archivePath, applicationPath, { cwd, read, write }) { + 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; +} + +async function executeArchive(archivePath, { read, cwd }) { + const currentLocation = new URL(`${cwd()}/`, "file:///"); + const archiveLocation = new URL(archivePath, currentLocation); + const application = await loadArchive(read, archiveLocation); + return execute(application); +} + +async function compartmap(applicationPath, { read, cwd }) { + const currentLocation = new URL(`${cwd()}/`, "file:///"); + const applicationLocation = new URL(applicationPath, currentLocation); + const { packageLocation } = await search(read, applicationLocation); + const compartmentMap = await compartmentMapForNodeModules( + read, + packageLocation + ); + console.log(JSON.stringify(compartmentMap, null, 2)); + return 0; +} + +// run parses command line arguments and dispatches to a subcommand. +async function run(args, powers) { + for (const arg of args) { + if (arg === "--") { + for (const applicationLocation of args) { + return executeLocation(applicationLocation, powers); + } + } else if (arg === "--compartmap") { + for (const applicationPath of args) { + const rem = Array.from(args); + if (rem.length > 0) { + return usage( + `Unexpected arguments after --compartmap ${applicationPath}: ${rem}` + ); + } + return compartmap(applicationPath, powers); + } + } else if (arg === "-w") { + for (const archivePath of args) { + for (const applicationPath of args) { + const rem = Array.from(args); + if (rem.length > 0) { + return usage( + `Unexpected arguments after --archive ${archivePath} ${applicationPath}: ${rem}` + ); + } + return archive(archivePath, applicationPath, powers); + } + return usage(`Expected application path`); + } + return usage(`Expected archive path`); + } else if (arg === "-x") { + for (const archivePath of args) { + const rem = Array.from(args); + if (rem.length > 0) { + return usage(`Unexpected arguments after -x ${archivePath}: ${rem}`); + } + return executeArchive(archivePath, powers); + } + return usage(`Expected archive path`); + } else if (arg.startsWith("-")) { + return usage(`Unrecognized flag ${arg}`); + } else { + const rem = Array.from(args); + if (rem.length > 0) { + return usage(`Unexpected arguments after ${arg}: ${rem}`); + } + return executeLocation(arg, powers); + } + } + return usage(`Expected script path or flag`); +} + +export async function main(process, modules) { + const { fs } = modules; + const read = async location => fs.readFile(new URL(location).pathname); + const write = async (location, content) => + fs.writeFile(new URL(location).pathname, content); + try { + const args = iterate(process.argv.slice(2)); + process.exitCode = await run(args, { + read, + write, + cwd: process.cwd + }); + } 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 }; }; From 84260ed7345aa709d42b4e64311bf050abcae4d5 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Fri, 31 Jul 2020 20:09:56 -0700 Subject: [PATCH 2/8] Add endo bin --- packages/endo/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/endo/package.json b/packages/endo/package.json index 04ca0a83b3..f40889199d 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" + }, "scripts": { "build": "rollup --config rollup.config.js", "clean": "rm -rf dist", From ae1fd500f108e82b07d79d41c54b314f25334b75 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Fri, 31 Jul 2020 20:12:41 -0700 Subject: [PATCH 3/8] Use stdout for compartmap --- packages/endo/src/cli.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/endo/src/cli.js b/packages/endo/src/cli.js index 327bbc47e8..9e7a2b0954 100644 --- a/packages/endo/src/cli.js +++ b/packages/endo/src/cli.js @@ -47,7 +47,7 @@ async function executeArchive(archivePath, { read, cwd }) { return execute(application); } -async function compartmap(applicationPath, { read, cwd }) { +async function compartmap(applicationPath, { read, cwd, stdout }) { const currentLocation = new URL(`${cwd()}/`, "file:///"); const applicationLocation = new URL(applicationPath, currentLocation); const { packageLocation } = await search(read, applicationLocation); @@ -55,7 +55,7 @@ async function compartmap(applicationPath, { read, cwd }) { read, packageLocation ); - console.log(JSON.stringify(compartmentMap, null, 2)); + stdout.write(`${JSON.stringify(compartmentMap, null, 2)}\n`); return 0; } @@ -122,7 +122,8 @@ export async function main(process, modules) { process.exitCode = await run(args, { read, write, - cwd: process.cwd + cwd: process.cwd, + stdout: process.stdout }); } catch (error) { process.exitCode = usage(error.stack || error.message); From 5e4145e7c4c3ce892feb9b2ecb79a7c933f50b64 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Tue, 4 Aug 2020 18:11:23 -0700 Subject: [PATCH 4/8] Revise argument forms for endo bin --- packages/endo/src/cli.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/endo/src/cli.js b/packages/endo/src/cli.js index 9e7a2b0954..e06f13f49d 100644 --- a/packages/endo/src/cli.js +++ b/packages/endo/src/cli.js @@ -66,7 +66,7 @@ async function run(args, powers) { for (const applicationLocation of args) { return executeLocation(applicationLocation, powers); } - } else if (arg === "--compartmap") { + } else if (arg === "-m" || arg === "--compartmap") { for (const applicationPath of args) { const rem = Array.from(args); if (rem.length > 0) { @@ -76,7 +76,7 @@ async function run(args, powers) { } return compartmap(applicationPath, powers); } - } else if (arg === "-w") { + } else if (arg === "-c" || arg === "--create") { for (const archivePath of args) { for (const applicationPath of args) { const rem = Array.from(args); @@ -90,11 +90,11 @@ async function run(args, powers) { return usage(`Expected application path`); } return usage(`Expected archive path`); - } else if (arg === "-x") { + } else if (arg === "-e" || arg === "--execute") { for (const archivePath of args) { const rem = Array.from(args); if (rem.length > 0) { - return usage(`Unexpected arguments after -x ${archivePath}: ${rem}`); + return usage(`Unexpected arguments after -e ${archivePath}: ${rem}`); } return executeArchive(archivePath, powers); } From 49aeefef12acc90cadc632c42e07df7c7365c7e4 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Mon, 10 Aug 2020 17:03:06 -0700 Subject: [PATCH 5/8] endo/main Use subcommands, remove dubious ones --- packages/endo/src/cli.js | 166 +++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 94 deletions(-) diff --git a/packages/endo/src/cli.js b/packages/endo/src/cli.js index e06f13f49d..8ae19aa7cf 100644 --- a/packages/endo/src/cli.js +++ b/packages/endo/src/cli.js @@ -5,121 +5,99 @@ import { loadLocation, writeArchive, loadArchive } from "./main.js"; import { search } from "./search.js"; import { compartmentMapForNodeModules } from "./compartmap.js"; -const iterate = sequence => sequence[Symbol.iterator](); - -function usage(message) { - console.error(message); - return 1; +async function subcommand([arg, ...rest], handlers) { + const keys = Object.keys(handlers); + if (arg === undefined || !keys.includes(arg)) { + return usage(`expected one of ${keys}`); + } + return handlers[arg](rest); } -async function execute(application) { - const endowments = harden({ - console: { - log(...args) { - console.log(...args); - } - } - }); - - await application.execute(endowments); - return 0; +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 executeLocation(applicationPath, { cwd, read }) { - const currentLocation = new URL(`${cwd()}/`, "file:///"); - const applicationLocation = new URL(applicationPath, currentLocation); - const application = await loadLocation(read, applicationLocation); - return execute(application); +function usage(message) { + console.error(message); + return 1; } -async function archive(archivePath, applicationPath, { cwd, read, write }) { - 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; +async function noEntryUsage() { + return usage(`expected path to program`); } -async function executeArchive(archivePath, { read, cwd }) { - const currentLocation = new URL(`${cwd()}/`, "file:///"); - const archiveLocation = new URL(archivePath, currentLocation); - const application = await loadArchive(read, archiveLocation); - return execute(application); +async function noArchiveUsage() { + return usage(`expected path for archive`); } -async function compartmap(applicationPath, { read, cwd, stdout }) { - 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; -} +async function run(args, { cwd, read, write, stdout }) { -// run parses command line arguments and dispatches to a subcommand. -async function run(args, powers) { - for (const arg of args) { - if (arg === "--") { - for (const applicationLocation of args) { - return executeLocation(applicationLocation, powers); - } - } else if (arg === "-m" || arg === "--compartmap") { - for (const applicationPath of args) { - const rem = Array.from(args); - if (rem.length > 0) { - return usage( - `Unexpected arguments after --compartmap ${applicationPath}: ${rem}` - ); - } - return compartmap(applicationPath, powers); - } - } else if (arg === "-c" || arg === "--create") { - for (const archivePath of args) { - for (const applicationPath of args) { - const rem = Array.from(args); - if (rem.length > 0) { - return usage( - `Unexpected arguments after --archive ${archivePath} ${applicationPath}: ${rem}` - ); - } - return archive(archivePath, applicationPath, powers); - } - return usage(`Expected application path`); + async function compartmap(args) { + async function handleEntry(applicationPath, args) { + if (args.length) { + return usage(`unexpected arguments: ${JSON.stringify(args)}`); } - return usage(`Expected archive path`); - } else if (arg === "-e" || arg === "--execute") { - for (const archivePath of args) { - const rem = Array.from(args); - if (rem.length > 0) { - return usage(`Unexpected arguments after -e ${archivePath}: ${rem}`); + 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)}`); } - return executeArchive(archivePath, powers); + 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 usage(`Expected archive path`); - } else if (arg.startsWith("-")) { - return usage(`Unrecognized flag ${arg}`); - } else { - const rem = Array.from(args); - if (rem.length > 0) { - return usage(`Unexpected arguments after ${arg}: ${rem}`); - } - return executeLocation(arg, powers); + return parameter(args, handleEntry, noEntryUsage); } + return parameter(args, handleArchive, noArchiveUsage); } - return usage(`Expected script path or flag`); + + return subcommand(args, { compartmap, archive }); } export async function main(process, modules) { const { fs } = modules; - const read = async location => fs.readFile(new URL(location).pathname); - const write = async (location, content) => - fs.writeFile(new URL(location).pathname, content); + + 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 { - const args = iterate(process.argv.slice(2)); - process.exitCode = await run(args, { + process.exitCode = await run(process.argv.slice(2), { read, write, cwd: process.cwd, From ed0a6b5c291690b1ce8141abae6062ae3b6962e2 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Mon, 10 Aug 2020 17:07:12 -0700 Subject: [PATCH 6/8] endo/main Fix lint --- packages/endo/src/cli.js | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/endo/src/cli.js b/packages/endo/src/cli.js index 8ae19aa7cf..3c3057a193 100644 --- a/packages/endo/src/cli.js +++ b/packages/endo/src/cli.js @@ -1,44 +1,42 @@ -/* global harden */ - +/* eslint no-shadow: [0] */ import "./lockdown.js"; -import { loadLocation, writeArchive, loadArchive } from "./main.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)) { + if (arg === undefined || !keys.includes(arg)) { return usage(`expected one of ${keys}`); } return handlers[arg](rest); } async function parameter(args, handle, usage) { - const [ arg, ...rest ] = args; + const [arg, ...rest] = args; if (arg === undefined) { return usage(`expected an argument`); } - if (arg.startsWith('-')) { + if (arg.startsWith("-")) { return usage(`unexpected flag: ${arg}`); } return handle(arg, rest); } -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 run(args, { cwd, read, write, stdout }) { - async function compartmap(args) { async function handleEntry(applicationPath, args) { if (args.length) { From 3e5bcb0646457c86a1b2e014a6cad61c12865737 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Tue, 11 Aug 2020 13:43:54 -0700 Subject: [PATCH 7/8] endo/cli Nits --- packages/endo/src/cli.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/endo/src/cli.js b/packages/endo/src/cli.js index 3c3057a193..868546f3a2 100644 --- a/packages/endo/src/cli.js +++ b/packages/endo/src/cli.js @@ -20,7 +20,7 @@ async function noArchiveUsage() { async function subcommand([arg, ...rest], handlers) { const keys = Object.keys(handlers); if (arg === undefined || !keys.includes(arg)) { - return usage(`expected one of ${keys}`); + return usage(`expected one of ${keys.join(", ")}`); } return handlers[arg](rest); } @@ -77,6 +77,9 @@ async function run(args, { cwd, read, write, stdout }) { 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 { @@ -98,8 +101,8 @@ export async function main(process, modules) { process.exitCode = await run(process.argv.slice(2), { read, write, - cwd: process.cwd, - stdout: process.stdout + cwd, + stdout }); } catch (error) { process.exitCode = usage(error.stack || error.message); From 0ed3dbc82e9e3fe8adacdcdb863d7c2552b826d1 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Tue, 25 Aug 2020 11:08:49 -0700 Subject: [PATCH 8/8] Direct bin:endo at bin/endo.js --- packages/endo/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/endo/package.json b/packages/endo/package.json index f40889199d..8e8505343a 100644 --- a/packages/endo/package.json +++ b/packages/endo/package.json @@ -15,7 +15,7 @@ "browser": "./dist/endo.umd.js" }, "bin": { - "endo": "./bin/endo" + "endo": "./bin/endo.js" }, "scripts": { "build": "rollup --config rollup.config.js",