Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add shim for Command #183

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/shim-deno/src/deno/stable/classes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { Command } from "./classes/Command.js";
export { File, FsFile } from "./classes/FsFile.js";
export { Permissions } from "./classes/Permissions.js";
export { PermissionStatus } from "./classes/PermissionStatus.js";
188 changes: 188 additions & 0 deletions packages/shim-deno/src/deno/stable/classes/Command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
///<reference path="../lib.deno.d.ts" />

import childProcess from "child_process";
import fs from "fs";
import os from "os";
import url from "url";
import { once } from "events";
import which from "which";

import { BufferStreamReader } from "../../internal/streams.js";
import * as errors from "../variables/errors.js";

type SignalName = keyof typeof os.constants.signals;

function getCmd(firstArg: string | URL) {
if (firstArg instanceof URL) {
return url.fileURLToPath(firstArg);
} else {
return firstArg;
}
}

function getEnv(options: Deno.CommandOptions) {
const env = options.env ?? {};
for (const name in process.env) {
if (!Object.prototype.hasOwnProperty.call(env, name)) {
if (options.clearEnv) {
if (os.platform() === "win32") {
env[name] = "";
} else {
delete env[name];
}
} else {
env[name] = process.env[name]!;
}
}
}
return env;
}

function getStdio(
value: Deno.CommandOptions["stdout"] | undefined,
): childProcess.StdioPipe | childProcess.StdioNull {
if (value === "inherit" || value == null) {
return "inherit" as const; // default
} else if (value === "piped") {
return "pipe" as const;
} else if (value === "null") {
return "ignore" as const;
} else {
throw new Error("Unknown value.");
}
}

function parseCommandStatus(
statusCode: number | null,
signalName: NodeJS.Signals | null,
): Deno.CommandStatus {
if (!statusCode && !signalName) {
return { success: false, code: 1, signal: null };
}

// when there is a signal, the exit code is 128 + signal code
const signal = signalName
? os.constants.signals[signalName]
: statusCode != null && statusCode > 128
? statusCode - 128
: undefined;

// default to 1 if code can not be determined
const code = statusCode != null
? statusCode
: signal != null
? 128 + signal
: 1;
const success = code === 0;

return { success, code, signal: signalName as Deno.Signal | null };
}

export class Command implements Deno.Command {
constructor(
readonly command: string | URL,
readonly options: Deno.CommandOptions = {},
) {}

async output(): Promise<Deno.CommandOutput> {
if (this.options.cwd && !fs.existsSync(this.options.cwd)) {
throw new errors.NotFound("No such file or directory.");
}

// childProcess.spawn will asynchronously check if the command exists, but
// we need to do this synchronously
const commandName = getCmd(this.command);
if (!which.sync(commandName, { nothrow: true })) {
throw new errors.NotFound("No such file or directory");
}

const cp = childProcess.spawn(commandName, this.options.args || [], {
cwd: this.options.cwd,
env: getEnv(this.options),
uid: this.options.uid,
gid: this.options.gid,
shell: false,
windowsVerbatimArguments: this.options.windowsRawArguments,
signal: this.options.signal,
stdio: [
getStdio(this.options.stdin),
getStdio(this.options.stdout),
getStdio(this.options.stderr),
],
});

const status = await once(cp, "exit");

const stdout = cp.stdout
? await new BufferStreamReader(cp.stdout).readAll()
: new Uint8Array();
const stderr = cp.stderr
? await new BufferStreamReader(cp.stderr).readAll()
: new Uint8Array();

const [statusCode, signalName] = status as [
number,
SignalName | null,
];

const commandStatus = parseCommandStatus(statusCode, signalName);

const out: Deno.CommandOutput = {
stdout: stdout,
stderr: stderr,
...commandStatus,
};

return out;
}

outputSync(): Deno.CommandOutput {
if (this.options.cwd && !fs.existsSync(this.options.cwd)) {
throw new errors.NotFound("No such file or directory.");
}

// childProcess.spawn will asynchronously check if the command exists, but
// we need to do this synchronously
const commandName = getCmd(this.command);
if (!which.sync(commandName, { nothrow: true })) {
throw new errors.NotFound("No such file or directory");
}

const cp = childProcess.spawnSync(
commandName,
{
cwd: this.options.cwd,
env: getEnv(this.options),
uid: this.options.uid,
gid: this.options.gid,
windowsVerbatimArguments: this.options.windowsRawArguments,
signal: this.options.signal,
stdio: [
getStdio(this.options.stdin),
getStdio(this.options.stdout),
getStdio(this.options.stderr),
],
},
);

const stdout = new Uint8Array(cp.stdout);
const stderr = new Uint8Array(cp.stderr);

const commandStatus = parseCommandStatus(
cp.status,
cp.signal,
);

const out: Deno.CommandOutput = {
stdout: stdout,
stderr: stderr,
...commandStatus,
};

return out;
}

spawn(): Deno.ChildProcess {
throw new Error("Not implemented");
}
}
4 changes: 4 additions & 0 deletions packages/shim-deno/src/deno/stable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export type Closer = Deno.Closer;
export type Conn = Deno.Conn;
export type ConnectOptions = Deno.ConnectOptions;
export type ConnectTlsOptions = Deno.ConnectTlsOptions;
export type CommandOptions = Deno.CommandOptions;
export type CommandOutput = Deno.CommandOutput;
export type CommandStatus = Deno.CommandStatus;
export type ChildProcess = Deno.ChildProcess;
export type DenoTest = Deno.DenoTest;
export type DirEntry = Deno.DirEntry;
export type EnvPermissionDescriptor = Deno.EnvPermissionDescriptor;
Expand Down
Loading