@zkochan/cmd-shim
Advanced tools
Comparing version 3.1.0 to 4.0.0
interface Options { | ||
/** | ||
* 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. | ||
*/ | ||
declare function cmdShim(src: string, to: string, opts: cmdShim.Options): Promise<void>; | ||
declare namespace cmdShim { | ||
/** | ||
* If a PowerShell script should be created. | ||
* Try to create shims. | ||
* | ||
* @default true | ||
*/ | ||
createPwshFile?: boolean; | ||
/** | ||
* If a Windows Command Prompt script should be created. | ||
* Does nothing if `src` doesn't exist. | ||
* | ||
* @default false | ||
* @param {string} src Path to program (executable or script). | ||
* @param {string} to Path to shims. | ||
* Don't add an extension if you will create multiple types of shims. | ||
* @param {Options} opts Options. | ||
*/ | ||
createCmdFile?: boolean; | ||
function cmdShimIfExists(src: string, to: string, opts: Options): Promise<void>; | ||
/** | ||
* If symbolic links should be preserved. | ||
* | ||
* @default false | ||
*/ | ||
preserveSymlinks?: boolean; | ||
interface Options { | ||
/** | ||
* If a PowerShell script should be created. | ||
* | ||
* @default true | ||
*/ | ||
createPwshFile?: boolean; | ||
/** | ||
* The path to the executable file. | ||
*/ | ||
prog?: string; | ||
/** | ||
* If a Windows Command Prompt script should be created. | ||
* | ||
* @default false | ||
*/ | ||
createCmdFile?: boolean; | ||
/** | ||
* The arguments to initialize the `node` process with. | ||
*/ | ||
args?: string; | ||
/** | ||
* If symbolic links should be preserved. | ||
* | ||
* @default false | ||
*/ | ||
preserveSymlinks?: boolean; | ||
/** | ||
* The value of the $NODE_PATH environment variable. | ||
* | ||
* The single `string` format is only kept for legacy compatibility, | ||
* and the array form should be preferred. | ||
*/ | ||
nodePath?: string | string[]; | ||
/** | ||
* The path to the executable file. | ||
*/ | ||
prog?: string; | ||
/** | ||
* The arguments to initialize the `node` process with. | ||
*/ | ||
args?: string; | ||
/** | ||
* The value of the $NODE_PATH environment variable. | ||
* | ||
* The single `string` format is only kept for legacy compatibility, | ||
* and the array form should be preferred. | ||
*/ | ||
nodePath?: string | string[]; | ||
} | ||
} | ||
declare function cmdShim(src: string, to: string, opts: Options): Promise<void> | ||
declare namespace cmdShim { | ||
function cmdShimIfExists(src: string, to: string, opts: Options): Promise<void> | ||
} | ||
export = cmdShim; |
384
index.js
@@ -15,5 +15,27 @@ 'use strict' | ||
/** | ||
* @typedef {import('./index').Options} Options | ||
* | ||
* @typedef {object} RuntimeInfo Information of runtime and its arguments | ||
* of the script `target`, defined in the shebang of it. | ||
* @property {string|null} [program] If `program` is `null`, the program may | ||
* be a binary executable and can be called from shells by just its path. | ||
* (e.g. `.\foo.exe` in CMD or PowerShell) | ||
* @property {string} additionalArgs Additional arguments embedded in the shebang and passed to `program`. | ||
* `''` if nothing, unlike `program`. | ||
* | ||
* @callback ShimGenerator Callback functions to generate scripts for shims. | ||
* @param {string} src Path to the executable or script. | ||
* @param {string} to Path to the shim(s) that is going to be created. | ||
* @param {Options} opts Options. | ||
* @return {string} Generated script for shim. | ||
* | ||
* @typedef {object} ShimGenExtTuple | ||
* @property {ShimGenerator} generator The shim generator function. | ||
* @property {string} extension The file extension for the shim. | ||
*/ | ||
const fs = require('mz/fs') | ||
const mkdir = require('mkdirp-promise') | ||
const makeDir = require('make-dir') | ||
const path = require('path') | ||
@@ -27,12 +49,52 @@ const isWindows = require('is-windows') | ||
} | ||
/** | ||
* 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'], | ||
['.cmd', 'cmd'], | ||
['.bat', 'cmd'], | ||
['.ps1', 'pwsh'], // not 'powershell' | ||
['.sh', 'sh'] | ||
]) | ||
function cmdShimIfExists (src, to, opts) { | ||
/** | ||
* Try to create shims. | ||
* | ||
* @param {string} src Path to program (executable or script). | ||
* @param {string} to Path to shims. | ||
* Don't add an extension if you will create multiple types of shims. | ||
* @param {Options} opts Options. | ||
* @return {Promise<void>} | ||
* @throws If `src` is missing. | ||
*/ | ||
async function cmdShim (src, to, opts) { | ||
opts = Object.assign({}, DEFAULT_OPTIONS, opts) | ||
return fs.stat(src) | ||
.then(() => cmdShim(src, to, opts)) | ||
.catch(() => {}) | ||
await fs.stat(src) | ||
return cmdShim_(src, to, opts) | ||
} | ||
// Try to unlink, but ignore errors. | ||
// Any problems will surface later. | ||
/** | ||
* Try to create shims. | ||
* | ||
* Does nothing if `src` doesn't exist. | ||
* | ||
* @param {string} src Path to program (executable or script). | ||
* @param {string} to Path to shims. | ||
* Don't add an extension if you will create multiple types of shims. | ||
* @param {Options} opts Options. | ||
* @return {Promise<void>} | ||
*/ | ||
function cmdShimIfExists (src, to, opts) { | ||
return cmdShim(src, to, opts).catch(() => {}) | ||
} | ||
/** | ||
* Try to unlink, but ignore errors. | ||
* Any problems will surface later. | ||
* | ||
* @param {string} path File to be removed. | ||
* @return {Promise<void>} | ||
*/ | ||
function rm (path) { | ||
@@ -42,93 +104,209 @@ return fs.unlink(path).catch(() => {}) | ||
function cmdShim (src, to, opts) { | ||
/** | ||
* Try to create shims **even if `src` is missing**. | ||
* | ||
* @param {string} src Path to program (executable or script). | ||
* @param {string} to Path to shims. | ||
* Don't add an extension if you will create multiple types of shims. | ||
* @param {Options} opts Options. | ||
* | ||
*/ | ||
async function cmdShim_ (src, to, opts) { | ||
opts = Object.assign({}, DEFAULT_OPTIONS, opts) | ||
return fs.stat(src) | ||
.then(() => cmdShim_(src, to, opts)) | ||
const srcRuntimeInfo = await searchScriptRuntime(src) | ||
// 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) | ||
return writeAllShims(src, to, srcRuntimeInfo, opts) | ||
} | ||
function cmdShim_ (src, to, opts) { | ||
return Promise.all([ | ||
rm(to), | ||
rm(`${to}.ps1`), | ||
opts.createCmdFile && rm(`${to}.cmd`) | ||
]) | ||
.then(() => writeShim(src, to, opts)) | ||
/** | ||
* Do processes before **all** shims are created. | ||
* This must be called **only once** for one call of `cmdShim(IfExists)`. | ||
* | ||
* @param {string} target Path of shims that are going to be created. | ||
*/ | ||
function writeShimsPreCommon (target) { | ||
return makeDir(path.dirname(target)) | ||
} | ||
function writeShim (src, to, opts) { | ||
/** | ||
* Write all types (sh & cmd & pwsh) of shims to files. | ||
* Extensions (`.cmd` and `.ps1`) are appended to cmd and pwsh shims. | ||
* | ||
* | ||
* @param {string} src Path to program (executable or script). | ||
* @param {string} to Path to shims **without extensions**. | ||
* Extensions are added for CMD and PowerShell shims. | ||
* @param {RuntimeInfo} srcRuntimeInfo Return value of `await searchScriptRuntime(src)`. | ||
* @param {Options} opts Options. | ||
*/ | ||
function writeAllShims (src, to, srcRuntimeInfo, opts) { | ||
opts = Object.assign({}, DEFAULT_OPTIONS, opts) | ||
const defaultArgs = opts.preserveSymlinks ? '--preserve-symlinks' : '' | ||
// make a cmd file and a sh script | ||
/** @type {ShimGenExtTuple[]} */ | ||
const generatorAndExts = [{ generator: generateShShim, extension: '' }] | ||
if (opts.createCmdFile) { | ||
generatorAndExts.push({ generator: generateCmdShim, extension: '.cmd' }) | ||
} | ||
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 {string} target Path to shim that is going to be created. | ||
*/ | ||
function writeShimPre (target) { | ||
return rm(target) | ||
} | ||
/** | ||
* Do processes after writing the shim. | ||
* | ||
* @param {string} target Path to just created shim. | ||
*/ | ||
function writeShimPost (target) { | ||
// Only chmoding shims as of now. | ||
// Some other processes may be appended. | ||
return chmodShim(target) | ||
} | ||
/** | ||
* Look into runtime (e.g. `node` & `sh` & `pwsh`) and its arguments | ||
* of the target program (script or executable). | ||
* | ||
* @param {string} target Path to the executable or script. | ||
* @return {Promise<RuntimeInfo>} Promise of infomation of runtime of `target`. | ||
*/ | ||
async function searchScriptRuntime (target) { | ||
const data = await fs.readFile(target, 'utf8') | ||
// First, check if the bin is a #! of some sort. | ||
// If not, then assume it's something that'll be compiled, or some other | ||
// sort of script, and just call it directly. | ||
return mkdir(path.dirname(to)) | ||
.then(() => { | ||
return fs.readFile(src, 'utf8') | ||
.then(data => { | ||
const firstLine = data.trim().split(/\r*\n/)[0] | ||
const shebang = firstLine.match(shebangExpr) | ||
if (!shebang) return writeShim_(src, to, Object.assign({}, opts, {args: defaultArgs})) | ||
const prog = shebang[1] | ||
const args = (shebang[2] && ((defaultArgs && (shebang[2] + ' ' + defaultArgs)) || shebang[2])) || defaultArgs | ||
return writeShim_(src, to, Object.assign({}, opts, {prog, args})) | ||
}) | ||
.catch(() => writeShim_(src, to, Object.assign({}, opts, {args: defaultArgs}))) | ||
}) | ||
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] | ||
} | ||
} | ||
function writeShim_ (src, to, opts) { | ||
opts = Object.assign({}, DEFAULT_OPTIONS, opts) | ||
let shTarget = path.relative(path.dirname(to), src) | ||
/** | ||
* Write shim to the file system while executing the pre- and post-processes | ||
* defined in `WriteShimPre` and `WriteShimPost`. | ||
* | ||
* @param {string} src Path to the executable or script. | ||
* @param {string} to Path to the (sh) shim(s) that is going to be created. | ||
* @param {RuntimeInfo} srcRuntimeInfo Result of `await searchScriptRuntime(src)`. | ||
* @param {ShimGenerator} generateShimScript Generator of shim script. | ||
* @param {Options} 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) | ||
await fs.writeFile(to, generateShimScript(src, to, opts), 'utf8') | ||
return writeShimPost(to) | ||
} | ||
/** | ||
* Generate the content of a shim for CMD. | ||
* | ||
* @type {ShimGenerator} | ||
* @param {string} src Path to the executable or script. | ||
* @param {string} to Path to the shim to be created. | ||
* It is highly recommended to end with `.cmd` (or `.bat`). | ||
* @param {Options} opts Options. | ||
* @return {string} 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('\\') | ||
let longProg | ||
let prog = opts.prog | ||
let shProg = prog && prog.split('\\').join('/') | ||
let shLongProg | ||
let pwshProg = shProg && `"${shProg}$exe"` | ||
let pwshLongProg | ||
shTarget = shTarget.split('\\').join('/') | ||
let args = opts.args || '' | ||
let { | ||
win32: nodePath, | ||
posix: shNodePath | ||
} = normalizePathEnvVar(opts.nodePath) | ||
const nodePath = normalizePathEnvVar(opts.nodePath).win32 | ||
if (!prog) { | ||
prog = `"%~dp0\\${target}"` | ||
shProg = `"$basedir/${shTarget}"` | ||
pwshProg = shProg | ||
args = '' | ||
target = '' | ||
shTarget = '' | ||
} else { | ||
longProg = `"%~dp0\\${prog}.exe"` | ||
shLongProg = '"$basedir/' + prog + '"' | ||
pwshLongProg = `"$basedir/${prog}$exe"` | ||
target = `"%~dp0\\${target}"` | ||
shTarget = `"$basedir/${shTarget}"` | ||
} | ||
let cmd | ||
if (opts.createCmdFile) { | ||
// @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" %* | ||
// ) | ||
cmd = nodePath ? `@SET NODE_PATH=${nodePath}\r\n` : '' | ||
if (longProg) { | ||
cmd += '@IF EXIST ' + longProg + ' (\r\n' + | ||
' ' + longProg + ' ' + args + ' ' + target + ' %*\r\n' + | ||
') ELSE (\r\n' + | ||
' @SETLOCAL\r\n' + | ||
' @SET PATHEXT=%PATHEXT:;.JS;=;%\r\n' + | ||
' ' + prog + ' ' + args + ' ' + target + ' %*\r\n' + | ||
')' | ||
} else { | ||
cmd += `@${prog} ${args} ${target} %*\r\n` | ||
} | ||
// @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 = nodePath ? `@SET NODE_PATH=${nodePath}\r\n` : '' | ||
if (longProg) { | ||
cmd += '@IF EXIST ' + longProg + ' (\r\n' + | ||
' ' + longProg + ' ' + args + ' ' + target + ' %*\r\n' + | ||
') ELSE (\r\n' + | ||
' @SETLOCAL\r\n' + | ||
' @SET PATHEXT=%PATHEXT:;.JS;=;%\r\n' + | ||
' ' + prog + ' ' + args + ' ' + target + ' %*\r\n' + | ||
')' | ||
} else { | ||
cmd += `@${prog} ${args} ${target} %*\r\n` | ||
} | ||
return cmd | ||
} | ||
/** | ||
* Generate the content of a shim for (Ba)sh in, for example, Cygwin and MSYS(2). | ||
* | ||
* @type {ShimGenerator} | ||
* @param {string} src Path to the executable or script. | ||
* @param {string} to Path to the shim to be created. | ||
* It is highly recommended to end with `.sh` or to contain no extension. | ||
* @param {Options} opts Options. | ||
* @return {string} 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('/') | ||
let args = opts.args || '' | ||
const shNodePath = normalizePathEnvVar(opts.nodePath).posix | ||
if (!shProg) { | ||
shProg = `"$basedir/${shTarget}"` | ||
args = '' | ||
shTarget = '' | ||
} else { | ||
shLongProg = `"$basedir/${opts.prog}"` | ||
shTarget = `"$basedir/${shTarget}"` | ||
} | ||
// #!/bin/sh | ||
@@ -175,2 +353,34 @@ // basedir=`dirname "$0"` | ||
return sh | ||
} | ||
/** | ||
* Generate the content of a shim for PowerShell. | ||
* | ||
* @type {ShimGenerator} | ||
* @param {string} src Path to the executable or script. | ||
* @param {string} to Path to the shim to be created. | ||
* It is highly recommended to end with `.ps1`. | ||
* @param {Options} opts Options. | ||
* @return {string} 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('/') | ||
let args = opts.args || '' | ||
let normalizedPathEnvVar = normalizePathEnvVar(opts.nodePath) | ||
const nodePath = normalizedPathEnvVar.win32 | ||
const shNodePath = normalizedPathEnvVar.posix | ||
if (!pwshProg) { | ||
pwshProg = `"$basedir/${shTarget}"` | ||
args = '' | ||
shTarget = '' | ||
} else { | ||
pwshLongProg = `"$basedir/${opts.prog}$exe"` | ||
shTarget = `"$basedir/${shTarget}"` | ||
} | ||
// #!/usr/bin/env pwsh | ||
@@ -212,3 +422,3 @@ // $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent | ||
pwsh += '\n' | ||
if (shLongProg) { | ||
if (pwshLongProg) { | ||
pwsh = pwsh + | ||
@@ -232,16 +442,12 @@ '$ret=0\n' + | ||
return Promise.all([ | ||
opts.createCmdFile && fs.writeFile(to + '.cmd', cmd, 'utf8'), | ||
opts.createPwshFile && fs.writeFile(`${to}.ps1`, pwsh, 'utf8'), | ||
fs.writeFile(to, sh, 'utf8') | ||
]) | ||
.then(() => chmodShim(to, opts)) | ||
return pwsh | ||
} | ||
function chmodShim (to, {createCmdFile, createPwshFile}) { | ||
return Promise.all([ | ||
fs.chmod(to, 0o755), | ||
createPwshFile && fs.chmod(`${to}.ps1`, 0o755), | ||
createCmdFile && fs.chmod(`${to}.cmd`, 0o755) | ||
]) | ||
/** | ||
* Chmod just created shim and make it executable | ||
* | ||
* @param {string} to Path to shim. | ||
*/ | ||
function chmodShim (to) { | ||
return fs.chmod(to, 0o755) | ||
} | ||
@@ -256,4 +462,4 @@ | ||
return { | ||
win32: nodePath, | ||
posix: nodePath | ||
win32: '', | ||
posix: '' | ||
} | ||
@@ -260,0 +466,0 @@ } |
{ | ||
"name": "@zkochan/cmd-shim", | ||
"version": "3.1.0", | ||
"version": "4.0.0", | ||
"description": "Used in pnpm for command line application support", | ||
@@ -12,3 +12,3 @@ "author": { | ||
"test:unit": "tape test/*.js", | ||
"test": "standard && npm run test:unit && mos test", | ||
"test": "standard && pnpm run test:unit && mos test", | ||
"md": "mos" | ||
@@ -28,3 +28,3 @@ }, | ||
"is-windows": "^1.0.0", | ||
"mkdirp-promise": "^5.0.1", | ||
"make-dir": "^3.0.0", | ||
"mz": "^2.5.0" | ||
@@ -38,6 +38,6 @@ }, | ||
"tape": "^4.6.2", | ||
"tape-promise": "^3.0.0" | ||
"tape-promise": "^2.0.1" | ||
}, | ||
"engines": { | ||
"node": ">=6" | ||
"node": ">=8.15" | ||
}, | ||
@@ -44,0 +44,0 @@ "mos": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
20610
499
+ Addedmake-dir@^3.0.0
+ Addedmake-dir@3.1.0(transitive)
+ Addedsemver@6.3.1(transitive)
- Removedmkdirp-promise@^5.0.1
- Removedmkdirp@3.0.1(transitive)
- Removedmkdirp-promise@5.0.1(transitive)