'use strict';
cmdShim.ifExists = cmdShimIfExists;
const util_1 = require("util");
const path = require("path");
const isWindows = require("is-windows");
const CMD_EXTENSION = require("cmd-extension");
const shebangExpr = /^#!\s*(?:\/usr\/bin\/env(?:\s+-S\s*)?)?\s*([^ \t]+)(.*)$/;
const DEFAULT_OPTIONS = {
    // Create PowerShell file by default if the option hasn't been specified
    createPwshFile: true,
    createCmdFile: isWindows(),
    fs: require('graceful-fs')
};
/**
 * Map from extensions of files that this module is frequently used for to their runtime.
 * @type {Map<string, string>}
 */
const extensionToProgramMap = new Map([
    ['.js', 'node'],
    ['.cjs', 'node'],
    ['.mjs', 'node'],
    ['.cmd', 'cmd'],
    ['.bat', 'cmd'],
    ['.ps1', 'pwsh'],
    ['.sh', 'sh']
]);
function ingestOptions(opts) {
    const opts_ = { ...DEFAULT_OPTIONS, ...opts };
    const fs = opts_.fs;
    opts_.fs_ = {
        chmod: fs.chmod ? (0, util_1.promisify)(fs.chmod) : (async () => { }),
        mkdir: (0, util_1.promisify)(fs.mkdir),
        readFile: (0, util_1.promisify)(fs.readFile),
        stat: (0, util_1.promisify)(fs.stat),
        unlink: (0, util_1.promisify)(fs.unlink),
        writeFile: (0, util_1.promisify)(fs.writeFile)
    };
    return opts_;
}
/**
 * Try to create shims.
 *
 * @param src Path to program (executable or script).
 * @param to Path to shims.
 * Don't add an extension if you will create multiple types of shims.
 * @param opts Options.
 * @throws If `src` is missing.
 */
async function cmdShim(src, to, opts) {
    const opts_ = ingestOptions(opts);
    await cmdShim_(src, to, opts_);
}
/**
 * Try to create shims.
 *
 * Does nothing if `src` doesn't exist.
 *
 * @param src Path to program (executable or script).
 * @param to Path to shims.
 * Don't add an extension if you will create multiple types of shims.
 * @param opts Options.
 */
function cmdShimIfExists(src, to, opts) {
    return cmdShim(src, to, opts).catch(() => { });
}
/**
 * Try to unlink, but ignore errors.
 * Any problems will surface later.
 *
 * @param path File to be removed.
 */
function rm(path, opts) {
    return opts.fs_.unlink(path).catch(() => { });
}
/**
 * Try to create shims **even if `src` is missing**.
 *
 * @param src Path to program (executable or script).
 * @param to Path to shims.
 * Don't add an extension if you will create multiple types of shims.
 * @param opts Options.
 */
async function cmdShim_(src, to, opts) {
    const srcRuntimeInfo = await searchScriptRuntime(src, opts);
    // Always tries to create all types of shims by calling `writeAllShims` as of now.
    // Append your code here to change the behavior in response to `srcRuntimeInfo`.
    // Create 3 shims for (Ba)sh in Cygwin / MSYS, no extension) & CMD (.cmd) & PowerShell (.ps1)
    await writeShimsPreCommon(to, opts);
    return writeAllShims(src, to, srcRuntimeInfo, opts);
}
/**
 * Do processes before **all** shims are created.
 * This must be called **only once** for one call of `cmdShim(IfExists)`.
 *
 * @param target Path of shims that are going to be created.
 */
function writeShimsPreCommon(target, opts) {
    return opts.fs_.mkdir(path.dirname(target), { recursive: true });
}
/**
 * Write all types (sh & cmd & pwsh) of shims to files.
 * Extensions (`.cmd` and `.ps1`) are appended to cmd and pwsh shims.
 *
 *
 * @param src Path to program (executable or script).
 * @param to Path to shims **without extensions**.
 * Extensions are added for CMD and PowerShell shims.
 * @param srcRuntimeInfo Return value of `await searchScriptRuntime(src)`.
 * @param opts Options.
 */
function writeAllShims(src, to, srcRuntimeInfo, opts) {
    const opts_ = ingestOptions(opts);
    const generatorAndExts = [{ generator: generateShShim, extension: '' }];
    if (opts_.createCmdFile) {
        generatorAndExts.push({ generator: generateCmdShim, extension: CMD_EXTENSION });
    }
    if (opts_.createPwshFile) {
        generatorAndExts.push({ generator: generatePwshShim, extension: '.ps1' });
    }
    return Promise.all(generatorAndExts.map((generatorAndExt) => writeShim(src, to + generatorAndExt.extension, srcRuntimeInfo, generatorAndExt.generator, opts_)));
}
/**
 * Do processes before writing shim.
 *
 * @param target Path to shim that is going to be created.
 */
function writeShimPre(target, opts) {
    return rm(target, opts);
}
/**
 * Do processes after writing the shim.
 *
 * @param target Path to just created shim.
 */
function writeShimPost(target, opts) {
    // Only chmoding shims as of now.
    // Some other processes may be appended.
    return chmodShim(target, opts);
}
/**
 * Look into runtime (e.g. `node` & `sh` & `pwsh`) and its arguments
 * of the target program (script or executable).
 *
 * @param target Path to the executable or script.
 * @return Promise of infomation of runtime of `target`.
 */
async function searchScriptRuntime(target, opts) {
    try {
        const data = await opts.fs_.readFile(target, 'utf8');
        // First, check if the bin is a #! of some sort.
        const firstLine = data.trim().split(/\r*\n/)[0];
        const shebang = firstLine.match(shebangExpr);
        if (!shebang) {
            // If not, infer script type from its extension.
            // If the inference fails, it's something that'll be compiled, or some other
            // sort of script, and just call it directly.
            const targetExtension = path.extname(target).toLowerCase();
            return {
                // undefined if extension is unknown but it's converted to null.
                program: extensionToProgramMap.get(targetExtension) || null,
                additionalArgs: ''
            };
        }
        return {
            program: shebang[1],
            additionalArgs: shebang[2]
        };
    }
    catch (err) {
        if (!isWindows() || err.code !== 'ENOENT')
            throw err;
        if (await opts.fs_.stat(`${target}${getExeExtension()}`)) {
            return {
                program: null,
                additionalArgs: '',
            };
        }
        throw err;
    }
}
function getExeExtension() {
    let cmdExtension;
    if (process.env.PATHEXT) {
        cmdExtension = process.env.PATHEXT
            .split(path.delimiter)
            .find(ext => ext.toLowerCase() === '.exe');
    }
    return cmdExtension || '.exe';
}
/**
 * Write shim to the file system while executing the pre- and post-processes
 * defined in `WriteShimPre` and `WriteShimPost`.
 *
 * @param src Path to the executable or script.
 * @param to Path to the (sh) shim(s) that is going to be created.
 * @param srcRuntimeInfo Result of `await searchScriptRuntime(src)`.
 * @param generateShimScript Generator of shim script.
 * @param opts Other options.
 */
async function writeShim(src, to, srcRuntimeInfo, generateShimScript, opts) {
    const defaultArgs = opts.preserveSymlinks ? '--preserve-symlinks' : '';
    // `Array.prototype.filter` removes ''.
    // ['--foo', '--bar'].join(' ') and [].join(' ') returns '--foo --bar' and '' respectively.
    const args = [srcRuntimeInfo.additionalArgs, defaultArgs].filter(arg => arg).join(' ');
    opts = Object.assign({}, opts, {
        prog: srcRuntimeInfo.program,
        args: args
    });
    await writeShimPre(to, opts);
    await opts.fs_.writeFile(to, generateShimScript(src, to, opts), 'utf8');
    return writeShimPost(to, opts);
}
/**
 * Generate the content of a shim for CMD.
 *
 * @param src Path to the executable or script.
 * @param to Path to the shim to be created.
 * It is highly recommended to end with `.cmd` (or `.bat`).
 * @param opts Options.
 * @return The content of shim.
 */
function generateCmdShim(src, to, opts) {
    // `shTarget` is not used to generate the content.
    const shTarget = path.relative(path.dirname(to), src);
    let target = shTarget.split('/').join('\\');
    const quotedPathToTarget = path.isAbsolute(target) ? `"${target}"` : `"%~dp0\\${target}"`;
    let longProg;
    let prog = opts.prog;
    let args = opts.args || '';
    const nodePath = normalizePathEnvVar(opts.nodePath).win32;
    const prependToPath = normalizePathEnvVar(opts.prependToPath).win32;
    if (!prog) {
        prog = quotedPathToTarget;
        args = '';
        target = '';
    }
    else if (prog === 'node' && opts.nodeExecPath) {
        prog = `"${opts.nodeExecPath}"`;
        target = quotedPathToTarget;
    }
    else {
        longProg = `"%~dp0\\${prog}.exe"`;
        target = quotedPathToTarget;
    }
    let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
    // @IF EXIST "%~dp0\node.exe" (
    //   "%~dp0\node.exe" "%~dp0\.\node_modules\npm\bin\npm-cli.js" %*
    // ) ELSE (
    //   SETLOCAL
    //   SET PATHEXT=%PATHEXT:;.JS;=;%
    //   node "%~dp0\.\node_modules\npm\bin\npm-cli.js" %*
    // )
    let cmd = '@SETLOCAL\r\n';
    if (prependToPath) {
        cmd += `@SET "PATH=${prependToPath}:%PATH%"\r\n`;
    }
    if (nodePath) {
        cmd += `\
@IF NOT DEFINED NODE_PATH (\r
  @SET "NODE_PATH=${nodePath}"\r
) ELSE (\r
  @SET "NODE_PATH=%NODE_PATH%;${nodePath}"\r
)\r
`;
    }
    if (longProg) {
        cmd += `\
@IF EXIST ${longProg} (\r
  ${longProg} ${args} ${target} ${progArgs}%*\r
) ELSE (\r
  @SET PATHEXT=%PATHEXT:;.JS;=;%\r
  ${prog} ${args} ${target} ${progArgs}%*\r
)\r
`;
    }
    else {
        cmd += `@${prog} ${args} ${target} ${progArgs}%*\r\n`;
    }
    return cmd;
}
/**
 * Generate the content of a shim for (Ba)sh in, for example, Cygwin and MSYS(2).
 *
 * @param src Path to the executable or script.
 * @param to Path to the shim to be created.
 * It is highly recommended to end with `.sh` or to contain no extension.
 * @param opts Options.
 * @return The content of shim.
 */
function generateShShim(src, to, opts) {
    let shTarget = path.relative(path.dirname(to), src);
    let shProg = opts.prog && opts.prog.split('\\').join('/');
    let shLongProg;
    shTarget = shTarget.split('\\').join('/');
    const quotedPathToTarget = path.isAbsolute(shTarget) ? `"${shTarget}"` : `"$basedir/${shTarget}"`;
    let args = opts.args || '';
    const shNodePath = normalizePathEnvVar(opts.nodePath).posix;
    if (!shProg) {
        shProg = quotedPathToTarget;
        args = '';
        shTarget = '';
    }
    else if (opts.prog === 'node' && opts.nodeExecPath) {
        shProg = `"${opts.nodeExecPath}"`;
        shTarget = quotedPathToTarget;
    }
    else {
        shLongProg = `"$basedir/${opts.prog}"`;
        shTarget = quotedPathToTarget;
    }
    let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
    // #!/bin/sh
    // basedir=`dirname "$0"`
    //
    // case `uname` in
    //     *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
    // esac
    //
    // export NODE_PATH="<nodepath>"
    //
    // if [ -x "$basedir/node.exe" ]; then
    //   exec "$basedir/node.exe" "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
    // else
    //   exec node "$basedir/node_modules/npm/bin/npm-cli.js" "$@"
    // fi
    let sh = `\
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")

case \`uname\` in
    *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;;
esac

`;
    if (opts.prependToPath) {
        sh += `\
export PATH="${opts.prependToPath}:$PATH"
`;
    }
    if (shNodePath) {
        sh += `\
if [ -z "$NODE_PATH" ]; then
  export NODE_PATH="${shNodePath}"
else
  export NODE_PATH="$NODE_PATH:${shNodePath}"
fi
`;
    }
    if (shLongProg) {
        sh += `\
if [ -x ${shLongProg} ]; then
  exec ${shLongProg} ${args} ${shTarget} ${progArgs}"$@"
else
  exec ${shProg} ${args} ${shTarget} ${progArgs}"$@"
fi
`;
    }
    else {
        sh += `\
${shProg} ${args} ${shTarget} ${progArgs}"$@"
exit $?
`;
    }
    return sh;
}
/**
 * Generate the content of a shim for PowerShell.
 *
 * @param src Path to the executable or script.
 * @param to Path to the shim to be created.
 * It is highly recommended to end with `.ps1`.
 * @param opts Options.
 * @return The content of shim.
 */
function generatePwshShim(src, to, opts) {
    let shTarget = path.relative(path.dirname(to), src);
    const shProg = opts.prog && opts.prog.split('\\').join('/');
    let pwshProg = shProg && `"${shProg}$exe"`;
    let pwshLongProg;
    shTarget = shTarget.split('\\').join('/');
    const quotedPathToTarget = path.isAbsolute(shTarget) ? `"${shTarget}"` : `"$basedir/${shTarget}"`;
    let args = opts.args || '';
    let normalizedNodePathEnvVar = normalizePathEnvVar(opts.nodePath);
    const nodePath = normalizedNodePathEnvVar.win32;
    const shNodePath = normalizedNodePathEnvVar.posix;
    let normalizedPrependPathEnvVar = normalizePathEnvVar(opts.prependToPath);
    const prependPath = normalizedPrependPathEnvVar.win32;
    const shPrependPath = normalizedPrependPathEnvVar.posix;
    if (!pwshProg) {
        pwshProg = quotedPathToTarget;
        args = '';
        shTarget = '';
    }
    else if (opts.prog === 'node' && opts.nodeExecPath) {
        pwshProg = `"${opts.nodeExecPath}"`;
        shTarget = quotedPathToTarget;
    }
    else {
        pwshLongProg = `"$basedir/${opts.prog}$exe"`;
        shTarget = quotedPathToTarget;
    }
    let progArgs = opts.progArgs ? `${opts.progArgs.join(` `)} ` : '';
    // #!/usr/bin/env pwsh
    // $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
    //
    // $ret=0
    // $exe = ""
    // if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
    //   # Fix case when both the Windows and Linux builds of Node
    //   # are installed in the same directory
    //   $exe = ".exe"
    // }
    // if (Test-Path "$basedir/node") {
    //   # Support pipeline input
    //   if ($MyInvocation.ExpectingInput) {
    //     $input | & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
    //   } else {
    //     & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
    //   }
    //   $ret=$LASTEXITCODE
    // } else {
    //   # Support pipeline input
    //   if ($MyInvocation.ExpectingInput) {
    //     $input | & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
    //   } else {
    //     & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
    //   }
    //   $ret=$LASTEXITCODE
    // }
    // exit $ret
    let pwsh = `\
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent

$exe=""
${(nodePath || prependPath) ? '$pathsep=":"\n' : ''}\
${nodePath ? `\
$env_node_path=$env:NODE_PATH
$new_node_path="${nodePath}"
` : ''}\
${prependPath ? `\
$env_path=$env:PATH
$prepend_path="${prependPath}"
` : ''}\
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
  # Fix case when both the Windows and Linux builds of Node
  # are installed in the same directory
  $exe=".exe"
${(nodePath || prependPath) ? '  $pathsep=";"\n' : ''}\
}`;
    if (shNodePath || shPrependPath) {
        pwsh += `\
 else {
${shNodePath ? `  $new_node_path="${shNodePath}"\n` : ''}\
${shPrependPath ? `  $prepend_path="${shPrependPath}"\n` : ''}\
}
`;
    }
    if (shNodePath) {
        pwsh += `\
if ([string]::IsNullOrEmpty($env_node_path)) {
  $env:NODE_PATH=$new_node_path
} else {
  $env:NODE_PATH="$env_node_path$pathsep$new_node_path"
}
`;
    }
    if (opts.prependToPath) {
        pwsh += `
$env:PATH="$prepend_path$pathsep$env:PATH"
`;
    }
    if (pwshLongProg) {
        pwsh += `
$ret=0
if (Test-Path ${pwshLongProg}) {
  # Support pipeline input
  if ($MyInvocation.ExpectingInput) {
    $input | & ${pwshLongProg} ${args} ${shTarget} ${progArgs}$args
  } else {
    & ${pwshLongProg} ${args} ${shTarget} ${progArgs}$args
  }
  $ret=$LASTEXITCODE
} else {
  # Support pipeline input
  if ($MyInvocation.ExpectingInput) {
    $input | & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
  } else {
    & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
  }
  $ret=$LASTEXITCODE
}
${nodePath ? '$env:NODE_PATH=$env_node_path\n' : ''}\
${prependPath ? '$env:PATH=$env_path\n' : ''}\
exit $ret
`;
    }
    else {
        pwsh += `
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
  $input | & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
} else {
  & ${pwshProg} ${args} ${shTarget} ${progArgs}$args
}
${nodePath ? '$env:NODE_PATH=$env_node_path\n' : ''}\
${prependPath ? '$env:PATH=$env_path\n' : ''}\
exit $LASTEXITCODE
`;
    }
    return pwsh;
}
/**
 * Chmod just created shim and make it executable
 *
 * @param to Path to shim.
 */
function chmodShim(to, opts) {
    return opts.fs_.chmod(to, 0o755);
}
function normalizePathEnvVar(nodePath) {
    if (!nodePath || !nodePath.length) {
        return {
            win32: '',
            posix: ''
        };
    }
    let split = (typeof nodePath === 'string' ? nodePath.split(path.delimiter) : Array.from(nodePath));
    let result = {};
    for (let i = 0; i < split.length; i++) {
        const win32 = split[i].split('/').join('\\');
        const posix = isWindows() ? split[i].split('\\').join('/').replace(/^([^:\\/]*):/, (_, $1) => `/mnt/${$1.toLowerCase()}`) : split[i];
        result.win32 = result.win32 ? `${result.win32};${win32}` : win32;
        result.posix = result.posix ? `${result.posix}:${posix}` : posix;
        result[i] = { win32, posix };
    }
    return result;
}
module.exports = cmdShim;
//# sourceMappingURL=index.js.map