Skip to content

Commit 964bd51

Browse files
committed
fix: Correct spawning of Windows binaries by batch/cmd with spaces
Signed-off-by: Chad Wilson <chadw@thoughtworks.com>
1 parent ae8904a commit 964bd51

File tree

11 files changed

+114
-77
lines changed

11 files changed

+114
-77
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"description": "Gauge support for VScode.",
99
"author": "ThoughtWorks",
1010
"license": "MIT",
11-
"version": "0.2.1",
11+
"version": "0.2.2",
1212
"publisher": "getgauge",
1313
"engines": {
1414
"vscode": "^1.82.0"

src/cli.ts

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
import { CommonSpawnOptions, spawn, spawnSync } from 'child_process';
3+
import { ChildProcess, CommonSpawnOptions, spawn, spawnSync, SpawnSyncReturns } from 'child_process';
44
import { platform } from 'os';
55
import { window } from 'vscode';
66
import { GaugeCommands, GRADLE_COMMAND, MAVEN_COMMAND } from './constants';
@@ -10,11 +10,11 @@ export class CLI {
1010
private readonly _gaugeVersion: string;
1111
private readonly _gaugeCommitHash: string;
1212
private readonly _gaugePlugins: Array<any>;
13-
private readonly _gaugeCommand: string;
14-
private readonly _mvnCommand: string;
15-
private readonly _gradleCommand: string;
13+
private readonly _gaugeCommand: Command;
14+
private readonly _mvnCommand: Command;
15+
private readonly _gradleCommand: Command;
1616

17-
public constructor(cmd: string, manifest: any, mvnCommand: string, gradleCommand: string) {
17+
public constructor(cmd: Command, manifest: any, mvnCommand: Command, gradleCommand: Command) {
1818
this._gaugeCommand = cmd;
1919
this._mvnCommand = mvnCommand;
2020
this._gradleCommand = gradleCommand;
@@ -23,18 +23,12 @@ export class CLI {
2323
this._gaugePlugins = manifest.plugins;
2424
}
2525

26-
public static getDefaultSpawnOptions(): CommonSpawnOptions {
27-
// should only deal with platform specific options
28-
return platform() === "win32" ? { shell: true } : {};
29-
}
30-
3126
public static instance(): CLI {
3227
const gaugeCommand = this.getCommand(GaugeCommands.Gauge);
3328
const mvnCommand = this.getCommand(MAVEN_COMMAND);
34-
let gradleCommand = this.getGradleCommand();
35-
if (!gaugeCommand || gaugeCommand === '') return new CLI(gaugeCommand, {}, mvnCommand, gradleCommand);
36-
let options = this.getDefaultSpawnOptions();
37-
let gv = spawnSync(gaugeCommand, [GaugeCommands.Version, GaugeCommands.MachineReadable], options);
29+
const gradleCommand = this.getGradleCommand();
30+
if (!gaugeCommand) return new CLI(undefined, {}, mvnCommand, gradleCommand);
31+
let gv = gaugeCommand.spawnSync([GaugeCommands.Version, GaugeCommands.MachineReadable]);
3832
let gaugeVersionInfo;
3933
try {
4034
gaugeVersionInfo = JSON.parse(gv.stdout.toString());
@@ -49,12 +43,12 @@ export class CLI {
4943
return this._gaugePlugins.some((p: any) => p.name === pluginName);
5044
}
5145

52-
public gaugeCommand(): string {
46+
public gaugeCommand(): Command {
5347
return this._gaugeCommand;
5448
}
5549

5650
public isGaugeInstalled(): boolean {
57-
return !!this._gaugeCommand && this._gaugeCommand !== '';
51+
return !!this._gaugeCommand;
5852
}
5953

6054
public isGaugeVersionGreaterOrEqual(version: string): boolean {
@@ -65,31 +59,21 @@ export class CLI {
6559
return this._gaugePlugins.find((p) => p.name === language).version;
6660
}
6761

68-
public getGaugeVersion(): string {
69-
return this._gaugeVersion;
70-
}
71-
7262
public async installGaugeRunner(language: string): Promise<any> {
7363
let oc = window.createOutputChannel("Gauge Install");
7464
let chan = new OutputChannel(oc, `Installing gauge ${language} plugin ...\n`, "");
7565
return new Promise((resolve, reject) => {
76-
let options = CLI.getDefaultSpawnOptions();
77-
let childProcess = spawn(this._gaugeCommand, [GaugeCommands.Install, language], options);
66+
let childProcess = this._gaugeCommand.spawn([GaugeCommands.Install, language]);
7867
childProcess.stdout.on('data', (chunk) => chan.appendOutBuf(chunk.toString()));
7968
childProcess.stderr.on('data', (chunk) => chan.appendErrBuf(chunk.toString()));
8069
childProcess.on('exit', (code) => {
81-
let postFailureMessage = '\nRefer https://docs.gauge.org/plugin.html' +
82-
' to install manually';
70+
let postFailureMessage = '\nRefer to https://docs.gauge.org/plugin.html to install manually';
8371
chan.onFinish(resolve, code, "", postFailureMessage, false);
8472
});
8573
});
8674
}
8775

88-
public isMavenInstalled(): boolean {
89-
return !!this._mvnCommand && this._mvnCommand !== '';
90-
}
91-
92-
public mavenCommand(): string {
76+
public mavenCommand(): Command {
9377
return this._mvnCommand;
9478
}
9579

@@ -107,23 +91,55 @@ export class CLI {
10791
return `${v}\n${cm}\n\n${plugins}`;
10892
}
10993

110-
public static getCommandCandidates(command: string): string[] {
111-
return (platform() === 'win32' ? [".exe", ".bat", ".cmd"] : [""])
112-
.map((ext) => `${command}${ext}`);
94+
public static getCommandCandidates(command: string): Command[] {
95+
return platform() === 'win32' ? [
96+
new Command(command, ".exe"),
97+
new Command(command, ".bat", true),
98+
new Command(command, ".cmd", true),
99+
] : [
100+
new Command(command)
101+
]
113102
}
114103

115-
public static checkSpawnable(command: string): boolean {
116-
const result = spawnSync(command, [], CLI.getDefaultSpawnOptions());
104+
public static isSpawnable(command: Command): boolean {
105+
const result = command.spawnSync();
117106
return result.status === 0 && !result.error;
118107
}
119108

120-
private static getCommand(command: string): string {
109+
private static getCommand(command: string): Command | undefined {
121110
for (const candidate of this.getCommandCandidates(command)) {
122-
if (this.checkSpawnable(candidate)) return candidate;
111+
if (this.isSpawnable(candidate)) return candidate;
123112
}
124113
}
125114

126115
private static getGradleCommand() {
127-
return platform() === 'win32' ? `${GRADLE_COMMAND}.bat` : `./${GRADLE_COMMAND}`;
116+
return platform() === 'win32' ? new Command(GRADLE_COMMAND, ".bat", true) : new Command(`./${GRADLE_COMMAND}`);
117+
}
118+
}
119+
120+
export type PlatformDependentSpawnOptions = {
121+
shell?: boolean
122+
}
123+
124+
export class Command {
125+
public readonly command: string
126+
public readonly defaultSpawnOptions: PlatformDependentSpawnOptions
127+
128+
constructor(public readonly cmdPrefix: string, public readonly cmdSuffix: string = "", public readonly shellMode: boolean = false) {
129+
this.command = this.cmdPrefix + this.cmdSuffix;
130+
this.defaultSpawnOptions = this.shellMode ? { shell: true } : {};
131+
}
132+
133+
spawn(args: string[] = [], options: CommonSpawnOptions = {}): ChildProcess {
134+
return spawn(this.command, this.argsForSpawnType(args), { ...options, ...this.defaultSpawnOptions });
135+
}
136+
137+
spawnSync(args: string[] = [], options: CommonSpawnOptions = {}): SpawnSyncReturns<Buffer> {
138+
return spawnSync(this.command, this.argsForSpawnType(args), { ...options, ...this.defaultSpawnOptions });
139+
}
140+
141+
// See https://github.com/nodejs/node/issues/38490
142+
argsForSpawnType(args: string[]): string[] {
143+
return this.shellMode ? args.map(arg => arg.indexOf(" ") !== -1 ? `"${arg}"` : arg) : args;
128144
}
129145
}

src/execution/gaugeExecutor.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class GaugeExecutor extends Disposable {
3737
private postExecute: Function[] = [];
3838
private _disposables: Disposable[] = [];
3939
private gaugeDebugger: GaugeDebugger;
40-
private processors: Array<LineTextProcessor> = new Array();
40+
private processors: Array<LineTextProcessor> = [];
4141

4242
constructor(private gaugeWorkspace: GaugeWorkspace, private cli: CLI) {
4343
super(() => this.dispose());
@@ -67,13 +67,12 @@ export class GaugeExecutor extends Disposable {
6767
const relPath = relative(config.getProject().root(), config.getStatus());
6868
this.preExecute.forEach((f) => { f.call(null, env, relPath); });
6969
this.aborted = false;
70-
let options: SpawnOptions = { cwd: config.getProject().root(), env: env , detached: false };
71-
if (platform() !== 'win32') {
72-
options.detached = true;
73-
} else {
74-
options.shell = true;
75-
}
76-
this.childProcess = spawn(cmd, args, options);
70+
let options: SpawnOptions = {
71+
cwd: config.getProject().root(),
72+
env: env,
73+
detached: platform() !== 'win32'
74+
};
75+
this.childProcess = cmd.spawn(args, options);
7776
this.childProcess.stdout.on('data', this.filterStdoutDataDumpsToTextLines((lineText: string) => {
7877
chan.appendOutBuf(lineText);
7978
lineText.split("\n").forEach((lineText) => {

src/gaugeWorkspace.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,16 @@ export class GaugeWorkspace extends Disposable {
146146
let project = ProjectFactory.get(folder);
147147
if (this._clientsMap.has(project.root())) return;
148148
process.env.GAUGE_IGNORE_RUNNER_BUILD_FAILURES = "true";
149+
let cmd = this.cli.gaugeCommand();
149150
let serverOptions: ServerOptions = {
150-
command: this.cli.gaugeCommand(),
151-
args: ["daemon", "--lsp", "--dir=" + project.root()],
152-
options: { env: { ...process.env, ...project.envs(this.cli) } },
151+
command: cmd.command,
152+
args: cmd.argsForSpawnType(["daemon", "--lsp", "--dir", project.root()]),
153+
options: {
154+
env: { ...process.env, ...project.envs(this.cli) },
155+
...cmd.defaultSpawnOptions,
156+
},
153157
};
154158

155-
if (platform() === "win32") {
156-
serverOptions.options.shell = true;
157-
}
158-
159159
this._launchConfig = workspace.getConfiguration(GAUGE_LAUNCH_CONFIG);
160160
if (this._launchConfig.get(DEBUG_LOG_LEVEL_CONFIG)) {
161161
serverOptions.args.push("-l");

src/init/projectInit.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22

3-
import { CommonSpawnOptions, spawn, spawnSync } from 'child_process';
43
import * as fs from 'fs-extra';
54
import * as path from 'path';
65
import { commands, Disposable, Progress, Uri, window, workspace } from 'vscode';
@@ -68,9 +67,9 @@ export class ProjectInitializer extends Disposable {
6867

6968
private async createFromCommandLine(template: FileListItem, projectFolder: Uri, p: ProgressHandler) {
7069
let args = [GaugeCommands.Init, template.label];
71-
let options: CommonSpawnOptions = { cwd: projectFolder.fsPath, env: process.env, ...CLI.getDefaultSpawnOptions() };
70+
const cmd = this.cli.gaugeCommand();
7271
p.report("Initializing project...");
73-
let proc = spawn(this.cli.gaugeCommand(), args, options);
72+
let proc = cmd.spawn(args, { cwd: projectFolder.fsPath, env: process.env });
7473
proc.addListener('error', async (err) => {
7574
this.handleError(p, "Failed to create template. " + err.message, projectFolder.fsPath);
7675
});
@@ -83,8 +82,7 @@ export class ProjectInitializer extends Disposable {
8382

8483
private async getTemplatesList(): Promise<Array<FileListItem>> {
8584
let args = ["template", "--list", "--machine-readable"];
86-
let options: CommonSpawnOptions = { env: process.env, ...CLI.getDefaultSpawnOptions() };
87-
let cp = spawnSync(this.cli.gaugeCommand(), args, options);
85+
let cp = this.cli.gaugeCommand().spawnSync(args, { env: process.env });
8886
try {
8987
let _templates = JSON.parse(cp.stdout.toString());
9088
return _templates.map((tmpl) => new FileListItem(tmpl.key, tmpl.Description, tmpl.value));

src/project/gaugeProject.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
import { isAbsolute, relative } from 'path';
4-
import { CLI } from '../cli';
4+
import { CLI, Command } from '../cli';
55

66
export class GaugeProject {
77
private readonly _projectRoot: string;
@@ -18,7 +18,7 @@ export class GaugeProject {
1818
}
1919
}
2020

21-
public getExecutionCommand(cli: CLI): string {
21+
public getExecutionCommand(cli: CLI): Command {
2222
return cli.gaugeCommand();
2323
}
2424

0 commit comments

Comments
 (0)