alwaysai
Version:
The alwaysAI command-line interface (CLI)
88 lines (79 loc) • 2.17 kB
text/typescript
import { Readable } from 'stream';
import { Spawner } from './types';
import { CodedError } from '@carnesen/coded-error';
export function GnuSpawner(context: {
resolvePath: Spawner['resolvePath'];
run: Spawner['run'];
runForegroundSync: Spawner['runForegroundSync'];
runForeground: Spawner['runForeground'];
runStreaming: Spawner['runStreaming'];
}): Spawner {
const { resolvePath, run, runForegroundSync, runForeground, runStreaming } = context;
return {
run,
runForegroundSync,
runForeground,
runStreaming,
resolvePath,
readdir,
mkdirp,
rimraf,
tar,
untar,
exists,
};
async function mkdirp(path?: string) {
await run({ exe: 'mkdir', args: ['-p', resolvePath(path)] });
}
async function readdir(path?: string) {
let output: string;
const resolvedPath = resolvePath(path);
try {
output = await run({ exe: 'ls', args: ['-A1', resolvedPath] });
// ^^ The output looks like '/foo/bar.txt' if path is an existing file
// Else it looks like 'a b c' if the path is a directory with files/subdirs a, b, c.
} catch (ex) {
if (
ex &&
typeof ex.message === 'string' &&
ex.message.includes('No such file or directory')
) {
ex.code = 'ENOENT';
}
throw ex;
}
if (output.startsWith('/')) {
throw new CodedError(`ENOTDIR: not a directory "${resolvedPath}"`, 'ENOTDIR');
}
return output.length > 0 ? output.split('\n') : [];
}
async function rimraf(path?: string) {
await run({ exe: 'rm', args: ['-rf', resolvePath(path)] });
}
async function tar(...paths: string[]) {
return await runStreaming({
exe: 'tar',
args: ['-cz', ...paths],
cwd: resolvePath(),
});
}
async function untar(input: Readable, cwd = '.') {
await run({
exe: 'tar',
args: ['-xz'],
cwd,
input,
});
}
async function exists(path: string) {
if (!path) {
throw new Error('"path" is required');
}
try {
await run({ exe: 'stat', args: [resolvePath(path)] });
return true;
} catch (ex) {
return false;
}
}
}