Skip to content

Fix #989 vscode v1_92 breaking api change when spawning .bat or .cmd files #990

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

Merged
merged 11 commits into from
Mar 1, 2025
Merged
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
46 changes: 38 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import { spawn, spawnSync } from 'child_process';
import { CommonSpawnOptions, spawn, spawnSync } from 'child_process';
import { platform } from 'os';
import { window } from 'vscode';
import { GaugeCommands, GRADLE_COMMAND, MAVEN_COMMAND } from './constants';
Expand All @@ -23,12 +23,26 @@ export class CLI {
this._gaugePlugins = manifest.plugins;
}

public static getDefaultSpawnOptions(): CommonSpawnOptions {
// should only deal with platform specific options
let options: CommonSpawnOptions = {};
if(platform() === "win32") {
options.shell = true;
}
return options;
}

public static instance(): CLI {
const gaugeCommand = this.getCommand(GaugeCommands.Gauge);
let mvnCommand = this.getCommand(MAVEN_COMMAND);
let gradleCommand = this.getGradleCommand();
if (!gaugeCommand || gaugeCommand === '') return new CLI(gaugeCommand, {}, mvnCommand, gradleCommand);
let gv = spawnSync(gaugeCommand, [GaugeCommands.Version, GaugeCommands.MachineReadable]);
let options = this.getDefaultSpawnOptions();
let gv = spawnSync(
gaugeCommand,
[GaugeCommands.Version, GaugeCommands.MachineReadable],
options
);
let gaugeVersionInfo;
try {
gaugeVersionInfo = JSON.parse(gv.stdout.toString());
Expand Down Expand Up @@ -67,7 +81,12 @@ export class CLI {
let oc = window.createOutputChannel("Gauge Install");
let chan = new OutputChannel(oc, `Installing gauge ${language} plugin ...\n`, "");
return new Promise((resolve, reject) => {
let childProcess = spawn(this._gaugeCommand, [GaugeCommands.Install, language]);
let options = CLI.getDefaultSpawnOptions();
let childProcess = spawn(
this._gaugeCommand,
[GaugeCommands.Install, language],
options
);
childProcess.stdout.on('data', (chunk) => chan.appendOutBuf(chunk.toString()));
childProcess.stderr.on('data', (chunk) => chan.appendErrBuf(chunk.toString()));
childProcess.on('exit', (code) => {
Expand Down Expand Up @@ -100,12 +119,23 @@ export class CLI {
return `${v}\n${cm}\n\n${plugins}`;
}

private static getCommand(command: string): string {
public static getCommandCandidates(command: string): string[] {
let validExecExt = [""];
if (platform() === 'win32') validExecExt.push(".bat", ".exe", ".cmd");
for (const ext of validExecExt) {
let executable = `${command}${ext}`;
if (!spawnSync(executable).error) return executable;
if (platform() === 'win32') {
validExecExt.push(".bat", ".exe", ".cmd");
}
return validExecExt.map((ext) => `${command}${ext}`);
}

public static checkSpawnable(command: string): boolean {
const result = spawnSync(command, [], CLI.getDefaultSpawnOptions());
return result.status === 0 && !result.error;
}

private static getCommand(command: string): string {
let possiableCommands = this.getCommandCandidates(command);
for (const possiableCommand of possiableCommands) {
if (this.checkSpawnable(possiableCommand)) return possiableCommand;
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/execution/gaugeExecutor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import { ChildProcess, spawn } from 'child_process';
import { ChildProcess, spawn, SpawnOptions } from 'child_process';
import { platform } from 'os';
import {
CancellationTokenSource, commands, Disposable, Position,
Expand Down Expand Up @@ -67,9 +67,11 @@ export class GaugeExecutor extends Disposable {
const relPath = relative(config.getProject().root(), config.getStatus());
this.preExecute.forEach((f) => { f.call(null, env, relPath); });
this.aborted = false;
let options = { cwd: config.getProject().root(), env: env , detached: false};
let options: SpawnOptions = { cwd: config.getProject().root(), env: env , detached: false };
if (platform() !== 'win32') {
options.detached = true;
} else {
options.shell = true;
}
this.childProcess = spawn(cmd, args, options);
this.childProcess.stdout.on('data', this.filterStdoutDataDumpsToTextLines((lineText: string) => {
Expand Down
14 changes: 9 additions & 5 deletions src/gaugeWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
CancellationTokenSource, commands, Disposable, OutputChannel, Uri,
window, workspace, WorkspaceConfiguration, WorkspaceFoldersChangeEvent
} from "vscode";
import { DynamicFeature, LanguageClient, LanguageClientOptions, RevealOutputChannelOn } from "vscode-languageclient/node";
import { DynamicFeature, LanguageClient, LanguageClientOptions, RevealOutputChannelOn, ServerOptions } from "vscode-languageclient/node";
import { CLI } from './cli';
import GaugeConfig from './config/gaugeConfig';
import { GaugeJavaProjectConfig } from './config/gaugeProjectConfig';
Expand Down Expand Up @@ -146,12 +146,16 @@ export class GaugeWorkspace extends Disposable {
let project = ProjectFactory.get(folder);
if (this._clientsMap.has(project.root())) return;
process.env.GAUGE_IGNORE_RUNNER_BUILD_FAILURES = "true";
let serverOptions = {
command: this.cli.gaugeCommand(),
args: ["daemon", "--lsp", "--dir=" + project.root()],
options: { env: { ...process.env, ...project.envs(this.cli) } }
let serverOptions: ServerOptions = {
command: this.cli.gaugeCommand(),
args: ["daemon", "--lsp", "--dir=" + project.root()],
options: { env: { ...process.env, ...project.envs(this.cli) } },
};

if (platform() === "win32") {
serverOptions.options.shell = true;
}

this._launchConfig = workspace.getConfiguration(GAUGE_LAUNCH_CONFIG);
if (this._launchConfig.get(DEBUG_LOG_LEVEL_CONFIG)) {
serverOptions.args.push("-l");
Expand Down
8 changes: 5 additions & 3 deletions src/init/projectInit.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
'use strict';

import { spawn, spawnSync } from 'child_process';
import { CommonSpawnOptions, spawn, spawnSync } from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path';
import { commands, Disposable, Progress, Uri, window, workspace } from 'vscode';
import { CLI } from '../cli';
import { GaugeCommands, GaugeVSCodeCommands, INSTALL_INSTRUCTION_URI, VSCodeCommands } from "../constants";
import { FileListItem } from '../types/fileListItem';
import { platform } from 'os';
export class ProjectInitializer extends Disposable {
private readonly _disposable: Disposable;

Expand Down Expand Up @@ -67,7 +68,7 @@ export class ProjectInitializer extends Disposable {

private async createFromCommandLine(template: FileListItem, projectFolder: Uri, p: ProgressHandler) {
let args = [GaugeCommands.Init, template.label];
let options = { cwd: projectFolder.fsPath, env: process.env };
let options: CommonSpawnOptions = { cwd: projectFolder.fsPath, env: process.env, ...CLI.getDefaultSpawnOptions() };
p.report("Initializing project...");
let proc = spawn(this.cli.gaugeCommand(), args, options);
proc.addListener('error', async (err) => {
Expand All @@ -82,7 +83,8 @@ export class ProjectInitializer extends Disposable {

private async getTemplatesList(): Promise<Array<FileListItem>> {
let args = ["template", "--list", "--machine-readable"];
let cp = spawnSync(this.cli.gaugeCommand(), args, { env: process.env });
let options: CommonSpawnOptions = { env: process.env, ...CLI.getDefaultSpawnOptions() };
let cp = spawnSync(this.cli.gaugeCommand(), args, options);
try {
let _templates = JSON.parse(cp.stdout.toString());
return _templates.map((tmpl) => new FileListItem(tmpl.key, tmpl.Description, tmpl.value));
Expand Down
39 changes: 39 additions & 0 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as assert from 'assert';
import { CLI } from '../src/cli';
import path = require('path');

let testCommandsPath = path.join(__dirname, '..', '..', 'test', 'commands');

suite('CLI', () => {
test('.isPluginInstalled should tell a gauge plugin is installed or not', () => {
Expand Down Expand Up @@ -137,4 +140,40 @@ ruby (1.2.0)`;
assert.ok(!cli.isGaugeVersionGreaterOrEqual('1.3.0'));
done();
});

test('.getCommandCandidates choices all valid', (done) => {
let candidates = CLI.getCommandCandidates('test_command');
const originalPath = process.env.PATH;
process.env.PATH = testCommandsPath;
let invalid_candidates = [];
try {
for (const candidate of candidates) {
if (!CLI.checkSpawnable(candidate)) {
invalid_candidates.push(candidate);
}
}
assert.ok(invalid_candidates.length === 0, `invalid candidates: ${invalid_candidates.join(', ')}, those should be valid`);
} finally {
process.env.PATH = originalPath;
}
done();
});

test('.getCommandCandidates choices are all not valid', (done) => {
let candidates = CLI.getCommandCandidates('test_command_not_found');
const originalPath = process.env.PATH;
process.env.PATH = testCommandsPath;
let valid_candidates = [];
try {
for (const candidate of candidates) {
if (CLI.checkSpawnable(candidate)) {
valid_candidates.push(candidate);
}
}
assert.ok(valid_candidates.length === 0, `valid candidates: ${valid_candidates.join(', ')}, those should not be valid`);
} finally {
process.env.PATH = originalPath;
}
done();
});
});
2 changes: 2 additions & 0 deletions test/commands/test_command
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
echo success
1 change: 1 addition & 0 deletions test/commands/test_command.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
echo hello
2 changes: 2 additions & 0 deletions test/commands/test_command.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
echo success
Binary file added test/commands/test_command.exe
Binary file not shown.
Loading