not finished

This commit is contained in:
Asher
2019-01-07 18:46:19 -06:00
committed by Kyle Carberry
parent 776bb227e6
commit 9cd81f73fa
79 changed files with 11015 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
import * as cp from "child_process";
import * as stream from "stream";
import * as events from "events";
import * as net from "net";
import { wush, Session } from "@coder/wush";
import { useBuffer, throwUnimplementedError, throwSyncError } from "./util";
/**
* Readable stream.
*/
class Readable extends stream.Readable {
/**
* Read a chunk.
*/
public _read(_size: number): void {
// There is nothing to actually read.
}
}
/**
* Implementation of ChildProcess for the browser.
*/
class ChildProcess extends events.EventEmitter implements cp.ChildProcess {
public connected: boolean = true;
public killed: boolean = false;
public pid = 0;
public stdin: stream.Writable;
public stdout: Readable;
public stderr: Readable;
public stdio: [stream.Writable, stream.Readable, stream.Readable];
private emitter = new events.EventEmitter();
public constructor(private session: Session) {
super();
this.emitter = new events.EventEmitter();
this.stdin = new stream.Writable();
this.stdin._write = (
chunk: any, // tslint:disable-line no-any so we can match the Node API.
_encoding: string,
callback: (error?: Error) => void,
): void => {
session.sendStdin(chunk.toString());
callback();
};
this.stdout = new Readable();
this.stderr = new Readable();
this.stdio = [this.stdin, this.stdout, this.stderr];
session.onDone((exitCode) => {
this.emitter.emit("exit", exitCode);
});
session.onDisconnect(() => {
this.emitter.emit("exit", -1);
});
session.onStdout((data) => {
this.stdout.emit("data", data);
});
session.onStderr((data) => {
this.stderr.emit("data", data);
});
}
/**
* Kill the session.
*/
public kill(): void {
this.session.close();
}
/**
* Not implemented.
*/
public disconnect(): void {
throwUnimplementedError();
}
/**
* Not implemented.
*/
public ref(): void {
throwUnimplementedError();
}
/**
* Not implemented.
*/
public unref(): void {
throwUnimplementedError();
}
/**
* Not implemented.
*/
public send(
_message: any, // tslint:disable-line no-any so we can match the Node API.
_sendHandle?: net.Socket | net.Server | ((error: Error) => void),
_options?: cp.MessageOptions | ((error: Error) => void),
_callback?: (error: Error) => void,
): boolean {
throw throwUnimplementedError();
}
/**
* Add event listener.
*/
public on(
eventName: string,
callback: (...args: any[]) => void, // tslint:disable-line no-any so we can match the Node API.
): this {
this.emitter.on(eventName, callback);
return this;
}
}
// tslint:disable only-arrow-functions
function exec(
command: string,
options?: { encoding?: BufferEncoding | string | "buffer" | null } & cp.ExecOptions | null | ((error: Error | null, stdout: string, stderr: string) => void) | ((error: Error | null, stdout: Buffer, stderr: Buffer) => void),
callback?: ((error: Error | null, stdout: string, stderr: string) => void) | ((error: Error | null, stdout: Buffer, stderr: Buffer) => void),
): cp.ChildProcess {
const process = new ChildProcess(wush.execute({ command }));
let stdout = "";
process.stdout.on("data", (data) => {
stdout += data.toString();
});
let stderr = "";
process.stderr.on("data", (data) => {
stderr += data.toString();
});
process.on("exit", (exitCode) => {
const error = exitCode !== 0 ? new Error(stderr) : null;
if (typeof options === "function") {
callback = options;
}
// @ts-ignore not sure how to make this work.
callback(
error,
useBuffer(options) ? Buffer.from(stdout) : stdout,
useBuffer(options) ? Buffer.from(stderr) : stderr,
);
});
return process;
}
function fork(modulePath: string): cp.ChildProcess {
return new ChildProcess(wush.execute({
command: `node ${modulePath}`,
}));
}
function spawn(_command: string, _args?: ReadonlyArray<string>, _options?: cp.SpawnOptions): cp.ChildProcess {
throw new Error("not implemented");
}
// tslint:enable only-arrow-functions
// To satisfy the types.
// tslint:disable no-any
exec.__promisify__ = undefined as any;
// tslint:enable no-any
const exp: typeof cp = {
exec,
execFile: throwUnimplementedError,
execFileSync: throwSyncError,
execSync: throwSyncError,
fork,
spawn,
spawnSync: throwSyncError,
};
export = exp;

View File

@@ -0,0 +1 @@
module.exports = {};

View File

@@ -0,0 +1,715 @@
import { ChildProcess } from "child_process";
import * as fs from "fs";
import { EventEmitter } from "events";
import { promisify } from "util";
import { Writable } from "stream";
import { exec } from "./child_process";
import {
bashCommand, throwUnimplementedError, throwSyncError, escapePath,
useBuffer, NewlineInputBuffer, Queue,
} from "./util";
/**
* An open file.
*/
interface IOpenFile {
readonly path: fs.PathLike;
position: number | undefined;
}
type ReaddirCallback = (error?: NodeJS.ErrnoException, files?: string[]) => void;
/**
* Queue for readdir.
*/
class ReaddirQueue extends Queue<ReaddirCallback> {
public async run(items: Map<string, ReaddirCallback[]>): Promise<void> {
const keys = Array.from(items.keys());
try {
const stdio = await promisify(exec)(`bash -c '${keys.map((key) => `cd ${escapePath(key)} && ls -1a; echo;`).join(" ")}'`);
stdio.stdout.trim().split("\n\n").forEach((split, index) => {
const path = keys[index];
const cbs = items.get(path);
if (split.indexOf("does not exist") !== -1) {
cbs.forEach((cb) => {
cb({
code: "ENOENT",
message: "No such file or directory " + path,
name: "Not found",
});
});
} else {
const files = split.trim().split("\n");
cbs.forEach((cb) => {
cb(undefined, files.filter((f) => f !== "." && f !== ".."));
});
}
});
} catch (error) {
items.forEach((cbs) => cbs.forEach((cb) => cb(new Error("failed to ls"))));
}
}
}
type StatCallback = (error?: NodeJS.ErrnoException, stats?: fs.Stats) => void;
/**
* Queue for stat.
*/
class StatQueue extends Queue<StatCallback> {
public constructor() {
super(100);
}
public async run(items: Map<string, StatCallback[]>): Promise<void> {
try {
const stats = await this.stat(Array.from(items.keys()));
items.forEach((callbacks, path) => {
if (stats.has(path)) {
callbacks.forEach((cb) => {
cb(undefined, stats.get(path));
});
} else {
callbacks.forEach((cb) => {
cb({
code: "ENOENT",
message: "No such file or directory " + path,
name: "Not found",
});
});
}
});
} catch (error) {
items.forEach((callbacks) => {
callbacks.forEach((cb) => {
cb({
code: "ECMDFAIL",
message: "failed to stat",
name: "failed to stat",
});
});
});
}
}
/**
* Perform stat on multiple paths. Invalid files are ignored.
*/
private async stat(paths: string[]): Promise<Map<string, fs.Stats>> {
const map = new Map<string, fs.Stats>();
const pathsStr = paths.map(escapePath).join(" ");
const resp = await promisify(exec)(
`bash -c "stat ${pathsStr} -c \\\\'%n\\\\',%s,%F,%Y,%a,%g,%u,%X,%W,%d,%i,%b,%B,%Z,%h,%t"`,
);
resp.stdout.split("\n").forEach((stat) => {
const matches = stat.trim().match(/(^'.*'|[^',\s]+)(?=\s*,|\s*$)/g);
if (!matches || matches.length < 16) {
return;
}
const name = matches[0].substring(1, matches[0].length -1);
const size = parseInt(matches[1], 10);
const fileType = matches[2];
const mtime = new Date(parseInt(matches[3], 10) * 1000);
const mode = parseInt(matches[4], 10);
const gid = parseInt(matches[5], 10);
const uid = parseInt(matches[6], 10);
const atime = new Date(parseInt(matches[7], 10) * 1000);
const birthtime = new Date(parseInt(matches[8], 10) * 1000);
const dev = parseInt(matches[9], 10);
const ino = parseInt(matches[10], 10);
const blocks = parseInt(matches[11], 10);
const blksize = parseInt(matches[12], 10);
const ctime = new Date(parseInt(matches[13], 10) * 1000);
const nlink = parseInt(matches[14], 10);
const rdev = parseInt(matches[15], 10);
map.set(name, {
atime: atime,
atimeMs: atime.getTime(),
birthtime,
birthtimeMs: birthtime.getTime(),
blksize,
blocks,
ctime,
ctimeMs: ctime.getTime(),
dev,
gid,
ino,
isBlockDevice: (): boolean => fileType === "block special file",
isCharacterDevice: (): boolean => fileType === "character special file",
isDirectory: (): boolean => fileType === "directory",
isFIFO: (): boolean => fileType === "fifo",
isFile: (): boolean => fileType === "regular file",
isSocket: (): boolean => fileType === "socket",
isSymbolicLink: (): boolean => fileType === "symbolic link",
mode,
mtime,
mtimeMs: mtime.getTime(),
nlink,
rdev,
size,
uid,
});
});
return map;
}
}
type ReadFileCallback = (err: NodeJS.ErrnoException, content: string) => void;
/**
* Queue for readFile.
*/
class ReadFileQueue extends Queue<ReadFileCallback> {
public constructor() {
super(100);
}
public async run(items: Map<string, ReadFileCallback[]>): Promise<void> {
try {
throwUnimplementedError();
} catch (error) {
items.forEach((cbs) => cbs.forEach((cb) => cb(error, "")));
}
}
}
class Watcher extends EventEmitter implements fs.FSWatcher {
public constructor(private readonly process: ChildProcess) {
super();
}
public close(): void {
this.process.kill();
}
}
class WriteStream extends Writable implements fs.WriteStream {
public path: string;
private process: ChildProcess;
public constructor(path: fs.PathLike) {
super();
this.path = path.toString();
this.process = exec(`cat > ${escapePath(this.path)}`);
setTimeout(() => {
// Set timeout so listeners have time to register.
this.emit("open");
}, 0);
this.process.on("exit", () => {
this.emit("close");
});
}
public get bytesWritten(): number {
throw throwUnimplementedError();
}
// tslint:disable-next-line no-any
public _write(chunk: any, _encoding: string, callback: () => void): void {
this.process.stdin.write(chunk);
callback();
}
public close(): void {
this.process.kill();
}
}
// Used to identify files by descriptor.
let lastFileDescriptor = 0;
const readdirQueue = new ReaddirQueue();
const readFileQueue = new ReadFileQueue();
const statQueue = new StatQueue();
const openFiles = new Map<number, IOpenFile>();
// tslint:disable only-arrow-functions
// A common pattern is to exec and call the callback with an error or null.
function execAndCallback(command: string, callback: (err: NodeJS.ErrnoException) => void): void {
promisify(exec)(command).then(() => {
callback(null as any); // tslint:disable-line no-any
}).catch((error) => {
callback(error);
});
}
function appendFile(
path: fs.PathLike | number,
data: any, // tslint:disable-line no-any
options?: { encoding?: string | null; mode?: string | number; flag?: string; } | string | undefined | null | ((err: NodeJS.ErrnoException) => void),
callback?: (err: NodeJS.ErrnoException) => void,
): void {
if (typeof options === "function") {
callback = options;
}
if (typeof path === "number") {
if (!openFiles.has(path)) {
// @ts-ignore not sure how to make this work.
return callback(new Error("not open"), undefined as any); // tslint:disable-line no-any
}
path = openFiles.get(path).path;
}
const process = exec(`${data ? "cat >>" : "touch"} ${escapePath(path.toString())}`, (error) => {
callback!(error as any); // tslint:disable-line no-any
});
if (data) {
process.stdin.write(data);
}
}
function close(fd: number, callback: (err: NodeJS.ErrnoException) => void): void {
if (!openFiles.has(fd)) {
return callback(new Error("file wasnt open"));
}
openFiles.delete(fd);
callback(null as any); // tslint:disable-line no-any
}
function createWriteStream(path: fs.PathLike, _options?: string | {
flags?: string;
encoding?: string;
fd?: number;
mode?: number;
}): fs.WriteStream {
return new WriteStream(path);
}
function exists(path: fs.PathLike, callback: (exists: boolean) => void): void {
const pathStr = escapePath(path.toString());
const command = bashCommand(
`if [ -d ${pathStr} ]; then echo true;`
+ ` elif [ -f ${pathStr} ]; then echo true;`
+ ` elif [ -s ${pathStr} ]; then echo true;`
+ "fi",
);
promisify(exec)(command).then((stdio) => {
callback(stdio.stdout.trim() === "true");
}).catch(() => {
callback(false);
});
}
function fstat(fd: number, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
if (!openFiles.has(fd)) {
return callback(new Error("not open"), null as any); // tslint:disable-line no-any
}
stat(openFiles.get(fd).path, callback);
}
function futimes(
fd: number,
atime: string | number | Date,
mtime: string | number | Date,
callback: (err: NodeJS.ErrnoException) => void,
): void {
if (!openFiles.has(fd)) {
return callback(new Error("not opened"));
}
const openFile = openFiles.get(fd);
const command = [
{ flag: "a", time: atime },
{ flag: "m", time: mtime },
]
.filter((item) => !!item.time)
.map((item) => `touch -${item.flag} --date="${item.time}" ${escapePath(openFile.path.toString())}`)
.join(";");
if (command.length === 0) {
return callback(new Error("atime or mtime required"));
}
execAndCallback(command, callback);
}
function lstat(path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
stat(path, callback);
}
function mkdir(
path: fs.PathLike, mode: number | string | undefined | null | ((err: NodeJS.ErrnoException) => void),
callback?: (err: NodeJS.ErrnoException) => void,
): void {
execAndCallback(
`mkdir -p ${escapePath(path.toString())}`,
typeof mode === "function" ? mode : callback!,
);
}
function open(
path: fs.PathLike,
flags: string | number, mode: number | string | undefined | null | ((err: NodeJS.ErrnoException, fd: number) => void),
callback?: (err: NodeJS.ErrnoException, fd: number) => void,
): void {
if (typeof mode === "function") {
callback = mode;
}
// Don't touch if read-only.
const promise = flags !== "r"
? promisify(exec)(`touch ${escapePath(path.toString())}`)
.then(() => Promise.resolve())
.catch((error) => {
if (error.message.indexOf("No such file or directory") !== -1) {
return Promise.reject({
code: "ENOENT",
message: "No such file or directory " + path,
name: "Not found",
});
}
return Promise.reject(error);
})
: Promise.resolve();
promise.then(() => {
const id = lastFileDescriptor++;
openFiles.set(id, {
path,
position: undefined,
});
callback!(null as any, id); // tslint:disable-line no-any
}).catch((error) => {
callback!(error, undefined as any); // tslint:disable-line no-any
});
}
function read<TBuffer extends Buffer | Uint8Array>(
fd: number,
buffer: TBuffer,
offset: number,
length: number,
position: number | null,
callback?: (err: NodeJS.ErrnoException, bytesRead: number, buffer: TBuffer) => void,
): void {
if (!openFiles.has(fd)) {
if (callback) {
// tslint:disable-next-line no-any
callback(new Error("not opened"), undefined as any, undefined as any);
}
return;
}
const hasPosition = typeof position === "number";
const openFile = openFiles.get(fd);
if (!hasPosition) {
position = openFile.position || 0;
}
readFile(openFile.path, (error, data) => {
if (error) {
if (callback) {
// tslint:disable-next-line no-any
callback(error, undefined as any, undefined as any);
}
return;
}
const output = data.slice(position!, position! + length);
if (output.length !== 0) {
buffer.set(output, offset);
}
if (!hasPosition) {
if (typeof openFile.position !== "undefined") {
openFile.position += output.length;
} else {
openFile.position = output.length;
}
openFiles.set(fd, openFile);
}
if (callback) {
callback(null as any, output.length, buffer); // tslint:disable-line no-any
}
});
}
function readFile(
path: fs.PathLike | number,
options: { encoding?: string | null; flag?: string; } | string | undefined | null | ((err: NodeJS.ErrnoException, data: Buffer) => void),
callback?: ((err: NodeJS.ErrnoException, data: Buffer | string) => void) | ((err: NodeJS.ErrnoException, data: Buffer) => void) | ((err: NodeJS.ErrnoException, data: string) => void),
): void {
if (typeof options === "function") {
callback = options;
}
if (typeof path === "number") {
if (!openFiles.has(path)) {
// @ts-ignore not sure how to make this work.
return callback(new Error("not open"), undefined as any); // tslint:disable-line no-any
}
path = openFiles.get(path).path;
}
readFileQueue.add(path.toString(), (error, result) => {
// @ts-ignore not sure how to make this work.
callback(
error,
result && useBuffer(options) ? Buffer.from(result) : result,
);
});
}
function readdir(
path: fs.PathLike,
options: { encoding?: string | null } | string | undefined | null | ((err: NodeJS.ErrnoException, files: string[]) => void),
callback?: ((err: NodeJS.ErrnoException, files: string[]) => void) | ((err: NodeJS.ErrnoException, files: Buffer[]) => void) | ((err: NodeJS.ErrnoException, files: Array<string | Buffer>) => void),
): void {
if (typeof options === "function") {
callback = options;
}
readdirQueue.add(path.toString(), (error, files) => {
if (typeof options === "function") {
callback = options;
}
// @ts-ignore not sure how to make this work.
callback(
error,
files && useBuffer(options)
? files.map((f) => Buffer.from(f))
: files,
);
});
}
function realpath(
path: fs.PathLike,
options: { encoding?: string | null } | string | undefined | null | ((err: NodeJS.ErrnoException, resolvedPath: string) => void),
callback?: ((err: NodeJS.ErrnoException, resolvedPath: string) => void) | ((err: NodeJS.ErrnoException, resolvedPath: Buffer) => void) | ((err: NodeJS.ErrnoException, resolvedPath: string | Buffer) => void),
): void {
if (typeof options === "function") {
callback = options;
}
// @ts-ignore not sure how to make this work.
callback(
null,
useBuffer(options) ? Buffer.from(path.toString()) : path.toString(),
);
}
function rename(oldPath: fs.PathLike, newPath: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
promisify(exec)(`mv ${escapePath(oldPath.toString())} ${escapePath(newPath.toString())}`).then(() => {
callback(null as any); // tslint:disable-line no-any
}).catch((error) => {
callback(error.message.indexOf("No such file or directory") !== -1 ? {
code: "ENOENT",
message: "No such file or directory " + oldPath,
name: "Not found",
} : error);
});
}
function rmdir(path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
execAndCallback(`rmdir ${escapePath(path.toString())}`, callback);
}
function stat(path: fs.PathLike, callback: (err: NodeJS.ErrnoException, stats: fs.Stats) => void): void {
statQueue.add(path.toString(), (error, stats) => {
callback(error as any, stats as any); // tslint:disable-line no-any
});
}
function unlink(path: fs.PathLike, callback: (err: NodeJS.ErrnoException) => void): void {
execAndCallback(`unlink ${escapePath(path.toString())}`, callback);
}
function watch(
filename: fs.PathLike,
options: { encoding?: string | null, persistent?: boolean, recursive?: boolean } | string | undefined | null | ((event: string, filename: string) => void),
listener?: ((event: string, filename: string) => void) | ((event: string, filename: Buffer) => void),
): fs.FSWatcher {
const buffer = new NewlineInputBuffer((msg): void => {
msg = msg.trim();
const index = msg.lastIndexOf(":");
const events = msg.substring(index + 1).split(",");
const baseFilename = msg.substring(0, index).split("/").pop();
events.forEach((event) => {
switch (event) {
// Rename is emitted when a file appears or disappears in the directory.
case "CREATE":
case "DELETE":
case "MOVED_FROM":
case "MOVED_TO":
watcher.emit("rename", baseFilename);
break;
case "CLOSE_WRITE":
watcher.emit("change", baseFilename);
break;
}
});
});
const process = exec(`inotifywait ${escapePath(filename.toString())} -m --format "%w%f:%e"`);
process.on("exit", (exitCode) => {
watcher.emit("error", new Error(`process terminated unexpectedly with code ${exitCode}`));
});
process.stdout.on("data", (data) => {
buffer.push(data);
});
const watcher = new Watcher(process);
watcher.on("change", (filename) => {
// @ts-ignore not sure how to make this work.
listener("change", useBuffer(options) ? Buffer.from(filename) : filename);
});
watcher.on("rename", (filename) => {
// @ts-ignore not sure how to make this work.
listener("rename", useBuffer(options) ? Buffer.from(filename) : filename);
});
return watcher;
}
function writeFile(
path: fs.PathLike | number,
data: any, // tslint:disable-line no-any
options: { encoding?: string | null; mode?: number | string; flag?: string; } | string | undefined | null | ((err: NodeJS.ErrnoException) => void),
callback?: (err: NodeJS.ErrnoException) => void,
): void {
if (typeof options === "function") {
callback = options;
}
if (typeof path === "number") {
if (!openFiles.has(path)) {
// @ts-ignore not sure how to make this work.
return callback(new Error("not open"), undefined as any); // tslint:disable-line no-any
}
path = openFiles.get(path).path;
}
const process = exec(`${data ? "cat >" : "touch"} ${escapePath(path.toString())}`, (error) => {
callback!(error as any); // tslint:disable-line no-any
});
if (data) {
process.stdin.write(data);
}
}
// tslint:enable only-arrow-functions
// Just to satisfy the types.
// tslint:disable no-any
appendFile.__promisify__ = undefined as any;
close.__promisify__ = undefined as any;
exists.__promisify__ = undefined as any;
fstat.__promisify__ = undefined as any;
futimes.__promisify__ = undefined as any;
lstat.__promisify__ = undefined as any;
mkdir.__promisify__ = undefined as any;
open.__promisify__ = undefined as any;
read.__promisify__ = undefined as any;
readFile.__promisify__ = undefined as any;
readdir.__promisify__ = undefined as any;
realpath.__promisify__ = undefined as any;
rename.__promisify__ = undefined as any;
rmdir.__promisify__ = undefined as any;
stat.__promisify__ = undefined as any;
unlink.__promisify__ = undefined as any;
writeFile.__promisify__ = undefined as any;
// tslint:enable no-any
const exp: typeof fs = {
constants: fs.constants,
Stats: fs.Stats,
ReadStream: fs.ReadStream,
WriteStream: fs.WriteStream,
access: throwUnimplementedError,
accessSync: throwSyncError,
appendFile,
appendFileSync: throwSyncError,
chmod: throwUnimplementedError,
chmodSync: throwSyncError,
chown: throwUnimplementedError,
chownSync: throwSyncError,
close,
copyFile: throwUnimplementedError,
copyFileSync: throwSyncError,
closeSync: throwSyncError,
createReadStream: throwUnimplementedError,
createWriteStream,
exists,
existsSync: throwSyncError,
fchmod: throwUnimplementedError,
fchmodSync: throwSyncError,
fchown: throwUnimplementedError,
fchownSync: throwSyncError,
fdatasync: throwUnimplementedError,
fdatasyncSync: throwSyncError,
fstat,
fstatSync: throwSyncError,
fsync: throwUnimplementedError,
fsyncSync: throwSyncError,
ftruncate: throwUnimplementedError,
ftruncateSync: throwSyncError,
futimes,
futimesSync: throwSyncError,
lchmod: throwUnimplementedError,
lchmodSync: throwSyncError,
lchown: throwUnimplementedError,
lchownSync: throwSyncError,
link: throwUnimplementedError,
linkSync: throwSyncError,
lstat,
lstatSync: throwSyncError,
mkdir,
mkdirSync: throwSyncError,
mkdtemp: throwUnimplementedError,
mkdtempSync: throwSyncError,
open,
openSync: throwSyncError,
read,
readFile,
readFileSync: throwSyncError,
readSync: throwSyncError,
readdir,
readdirSync: throwSyncError,
readlink: throwUnimplementedError,
readlinkSync: throwSyncError,
realpath,
realpathSync: throwSyncError,
rename,
renameSync: throwSyncError,
rmdir,
rmdirSync: throwSyncError,
stat,
statSync: throwSyncError,
symlink: throwUnimplementedError,
symlinkSync: throwSyncError,
truncate: throwUnimplementedError,
truncateSync: throwSyncError,
unlink,
unlinkSync: throwSyncError,
unwatchFile: throwUnimplementedError,
utimes: throwUnimplementedError,
utimesSync: throwSyncError,
watch,
watchFile: throwUnimplementedError,
write: throwUnimplementedError,
writeFile,
writeFileSync: throwSyncError,
writeSync: throwSyncError,
};
export = exp;

View File

@@ -0,0 +1,2 @@
import { bashCommand, escapePath, isBrowserEnvironment } from "./util";
export { bashCommand, escapePath, isBrowserEnvironment };

View File

@@ -0,0 +1,70 @@
import * as net from "net";
/**
* Implementation of Socket for the browser.
*/
class Socket extends net.Socket {
public connect(): this {
throw new Error("not implemented");
}
}
/**
* Implementation of Server for the browser.
*/
class Server extends net.Server {
public listen(
_port?: number | any | net.ListenOptions, // tslint:disable-line no-any so we can match the Node API.
_hostname?: string | number | Function,
_backlog?: number | Function,
_listeningListener?: Function,
): this {
throw new Error("not implemented");
}
}
// tslint:disable only-arrow-functions
function connect(): net.Socket {
throw new Error("not implemented");
}
function createConnection(): net.Socket {
throw new Error("not implemented");
}
function isIP(_input: string): number {
throw new Error("not implemented");
}
function isIPv4(_input: string): boolean {
throw new Error("not implemented");
}
function isIPv6(_input: string): boolean {
throw new Error("not implemented");
}
function createServer(
_options?: { allowHalfOpen?: boolean, pauseOnConnect?: boolean } | ((socket: net.Socket) => void),
_connectionListener?: (socket: net.Socket) => void,
): Server {
return new Server();
}
// tslint:enable only-arrow-functions
const exp: typeof net = {
Socket,
Server,
connect,
createConnection,
isIP,
isIPv4,
isIPv6,
createServer,
};
export = exp;

View File

@@ -0,0 +1,137 @@
// The type doesn't matter for these since we're just throwing.
// tslint:disable no-any
export const throwUnimplementedError = (): any => {
throw new Error("not implemented");
};
// In case the types except the promisify property.
throwUnimplementedError.__promisify__ = undefined as any;
// This one seems to be a mistake in the types for `link`.
throwUnimplementedError.link = undefined as any;
export const throwSyncError = (): any => {
throw new Error("sync is not supported");
};
// tslint:enable no-any
/**
* Return true if the options specify to use a Buffer instead of string.
*/
export const useBuffer = (options: { encoding?: string | null } | string | undefined | null | Function): boolean => {
return options === "buffer"
|| (!!options && typeof options !== "string" && typeof options !== "function"
&& (options.encoding === "buffer" || options.encoding === null));
};
/**
* Run a command with bash.
*/
export const bashCommand = (command: string): string => {
return `bash -c "${command.replace(/"/g, "\\\"")}"`;
};
/**
* Return true if we're in a browser environment (including web workers).
*/
export const isBrowserEnvironment = (): boolean => {
return typeof process === "undefined" || typeof process.stdout === "undefined";
};
/**
* Escape a path. This prevents any issues with file names that have quotes,
* spaces, braces, etc.
*/
export const escapePath = (path: string): string => {
return `'${path.replace(/'/g, "'\\''")}'`;
};
/**
* This queues up items then runs on all the items at once after a timeout. Each
* item has a callback that expects the response for that item which is the
* extending class's responsibility to call.
*
* You can also specify a maximum number of items to keep in the queue.
*/
export abstract class Queue<T> {
private items: Map<string, T[]>;
private timeout: number | NodeJS.Timer | undefined;
private max: number | undefined;
private timeoutDelay = 1;
public constructor(max?: number) {
this.items = new Map();
this.run = run;
this.max = max;
}
/**
* Add an item to the queue.
*/
public add(key: string, callback: T): void {
if (this.items.has(key)) {
this.items.get(key)!.push(callback);
} else {
this.items.set(key, [callback]);
}
const run = (): void => {
// tslint:disable-next-line no-any because NodeJS.Timer is valid.
clearTimeout(this.timeout as any);
this.timeout = undefined;
const newMap = new Map(this.items);
this.items.clear();
this.run(newMap);
};
if (typeof this.max !== "undefined" && this.items.size >= this.max) {
return run();
}
if (typeof this.timeout === "undefined") {
this.timeout = setTimeout(() => {
run();
}, this.timeoutDelay);
}
}
/**
* Run on the specified items then call their callbacks.
*/
protected abstract run(items: Map<string, T[]>): void;
}
/**
* Class for safely taking input and turning it into separate messages.
* Assumes that messages are split by newlines.
*/
export class NewlineInputBuffer {
private callback: (msg: string) => void;
private buffer: string | undefined;
public constructor(callback: (msg: string) => void) {
this.callback = callback;
}
/**
* Add data to be buffered.
*/
public push(data: string | Uint8Array): void {
let input = typeof data === "string" ? data : data.toString();
if (this.buffer) {
input = this.buffer + input;
this.buffer = undefined;
}
const lines = input.split("\n");
const length = lines.length - 1;
const lastLine = lines[length];
if (lastLine.length > 0) {
this.buffer = lastLine;
}
lines.pop(); // This is either the line we buffered or an empty string.
for (let i = 0; i < length; ++i) {
this.callback(lines[i]);
}
}
}