Skip to content

Commit

Permalink
feat(endo): Add command line (#398)
Browse files Browse the repository at this point in the history
This change provides a preliminary command line for `endo`, mostly because it is convenient for ad-hoc testing, though it will likely grow into something else.
  • Loading branch information
kriskowal authored Aug 25, 2020
1 parent cbe689e commit 350329a
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 10 deletions.
1 change: 1 addition & 0 deletions packages/endo/bin/endo
5 changes: 5 additions & 0 deletions packages/endo/bin/endo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env node
import fs from "fs";
import { main } from "../src/cli.js";

main(process, { fs: fs.promises });
3 changes: 3 additions & 0 deletions packages/endo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
110 changes: 110 additions & 0 deletions packages/endo/src/cli.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
2 changes: 1 addition & 1 deletion packages/endo/src/compartmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const inferParsers = (type, location) => {
// that the package exports.

const graphPackage = async (
name,
name = "",
readDescriptor,
graph,
{ packageLocation, packageDescriptor },
Expand Down
19 changes: 12 additions & 7 deletions packages/endo/src/import-archive.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
};
12 changes: 10 additions & 2 deletions packages/endo/src/zip.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
};

Expand Down

0 comments on commit 350329a

Please sign in to comment.