Skip to content

Commit

Permalink
Find Windows executable (#284)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrmarti committed Apr 11, 2024
1 parent ebe4531 commit 4432f30
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 3 deletions.
8 changes: 5 additions & 3 deletions common/src/dev-container-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from 'path';
import {env} from 'process';
import {promisify} from 'util';
import {ExecFunction} from './exec';
import {findWindowsExecutable} from './windows';

const cliVersion = "0"; // Use 'latest' to get latest CLI version, or pin to specific version e.g. '0.14.1' if required

Expand All @@ -27,7 +28,8 @@ function getSpecCliInfo() {

async function isCliInstalled(exec: ExecFunction): Promise<boolean> {
try {
const {exitCode} = await exec(getSpecCliInfo().command, ['--help'], {
const command = await findWindowsExecutable(getSpecCliInfo().command);
const {exitCode} = await exec(command, ['--help'], {
silent: true,
});
return exitCode === 0;
Expand Down Expand Up @@ -121,7 +123,7 @@ async function runSpecCliJsonCommand<T>(options: {
err: data => options.log(data),
env: options.env ? {...process.env, ...options.env} : process.env,
};
const command = getSpecCliInfo().command;
const command = await findWindowsExecutable(getSpecCliInfo().command);
console.log(`About to run ${command} ${options.args.join(' ')}`); // TODO - take an output arg to allow GH to use core.info
await spawn(command, options.args, spawnOptions);

Expand All @@ -138,7 +140,7 @@ async function runSpecCliNonJsonCommand(options: {
err: data => options.log(data),
env: options.env ? {...process.env, ...options.env} : process.env,
};
const command = getSpecCliInfo().command;
const command = await findWindowsExecutable(getSpecCliInfo().command);
console.log(`About to run ${command} ${options.args.join(' ')}`); // TODO - take an output arg to allow GH to use core.info
const result = await spawn(command, options.args, spawnOptions);
return result.code
Expand Down
86 changes: 86 additions & 0 deletions common/src/windows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as path from 'path';
import * as fs from 'fs';

// From Dev Containers CLI
export async function findWindowsExecutable(command: string): Promise<string> {
if (process.platform !== 'win32') {
return command;
}

// If we have an absolute path then we take it.
if (path.isAbsolute(command)) {
return await findWindowsExecutableWithExtension(command) || command;
}
const cwd = process.cwd();
if (/[/\\]/.test(command)) {
// We have a directory and the directory is relative (see above). Make the path absolute
// to the current working directory.
const fullPath = path.join(cwd, command);
return await findWindowsExecutableWithExtension(fullPath) || fullPath;
}
let pathValue: string | undefined = undefined;
let paths: string[] | undefined = undefined;
const env = process.env;
// Path can be named in many different ways and for the execution it doesn't matter
for (let key of Object.keys(env)) {
if (key.toLowerCase() === 'path') {
const value = env[key];
if (typeof value === 'string') {
pathValue = value;
paths = value.split(path.delimiter)
.filter(Boolean);
}
break;
}
}
// No PATH environment. Bail out.
if (paths === void 0 || paths.length === 0) {
const err = new Error(`No PATH to look up executable '${command}'.`);
(err as any).code = 'ENOENT';
throw err;
}
// We have a simple file name. We get the path variable from the env
// and try to find the executable on the path.
for (let pathEntry of paths) {
// The path entry is absolute.
let fullPath: string;
if (path.isAbsolute(pathEntry)) {
fullPath = path.join(pathEntry, command);
} else {
fullPath = path.join(cwd, pathEntry, command);
}
const withExtension = await findWindowsExecutableWithExtension(fullPath);
if (withExtension) {
return withExtension;
}
}
// Not found in PATH. Bail out.
const err = new Error(`Exectuable '${command}' not found on PATH '${pathValue}'.`);
(err as any).code = 'ENOENT';
throw err;
}

const pathext = process.env.PATHEXT;
const executableExtensions = pathext ? pathext.toLowerCase().split(';') : ['.com', '.exe', '.bat', '.cmd'];

async function findWindowsExecutableWithExtension(fullPath: string) {
if (executableExtensions.indexOf(path.extname(fullPath)) !== -1) {
return await isFile(fullPath) ? fullPath : undefined;
}
for (const ext of executableExtensions) {
const withExtension = fullPath + ext;
if (await isFile(withExtension)) {
return withExtension;
}
}
return undefined;
}

function isFile(filepath: string): Promise<boolean> {
return new Promise(r => fs.stat(filepath, (err, stat) => r(!err && stat.isFile())));
}

0 comments on commit 4432f30

Please sign in to comment.