Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@zkochan/cmd-shim

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@zkochan/cmd-shim - npm Package Compare versions

Comparing version 3.1.0 to 4.0.0

90

index.d.ts
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;

@@ -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": {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc