Skip to content

Commit

Permalink
feat(cli): Expand options for storing values (#2237)
Browse files Browse the repository at this point in the history
In, #2226 we gain the ability to
marshal copy data and remotables that are backed by formulas.

This change surfaces these features to the CLI by exposing a variety of
flags.

```console
> endo store --json '{"hello": "world"}' --name greeting
> endo show greeting
{ hello: 'world' }
```

```console
> endo store --text 'Hello, World!' --name greeting
> endo show greeting
Hello, World!
```

```console
> endo store --bigint 42 --name meaningful-bigint
> endo show meaningful-bigint
42n
```

```console
> endo store --json-stdin --name greeting < <(jq -n '{hello:"world"}')
> endo show greeting
{ hello: 'world' }
```

```console
> endo store --text-stdin --name greeting < <(echo hi)
> endo show greeting
hi
```

```console
> endo store --stdin --name greeting < <(echo hi)
> endo show greeting
Object [Alleged: Readable file with SHA-512 d78abb05...] {}
> endo cat greeting
hi
```
  • Loading branch information
kriskowal committed Apr 25, 2024
2 parents 108458d + 0eb8759 commit 2afacc5
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 21 deletions.
2 changes: 1 addition & 1 deletion packages/cli/src/commands/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ export const bundleCommand = async ({
const bundleBytes = textEncoder.encode(bundleText);
const readerRef = makeReaderRef([bundleBytes]);
return withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
await E(agent).store(readerRef, bundleName);
await E(agent).storeBlob(readerRef, bundleName);
});
};
2 changes: 1 addition & 1 deletion packages/cli/src/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const install = async ({
await withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
// Prepare a bundle, with the given name.
if (bundleReaderRef !== undefined) {
await E(agent).store(bundleReaderRef, bundleName);
await E(agent).storeBlob(bundleReaderRef, bundleName);
}

try {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/make.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const makeCommand = async ({
await withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
// Prepare a bundle, with the given name.
if (bundleReaderRef !== undefined) {
await E(agent).store(bundleReaderRef, bundleName);
await E(agent).storeBlob(bundleReaderRef, bundleName);
}

const resultP =
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/commands/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
import os from 'os';
import { E } from '@endo/far';
import { withEndoAgent } from '../context.js';
import { parsePetNamePath } from '../pet-name.js';

export const show = async ({ name, agentNames }) =>
withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
const pet = await E(agent).lookup(...name.split('.'));
const namePath = parsePetNamePath(name);
let pet = await E(agent).lookup(...namePath);
if (typeof pet === 'string') {
pet = pet.trim();
}
console.log(pet);
});
95 changes: 90 additions & 5 deletions packages/cli/src/commands/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,96 @@ import { E } from '@endo/far';

import { withEndoAgent } from '../context.js';

export const store = async ({ storablePath, name, agentNames }) => {
const nodeReadStream = fs.createReadStream(storablePath);
const reader = makeNodeReader(nodeReadStream);
const readerRef = makeReaderRef(reader);
/**
* @param {Array<Uint8Array>} arrays
* @returns {Uint8Array}
*/
const concat = arrays => {
let totalLength = 0;
for (const array of arrays) {
totalLength += array.byteLength;
}

const result = new Uint8Array(totalLength);

let offset = 0;
for (const array of arrays) {
result.set(array, offset);
offset += array.byteLength;
}

return result;
};

/**
* @param {AsyncIterable<Uint8Array>} reader
*/
const asyncConcat = async reader => {
const chunks = [];
for await (const chunk of reader) {
chunks.push(chunk);
}
return concat(chunks);
};

export const store = async ({
name,
agentNames,
storePath,
storeStdin,
storeText,
storeTextStdin,
storeJson,
storeJsonStdin,
storeBigInt,
}) => {
const modes = {
storePath,
storeStdin,
storeText,
storeTextStdin,
storeJson,
storeJsonStdin,
storeBigInt,
};
const selectedModes = Object.entries(modes).filter(
([_modeName, value]) => value !== undefined,
);
const selectedModeNames = selectedModes.map(([modeName]) => modeName);
if (selectedModes.length !== 1) {
// Usage error should be reported without trace.
// eslint-disable-next-line no-throw-literal
throw `Must provide exactly one store flag. Got flags for: (${selectedModeNames.join(
', ',
)})`;
}

await withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
await E(agent).store(readerRef, name);
if (storeText !== undefined) {
await E(agent).storeValue(storeText, name);
} else if (storeJson !== undefined) {
await E(agent).storeValue(JSON.parse(storeJson), name);
} else if (storeBigInt !== undefined) {
await E(agent).storeValue(BigInt(storeBigInt), name);
} else if (storeTextStdin !== undefined) {
const reader = makeNodeReader(process.stdin);
const bytes = await asyncConcat(reader);
const text = new TextDecoder().decode(bytes);
await E(agent).storeValue(text, name);
} else if (storeJsonStdin !== undefined) {
const reader = makeNodeReader(process.stdin);
const bytes = await asyncConcat(reader);
const text = new TextDecoder().decode(bytes);
await E(agent).storeValue(JSON.parse(text), name);
} else if (storeStdin !== undefined) {
const reader = makeNodeReader(process.stdin);
const readerRef = makeReaderRef(reader);
await E(agent).storeBlob(readerRef, name);
} else if (storePath !== undefined) {
const nodeReadStream = fs.createReadStream(storePath);
const reader = makeNodeReader(nodeReadStream);
const readerRef = makeReaderRef(reader);
await E(agent).storeBlob(readerRef, name);
}
});
};
33 changes: 28 additions & 5 deletions packages/cli/src/endo.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,15 +337,38 @@ export const main = async rawArgs => {
});

program
.command('store <path>')
.description('stores a blob')
.command('store')
.description('stores a blob or structured value')
.option(...commonOptions.as)
.option(...commonOptions.name)
.action(async (storablePath, cmd) => {
const { name, as: agentNames } = cmd.opts();
.option('-p,--path <path>', 'store a file as a blob')
.option('--stdin', 'store stdin as a blob')
.option('--text <text>', 'store a string of UTF-8 text')
.option('--text-stdin', 'store STDIN as UTF-8 text')
.option('--json <json>', 'store JSON')
.option('--json-stdin', 'store STDIN JSON')
.option('--bigint <bigint>', 'store a bigint')
.action(async cmd => {
const {
name,
as: agentNames,
path: storePath,
stdin: storeStdin,
text: storeText,
textStdin: storeTextStdin,
json: storeJson,
jsonStdin: storeJsonStdin,
bigint: storeBigInt,
} = cmd.opts();
const { store } = await import('./commands/store.js');
return store({
storablePath,
storePath,
storeStdin,
storeText,
storeTextStdin,
storeJson,
storeJsonStdin,
storeBigInt,
name,
agentNames,
});
Expand Down
4 changes: 2 additions & 2 deletions packages/daemon/src/host.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const makeHostMaker = ({
* @param {ERef<AsyncIterableIterator<string>>} readerRef
* @param {string} [petName]
*/
const store = async (readerRef, petName) => {
const storeBlob = async (readerRef, petName) => {
/** @type {DeferredTasks<ReadableBlobDeferredTaskParams>} */
const tasks = makeDeferredTasks();

Expand Down Expand Up @@ -632,7 +632,7 @@ export const makeHostMaker = ({
request,
send,
// Host
store,
storeBlob,
storeValue,
provideGuest,
provideHost,
Expand Down
2 changes: 1 addition & 1 deletion packages/daemon/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ export interface EndoGuest extends EndoAgent {}
export type FarEndoGuest = FarRef<EndoGuest>;

export interface EndoHost extends EndoAgent {
store(
storeBlob(
readerRef: ERef<AsyncIterableIterator<string>>,
petName: string,
): Promise<FarRef<EndoReadable>>;
Expand Down
8 changes: 4 additions & 4 deletions packages/daemon/test/test-endo.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ const doMakeBundle = async (host, filePath, callback) => {
const bundleBytes = textEncoder.encode(bundleText);
const bundleReaderRef = makeReaderRef([bundleBytes]);

await E(host).store(bundleReaderRef, bundleName);
await E(host).storeBlob(bundleReaderRef, bundleName);
const result = await callback(bundleName);
await E(host).remove(bundleName);
return result;
Expand Down Expand Up @@ -415,7 +415,7 @@ test('store without name', async t => {
const { host } = await prepareHost(t);

const readerRef = makeReaderRef([new TextEncoder().encode('hello\n')]);
const readable = await E(host).store(readerRef);
const readable = await E(host).storeBlob(readerRef);
const actualText = await E(readable).text();
t.is(actualText, 'hello\n');
});
Expand All @@ -426,7 +426,7 @@ test('store with name', async t => {
{
const { host } = await makeHost(config, cancelled);
const readerRef = makeReaderRef([new TextEncoder().encode('hello\n')]);
const readable = await E(host).store(readerRef, 'hello-text');
const readable = await E(host).storeBlob(readerRef, 'hello-text');
const actualText = await E(readable).text();
t.is(actualText, 'hello\n');
}
Expand Down Expand Up @@ -1384,7 +1384,7 @@ test('list special names', async t => {
const { host } = await prepareHost(t);

const readerRef = makeReaderRef([new TextEncoder().encode('hello\n')]);
await E(host).store(readerRef, 'hello-text');
await E(host).storeBlob(readerRef, 'hello-text');

/** @type {string[]} */
const names = await E(host).list();
Expand Down

0 comments on commit 2afacc5

Please sign in to comment.