Skip to content
This repository has been archived by the owner on May 21, 2019. It is now read-only.

Windows support #1033

Merged
Merged
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
6 changes: 0 additions & 6 deletions housekeeping/start.sh

This file was deleted.

14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -29,7 +29,6 @@
"@types/klaw": "1.3.2",
"@types/lodash": "4.14.64",
"@types/node": "7.0.18",
"@types/pty.js": "0.2.32",
"@types/react": "15.0.24",
"child-process-promise": "2.2.1",
"dirStat": "0.0.2",
@@ -42,7 +41,7 @@
"lodash": "4.17.4",
"mode-to-permissions": "0.0.2",
"node-ansiparser": "2.2.0",
"pty.js": "shockone/pty.js",
"node-pty": "0.6.6",
"react": "15.5.4",
"react-addons-test-utils": "15.5.1",
"react-dom": "15.5.4",
@@ -54,14 +53,19 @@
"@types/klaw": "1.3.2",
"@types/mocha": "2.2.41",
"chai": "3.5.0",
"concurrently": "3.4.0",
"cpx": "1.5.0",
"cross-env": "5.0.0",
"devtron": "1.4.0",
"electron": "1.6.7",
"electron-builder": "17.5.0",
"electron-mocha": "3.4.0",
"electron-rebuild": "1.5.11",
"enzyme": "2.8.2",
"mkdirp": "0.5.1",
"mocha": "3.3.0",
"npm-check-updates": "2.11.1",
"rimraf": "2.6.1",
"spectron": "3.7.0",
"ts-node": "3.0.4",
"tslint": "5.2.0",
@@ -74,15 +78,15 @@
"release": "build --publish always --draft=false",
"electron": "electron .",
"prestart": "npm install && npm run compile",
"start": "bash housekeeping/start.sh",
"start": "concurrently --kill-others -s first \"tsc --watch\" \"cross-env NODE_ENV=development npm run electron\"",
"test": "npm run lint && npm run unit-tests && npm run ui-tests && npm run integration-tests && build --publish never",
"unit-tests": "ELECTRON_RUN_AS_NODE=1 electron $(which mocha) --require ts-node/register $(find test -name '*_spec.ts')",
"ui-tests": "electron-mocha --require ts-node/register $(find test -name '*_spec.tsx')",
"integration-tests": "npm run compile && mocha",
"update-dependencies": "ncu -u",
"lint": "tslint `find src -name '*.ts*'` `find test -name '*.ts*'`",
"cleanup": "rm -rf compiled/src",
"copy-html": "mkdir -p compiled/src/views && cp src/views/index.html compiled/src/views",
"cleanup": "rimraf compiled/src",
"copy-html": "mkdirp compiled/src/views && cpx src/views/index.html compiled/src/views",
"compile": "npm run cleanup && npm run tsc && npm run copy-html",
"tsc": "tsc"
},
23 changes: 14 additions & 9 deletions src/PTY.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
import * as ChildProcess from "child_process";
import * as OS from "os";
import * as _ from "lodash";
import * as pty from "pty.js";
import * as pty from "node-pty";
import {loginShell} from "./utils/Shell";
import {debug} from "./utils/Common";

interface ITerminal {
write(data: string): void;
resize(cols: number, rows: number): void;
kill(signal?: string): void;
on(type: string, listener: (...args: any[]) => any): void;
}

export class PTY {
private terminal: pty.Terminal;
private terminal: ITerminal;

// TODO: write proper signatures.
// TODO: use generators.
// TODO: terminate. https://github.com/atom/atom/blob/v1.0.15/src/task.coffee#L151
constructor(words: EscapedShellWord[], env: ProcessEnvironment, dimensions: Dimensions, dataHandler: (d: string) => void, exitHandler: (c: number) => void) {
const shellArguments = [...loginShell.noConfigSwitches, "-i", "-c", words.join(" ")];
Copy link
Contributor Author

@ForNeVeR ForNeVeR May 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠ Warning: Unix-specific options were replaced by Windows-specific; need to find a portable way to express this option set.

  • (It has been fixed)

Sorry, something went wrong.

const shellArguments = [...loginShell.noConfigSwitches, ...loginShell.interactiveCommandSwitches, words.join(" ")];

debug(`PTY: ${loginShell.executableName} ${JSON.stringify(shellArguments)}`);

this.terminal = pty.fork(loginShell.executableName, shellArguments, {
this.terminal = <any> pty.fork(loginShell.executableName, shellArguments, {
cols: dimensions.columns,
rows: dimensions.rows,
cwd: env.PWD,
env: env,
});

this.terminal.on("data", (data: string) => dataHandler(data));
this.terminal.on("exit", (code: number) => {
exitHandler(code);
});
this.terminal.on("exit", (code: number) => exitHandler(code));
}

write(data: string): void {
@@ -65,7 +70,7 @@ export function executeCommand(
...execOptions,
env: _.extend({PWD: directory}, process.env),
cwd: directory,
shell: "/bin/bash",
shell: loginShell.commandExecutorPath,
};

ChildProcess.exec(`${command} ${args.join(" ")}`, options, (error, output) => {
@@ -86,5 +91,5 @@ export async function linedOutputOf(command: string, args: string[], directory:
export async function executeCommandWithShellConfig(command: string): Promise<string[]> {
const sourceCommands = (await loginShell.existingConfigFiles()).map(fileName => `source ${fileName} &> /dev/null`);

return await linedOutputOf(loginShell.executableName, ["-c", `'${[...sourceCommands, command].join("; ")}'`], process.env.HOME);
return await linedOutputOf(loginShell.executableName, [...loginShell.executeCommandSwitches, loginShell.combineCommands([...sourceCommands, command])], process.env.HOME);
}
5 changes: 2 additions & 3 deletions src/shell/Aliases.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {executeCommandWithShellConfig} from "../PTY";
import {loginShell} from "../utils/Shell";
import * as _ from "lodash";

export const aliasesFromConfig: Dictionary<string> = {};

export async function loadAliasesFromConfig(): Promise<void> {
const lines = await executeCommandWithShellConfig("alias");

const lines = await loginShell.loadAliases();
lines.map(parseAlias).forEach(parsed => aliasesFromConfig[parsed.name] = parsed.value);
}

3 changes: 2 additions & 1 deletion src/shell/Environment.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import {clone} from "lodash";
import {homeDirectory} from "../utils/Common";
import * as Path from "path";
import {AbstractOrderedSet} from "../utils/OrderedSet";
import {loginShell} from "../utils/Shell";

const ignoredEnvironmentVariables = [
"NODE_ENV",
@@ -34,7 +35,7 @@ export const preprocessEnv = (lines: string[]) => {

export const processEnvironment: Dictionary<string> = {};
export async function loadEnvironment(): Promise<void> {
const lines = preprocessEnv(await executeCommandWithShellConfig("env"));
const lines = preprocessEnv(await executeCommandWithShellConfig(loginShell.environmentCommand));

lines.forEach(line => {
const [key, ...valueComponents] = line.trim().split("=");
101 changes: 94 additions & 7 deletions src/utils/Shell.ts
Original file line number Diff line number Diff line change
@@ -2,15 +2,23 @@ import {basename} from "path";
import {readFileSync, statSync} from "fs";
import * as Path from "path";
import {EOL} from "os";
import {resolveFile, io, filterAsync, homeDirectory} from "./Common";
import {resolveFile, io, isWindows, filterAsync, homeDirectory} from "./Common";
import {executeCommandWithShellConfig} from "../PTY";
import * as _ from "lodash";

abstract class Shell {
abstract get executableName(): string;
abstract get configFiles(): string[];
abstract get noConfigSwitches(): string[];
abstract get executeCommandSwitches(): string[];
abstract get interactiveCommandSwitches(): string[];
abstract get preCommandModifiers(): string[];
abstract get historyFileName(): string;
abstract get commandExecutorPath(): string;
abstract get environmentCommand(): string;
abstract loadAliases(): Promise<string[]>;

abstract combineCommands(commands: string[]): string;

async existingConfigFiles(): Promise<string[]> {
const resolvedConfigFiles = this.configFiles.map(fileName => resolveFile(process.env.HOME, fileName));
@@ -33,7 +41,33 @@ abstract class Shell {
}
}

class Bash extends Shell {
abstract class UnixShell extends Shell {
get executeCommandSwitches() {
return ["-c"];
}

get interactiveCommandSwitches() {
return ["-i", "-c"];
}

get commandExecutorPath() {
return "/bin/bash";
}

get environmentCommand() {
return "env";
}

loadAliases() {
return executeCommandWithShellConfig("alias");
}

combineCommands(commands: string[]) {
return `'${commands.join("; ")}'`;
}
}

class Bash extends UnixShell {
get executableName() {
return "bash";
}
@@ -65,7 +99,7 @@ class Bash extends Shell {
}
}

class ZSH extends Shell {
class ZSH extends UnixShell {
get executableName() {
return "zsh";
}
@@ -105,18 +139,71 @@ class ZSH extends Shell {
}
}

class Cmd extends Shell {
static get cmdPath() {
return Path.join(process.env.WINDIR, "System32", "cmd.exe");
}

get executableName() {
return "cmd.exe";
}

get configFiles() {
return [
];
}

get executeCommandSwitches() {
return ["/c"];
}

get interactiveCommandSwitches() {
return ["/c"];
}

get noConfigSwitches() {
return [];
}

get preCommandModifiers(): string[] {
return [];
}

get historyFileName(): string {
return "";
}

get commandExecutorPath() {
return Cmd.cmdPath;
}

get environmentCommand() {
return "set";
}

combineCommands(commands: string[]) {
return `"${commands.join(" && ")}`;
}

async loadAliases() {
return [];
}
}

const supportedShells: Dictionary<Shell> = {
bash: new Bash(),
zsh: new ZSH() ,
zsh: new ZSH(),
"cmd.exe": new Cmd(),
};

const shell = () => {
const shellName = basename(process.env.SHELL);
const shellName = process.env.SHELL ? basename(process.env.SHELL) : "";
if (shellName in supportedShells) {
return process.env.SHELL;
} else {
console.error(`${shellName} is not supported; defaulting to /bin/bash`);
return "/bin/bash";
const defaultShell = isWindows ? Cmd.cmdPath : "/bin/bash";
console.error(`${shellName} is not supported; defaulting to ${defaultShell}`);
return defaultShell;
}
};