diff --git a/.config/babel.config.js b/.config/babel.config.js index 95aecbc..f84a6a3 100644 --- a/.config/babel.config.js +++ b/.config/babel.config.js @@ -1,7 +1,5 @@ 'use strict' -const { isEsmId } = require('../scripts/utils/packages') - module.exports = { plugins: [ '@babel/plugin-proposal-export-default-from', @@ -15,25 +13,6 @@ module.exports = { regenerator: false, version: '^7.25.7' } - ], - [ - '@babel/plugin-transform-modules-commonjs', - { - allowTopLevelThis: true, - importInterop: (specifier, requestingFilename) => { - if (requestingFilename) { - const specIsEsm = isEsmId(specifier, requestingFilename) - const parentIsEsm = isEsmId(requestingFilename) - if (specIsEsm && parentIsEsm) { - return 'none' - } - if (specIsEsm) { - return 'babel' - } - } - return 'node' - } - } ] ] } diff --git a/.config/rollup.base.config.mjs b/.config/rollup.base.config.mjs index 2a08193..af41348 100644 --- a/.config/rollup.base.config.mjs +++ b/.config/rollup.base.config.mjs @@ -53,6 +53,8 @@ const { const SOCKET_INTEROP = '_socketInterop' +const constantsSrcPath = path.join(rootSrcPath, 'constants.ts') + const builtinAliases = builtinModules.reduce((o, n) => { o[n] = `node:${n}` return o @@ -251,6 +253,7 @@ export default function baseConfig(extendConfig = {}) { } }), commonjs({ + defaultIsModuleExports: true, extensions: ['.cjs', '.js', '.ts', `.ts${ROLLUP_ENTRY_SUFFIX}`], ignoreDynamicRequires: true, ignoreGlobal: true, @@ -273,7 +276,7 @@ function ${SOCKET_INTEROP}(e) { let c = 0 for (const k in e ?? {}) { c = c === 0 && k === 'default' ? 1 : 0 - if (!c) break + if (!c && k !== '__esModule') break } return c ? e.default : e }` @@ -293,8 +296,16 @@ function ${SOCKET_INTEROP}(e) { ).map(o => ({ ...o, chunkFileNames: '[name].js', - manualChunks: id_ => - normalizeId(id_).includes(SLASH_NODE_MODULES_SLASH) ? 'vendor' : null + manualChunks: id_ => { + const id = normalizeId(id_) + if (id === constantsSrcPath) { + return 'constants' + } + if (id.includes(SLASH_NODE_MODULES_SLASH)) { + return 'vendor' + } + return null + } })) // Replace hard-coded absolute paths in source with hard-coded relative paths. diff --git a/.config/rollup.dist.config.mjs b/.config/rollup.dist.config.mjs index 0e4eef9..95857fe 100644 --- a/.config/rollup.dist.config.mjs +++ b/.config/rollup.dist.config.mjs @@ -1,11 +1,4 @@ -import { - chmodSync, - copyFileSync, - existsSync, - readFileSync, - rmSync, - writeFileSync -} from 'node:fs' +import { existsSync, rmSync, writeFileSync } from 'node:fs' import path from 'node:path' import { globSync as tinyGlobSync } from 'tinyglobby' @@ -35,33 +28,14 @@ const { rootSrcPath } = constants -const CONSTANTS_JS = 'constants.js' +const CONSTANTS_STUB_CODE = `'use strict'\n\nmodule.exports = require('../constants.js')\n` +const distConstantsPath = path.join(rootDistPath, 'constants.js') const distModuleSyncPath = path.join(rootDistPath, 'module-sync') const distRequirePath = path.join(rootDistPath, 'require') -const binBasenames = ['cli.js', 'npm-cli.js', 'npx-cli.js'] const editablePkgJson = readPackageJsonSync(rootPath, { editable: true }) -function copyConstantsModuleSync(srcPath, destPath) { - copyFileSync( - path.join(srcPath, CONSTANTS_JS), - path.join(destPath, CONSTANTS_JS) - ) -} - -function modifyConstantsModuleExportsSync(distPath) { - const filepath = path.join(distPath, CONSTANTS_JS) - let code = readFileSync(filepath, 'utf8') - code = code - // Remove @rollup/commonjs interop from code. - .replace(/var constants\$\d+ = {};?\n+/, '') - .replace(/Object\.defineProperty\(constants\$\d+[\s\S]+?\}\);?\n/, '') - .replace(/^(?:exports.[$\w]+|[$\w]+\.default)\s*=.*(?:\n|$)/gm, '') - code = code + 'module.exports = constants\n' - writeFileSync(filepath, code, 'utf8') -} - function removeDtsFilesSync(distPath) { for (const filepath of tinyGlobSync(['**/*.d.ts'], { absolute: true, @@ -71,21 +45,6 @@ function removeDtsFilesSync(distPath) { } } -function rewriteConstantsModuleSync(distPath) { - writeFileSync( - path.join(distPath, CONSTANTS_JS), - `'use strict'\n\nmodule.exports = require('../constants.js')\n`, - 'utf8' - ) -} - -function setBinPermsSync(distPath) { - for (const binBasename of binBasenames) { - // Make file chmod +x. - chmodSync(path.join(distPath, binBasename), 0o755) - } -} - function updateDepStatsSync(depStats) { const { content: pkgJson } = editablePkgJson const oldDepStats = existsSync(depStatsPath) @@ -129,8 +88,7 @@ export default () => { const moduleSyncConfig = baseConfig({ input: { cli: `${rootSrcPath}/cli.ts`, - 'npm-cli': `${rootSrcPath}/shadow/npm-cli.ts`, - 'npx-cli': `${rootSrcPath}/shadow/npx-cli.ts`, + 'shadow-bin': `${rootSrcPath}/shadow/shadow-bin.ts`, 'npm-injection': `${rootSrcPath}/shadow/npm-injection.ts` }, output: [ @@ -163,11 +121,14 @@ export default () => { }, plugins: [ { - writeBundle() { - setBinPermsSync(distModuleSyncPath) - copyConstantsModuleSync(distModuleSyncPath, rootDistPath) - modifyConstantsModuleExportsSync(rootDistPath) - rewriteConstantsModuleSync(distModuleSyncPath) + generateBundle(_options, bundle) { + if (bundle['constants.js']) { + let { code } = bundle['constants.js'] + // Remove @rollup/commonjs interop from code. + code = code.replace(/^exports.constants = constants;\n/m, '') + writeFileSync(distConstantsPath, code, 'utf8') + bundle['constants.js'].code = CONSTANTS_STUB_CODE + } } } ] @@ -176,8 +137,7 @@ export default () => { const requireConfig = baseConfig({ input: { cli: `${rootSrcPath}/cli.ts`, - 'npm-cli': `${rootSrcPath}/shadow/npm-cli.ts`, - 'npx-cli': `${rootSrcPath}/shadow/npx-cli.ts`, + 'shadow-bin': `${rootSrcPath}/shadow/shadow-bin.ts`, 'npm-injection': `${rootSrcPath}/shadow/npm-injection.ts` }, output: [ @@ -192,10 +152,13 @@ export default () => { ], plugins: [ { + generateBundle(_options, bundle) { + if (bundle['constants.js']) { + bundle['constants.js'].code = CONSTANTS_STUB_CODE + } + }, writeBundle() { - setBinPermsSync(distRequirePath) removeDtsFilesSync(distRequirePath) - rewriteConstantsModuleSync(distRequirePath) updateDepStatsSync(requireConfig.meta.depStats) } } diff --git a/.dep-stats.json b/.dep-stats.json index a7023cf..e9a2b1c 100644 --- a/.dep-stats.json +++ b/.dep-stats.json @@ -12,6 +12,7 @@ "blessed-contrib": "^4.11.0", "browserslist": "4.24.2", "chalk-table": "^1.0.2", + "cmd-shim": "^7.0.0", "hpagent": "^1.2.0", "ignore": "^6.0.2", "micromatch": "^4.0.8", @@ -51,6 +52,7 @@ "blessed-contrib": "^4.11.0", "browserslist": "4.24.2", "chalk-table": "^1.0.2", + "cmd-shim": "^7.0.0", "has-flag": "^4.0.0", "hpagent": "^1.2.0", "ignore": "^6.0.2", diff --git a/bin/npm-cli.js b/bin/npm-cli.js index fbbd157..22442ac 100755 --- a/bin/npm-cli.js +++ b/bin/npm-cli.js @@ -1,5 +1,6 @@ #!/usr/bin/env node 'use strict' -const constants = require('../dist/constants') -require(`../dist/${constants.DIST_TYPE}/npm-cli.js`) +const constants = require('../dist/constants.js') +const shadowBin = require(`../dist/${constants.DIST_TYPE}/shadow-bin.js`) +shadowBin('npm') diff --git a/bin/npx-cli.js b/bin/npx-cli.js index 2f052ac..e7eed79 100755 --- a/bin/npx-cli.js +++ b/bin/npx-cli.js @@ -1,5 +1,6 @@ #!/usr/bin/env node 'use strict' -const constants = require('../dist/constants') -require(`../dist/${constants.DIST_TYPE}/npx-cli.js`) +const constants = require('../dist/constants.js') +const shadowBin = require(`../dist/${constants.DIST_TYPE}/shadow-bin.js`) +shadowBin('npx') diff --git a/bin/shadow/module-sync/npm b/bin/shadow/module-sync/npm deleted file mode 100755 index 04adf79..0000000 --- a/bin/shadow/module-sync/npm +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node -'use strict' -require('../../dist/module-sync/npm-cli.js') diff --git a/bin/shadow/module-sync/npx b/bin/shadow/module-sync/npx deleted file mode 100755 index a28e933..0000000 --- a/bin/shadow/module-sync/npx +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node -'use strict' -require('../../dist/module-sync/npx-cli.js') diff --git a/bin/shadow/require/npm b/bin/shadow/require/npm deleted file mode 100755 index bf83edd..0000000 --- a/bin/shadow/require/npm +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node -'use strict' -require('../../dist/require/npm-cli.js') diff --git a/bin/shadow/require/npx b/bin/shadow/require/npx deleted file mode 100755 index 7d98973..0000000 --- a/bin/shadow/require/npx +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node -'use strict' -require('../../dist/require/npx-cli.js') diff --git a/eslint.config.js b/eslint.config.js index fc449c9..a87e6bc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -176,6 +176,7 @@ module.exports = [ rules: { ...nodePlugin.configs['flat/recommended-script'].rules, 'n/exports-style': ['error', 'module.exports'], + 'n/no-missing-require': ['off'], // The n/no-unpublished-bin rule does does not support non-trivial glob // patterns used in package.json "files" fields. In those cases we simplify // the glob patterns used. diff --git a/package-lock.json b/package-lock.json index 7605211..ec944f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "blessed-contrib": "^4.11.0", "browserslist": "4.24.2", "chalk-table": "^1.0.2", + "cmd-shim": "^7.0.0", "has-flag": "^4.0.0", "hpagent": "^1.2.0", "ignore": "^6.0.2", @@ -45,7 +46,6 @@ "@babel/plugin-proposal-export-default-from": "^7.25.9", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", "@babel/plugin-transform-runtime": "^7.25.9", "@babel/preset-env": "^7.26.0", "@babel/preset-typescript": "^7.26.0", @@ -59,6 +59,7 @@ "@rollup/pluginutils": "^5.1.3", "@tapjs/run": "^4.0.1", "@types/blessed": "^0.1.25", + "@types/cmd-shim": "^5.0.2", "@types/micromatch": "^4.0.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", @@ -4174,9 +4175,9 @@ } }, "node_modules/@socketregistry/yocto-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@socketregistry/yocto-spinner/-/yocto-spinner-1.0.1.tgz", - "integrity": "sha512-mU2QKlQKM6ja5NcRJmUrRuLdP0XITavhu7vi5G8wp0ZEg1LFI5iAHjfMLToEw5CdRJ1M/KV45bbUhnwO+NtH/w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@socketregistry/yocto-spinner/-/yocto-spinner-1.0.2.tgz", + "integrity": "sha512-P7N3M1hVqiCFvy2+SThjnXs7jzckWdaQJTq+wyIlze7wsQ5Q5Af6UrfCcSW7Ct5ov4rkDBIAwVFLHaL2TB+WOA==", "license": "MIT", "dependencies": { "yoctocolors-cjs": "^2.1.2" @@ -5910,6 +5911,13 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/cmd-shim": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/cmd-shim/-/cmd-shim-5.0.2.tgz", + "integrity": "sha512-Pnee6lEDnxqVmV0SBKGmAFKCmdZtI7sIYI3qCo5iNIZ1SYNspDFwWVJll8F3zvl0Ap/a/XllHiaV8sA9UTjdeA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/configstore": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/configstore/-/configstore-6.0.2.tgz", diff --git a/package.json b/package.json index 1dcce13..1a6159b 100644 --- a/package.json +++ b/package.json @@ -21,18 +21,15 @@ "exports": { "./bin/cli.js": { "types": "./dist/module-sync/cli.d.ts", - "module-sync": "./dist/module-sync/cli.js", - "require": "./dist/require/cli.js" + "default": "./dist/cli.js" }, "./bin/npm-cli.js": { "types": "./dist/module-sync/npm-cli.d.ts", - "module-sync": "./dist/module-sync/npm-cli.js", - "require": "./dist/require/npm-cli.js" + "default": "./dist/npm-cli.js" }, "./bin/npx-cli.js": { "types": "./dist/module-sync/npx-cli.d.ts", - "module-sync": "./dist/module-sync/npx-cli.js", - "require": "./dist/require/npx-cli.js" + "default": "./dist/npx-cli.js" }, "./package.json": "./package.json", "./translations.json": "./translations.json" @@ -70,6 +67,7 @@ "blessed-contrib": "^4.11.0", "browserslist": "4.24.2", "chalk-table": "^1.0.2", + "cmd-shim": "^7.0.0", "has-flag": "^4.0.0", "hpagent": "^1.2.0", "ignore": "^6.0.2", @@ -89,7 +87,6 @@ "@babel/plugin-proposal-export-default-from": "^7.25.9", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", "@babel/plugin-transform-runtime": "^7.25.9", "@babel/preset-env": "^7.26.0", "@babel/preset-typescript": "^7.26.0", @@ -103,6 +100,7 @@ "@rollup/pluginutils": "^5.1.3", "@tapjs/run": "^4.0.1", "@types/blessed": "^0.1.25", + "@types/cmd-shim": "^5.0.2", "@types/micromatch": "^4.0.9", "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", @@ -203,6 +201,7 @@ "files": [ "bin/**", "dist/**", + "shadow-bin/**", "translations.json" ] } diff --git a/scripts/constants.js b/scripts/constants.js index 97f9e4c..13cd1bd 100644 --- a/scripts/constants.js +++ b/scripts/constants.js @@ -23,21 +23,13 @@ const babelConfigPath = path.join(rootConfigPath, 'babel.config.js') const depStatsPath = path.join(rootPath, '.dep-stats.json') const tsconfigPath = path.join(rootConfigPath, 'tsconfig.rollup.json') -const LAZY_DIST_TYPE = () => - registryConstants.SUPPORTS_NODE_REQUIRE_MODULE ? 'module-sync' : 'require' - -const lazyDistPath = () => path.join(rootDistPath, constants.DIST_TYPE) - const constants = createConstantsObject( { - // Lazily defined values are initialized as `undefined` to keep their key order. - DIST_TYPE: undefined, ROLLUP_ENTRY_SUFFIX, ROLLUP_EXTERNAL_SUFFIX, SLASH_NODE_MODULES_SLASH, babelConfigPath, depStatsPath, - distPath: undefined, rootConfigPath, rootDistPath, rootPackageJsonPath, @@ -46,10 +38,6 @@ const constants = createConstantsObject( tsconfigPath }, { - getters: { - DIST_TYPE: LAZY_DIST_TYPE, - distPath: lazyDistPath - }, mixin: registryConstants } ) diff --git a/shadow-bin/npm b/shadow-bin/npm new file mode 100755 index 0000000..a56efe4 --- /dev/null +++ b/shadow-bin/npm @@ -0,0 +1,4 @@ +#!/usr/bin/env node +'use strict' + +require(`../dist/npm-cli.js`) diff --git a/shadow-bin/npx b/shadow-bin/npx new file mode 100755 index 0000000..5377b98 --- /dev/null +++ b/shadow-bin/npx @@ -0,0 +1,4 @@ +#!/usr/bin/env node +'use strict' + +require(`../dist/npx-cli.js`) diff --git a/src/cli.ts b/src/cli.ts index f15de6a..11d6e70 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,9 +2,9 @@ import { pathToFileURL } from 'node:url' -import colors from 'yoctocolors-cjs' import { messageWithCauses, stackWithCauses } from 'pony-cause' import updateNotifier from 'tiny-updater' +import colors from 'yoctocolors-cjs' import * as cliCommands from './commands' import constants from './constants' @@ -28,11 +28,9 @@ function camelToHyphen(str: string): string { // TODO: Add autocompletion using https://socket.dev/npm/package/omelette void (async () => { - const rootPkgJson = require(rootPkgJsonPath) - await updateNotifier({ name: 'socket', - version: rootPkgJson.version, + version: require(rootPkgJsonPath).version, ttl: 86_400_000 /* 24 hours in milliseconds */ }) diff --git a/src/commands/npm.ts b/src/commands/npm.ts index 1a9ab50..28c56a3 100644 --- a/src/commands/npm.ts +++ b/src/commands/npm.ts @@ -6,14 +6,14 @@ import constants from '../constants' import type { CliSubcommand } from '../utils/meow-with-subcommands' -const { distPath, execPath } = constants +const { execPath, rootBinPath } = constants const description = 'npm wrapper functionality' export const npm: CliSubcommand = { description, async run(argv, _importMeta, _ctx) { - const wrapperPath = path.join(distPath, 'npm-cli.js') + const wrapperPath = path.join(rootBinPath, 'npm-cli.js') process.exitCode = 1 const spawnPromise = spawn( execPath, diff --git a/src/commands/npx.ts b/src/commands/npx.ts index 795f61f..ad05fe2 100644 --- a/src/commands/npx.ts +++ b/src/commands/npx.ts @@ -6,14 +6,14 @@ import constants from '../constants' import type { CliSubcommand } from '../utils/meow-with-subcommands' -const { distPath, execPath } = constants +const { execPath, rootBinPath } = constants const description = 'npx wrapper functionality' export const npx: CliSubcommand = { description, async run(argv, _importMeta, _ctx) { - const wrapperPath = path.join(distPath, 'npx-cli.js') + const wrapperPath = path.join(rootBinPath, 'npx-cli.js') process.exitCode = 1 const spawnPromise = spawn( execPath, diff --git a/src/commands/optimize.ts b/src/commands/optimize.ts index 6559b48..c6589c8 100644 --- a/src/commands/optimize.ts +++ b/src/commands/optimize.ts @@ -42,7 +42,7 @@ import type { Spinner } from '@socketregistry/yocto-spinner' type PackageJson = Awaited> -const { UPDATE_SOCKET_OVERRIDES_IN_PACKAGE_LOCK_FILE, distPath, execPath } = +const { UPDATE_SOCKET_OVERRIDES_IN_PACKAGE_LOCK_FILE, execPath, rootBinPath } = constants const COMMAND_TITLE = 'Socket Optimize' @@ -894,7 +894,7 @@ export const optimize: CliSubcommand = { spinner.start(`Updating ${lockName}...`) try { if (isNpm) { - const wrapperPath = path.join(distPath, 'npm-cli.js') + const wrapperPath = path.join(rootBinPath, 'npm-cli.js') const npmSpawnOptions: Parameters[2] = { stdio: 'ignore', env: { diff --git a/src/constants.ts b/src/constants.ts index 53a959a..d34a66d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -35,6 +35,7 @@ const rootBinPath = path.join(rootPath, 'bin') const rootPkgJsonPath = path.join(rootPath, PACKAGE_JSON) const nmBinPath = path.join(rootPath, 'node_modules/.bin') const cdxgenBinPath = path.join(nmBinPath, 'cdxgen') +const shadowBinPath = path.join(rootPath, 'shadow-bin') const synpBinPath = path.join(nmBinPath, 'synp') const LAZY_DIST_TYPE = () => @@ -42,8 +43,6 @@ const LAZY_DIST_TYPE = () => const lazyConstants = () => constants const lazyDistPath = () => path.join(rootDistPath, constants.DIST_TYPE) -const lazyShadowBinPath = () => - path.join(rootPath, 'shadow', constants.DIST_TYPE) const constants = < { @@ -80,7 +79,7 @@ const constants = < rootDistPath, rootPath, rootPkgJsonPath, - shadowBinPath: undefined, + shadowBinPath, synpBinPath }, { @@ -88,11 +87,14 @@ const constants = < DIST_TYPE: LAZY_DIST_TYPE, // Preserve rollup chunk compatibility. constants: lazyConstants, - distPath: lazyDistPath, - shadowBinPath: lazyShadowBinPath + distPath: lazyDistPath }, mixin: registryConstants } ) +// Hard-code the exports we want. +// We'll remove the other in the writeBundle hook of rollup.dist.config.mjs. +module.exports = constants + export default constants diff --git a/src/shadow/link.ts b/src/shadow/link.ts index 24b03fb..70f7624 100644 --- a/src/shadow/link.ts +++ b/src/shadow/link.ts @@ -1,38 +1,52 @@ import { realpathSync } from 'node:fs' import path from 'node:path' +import cmdShim from 'cmd-shim' import which from 'which' -export function installLinks( - realDirname: string, +import constants from '../constants' + +const { WIN32, rootDistPath } = constants + +export async function installLinks( + realBinPath: string, binName: 'npm' | 'npx' -): string { - const realShadowBinDir = realDirname - // find package manager being shadowed by this process +): Promise { + // Find package manager being shadowed by this process. const bins = which.sync(binName, { all: true }) let shadowIndex = -1 const binPath = bins.find((binPath, i) => { - if (realpathSync(path.dirname(binPath)) === realShadowBinDir) { + // Skip our bin directory if it's in the front. + if (realpathSync(path.dirname(binPath)) === realBinPath) { shadowIndex = i return false } return true }) - const isWin = process.platform === 'win32' - if (isWin && binPath) { - return binPath - } if (!binPath) { console.error( `Socket unable to locate ${binName}; ensure it is available in the PATH environment variable` ) + // The exit code 127 indicates that the command or binary being executed + // could not be found. process.exit(127) } + // TODO: Is this early exit needed? + if (WIN32 && binPath) { + return binPath + } + // Move our bin directory to front of PATH so its found first. if (shadowIndex === -1) { - const binDir = path.join(realDirname) - process.env['PATH'] = `${binDir}${isWin ? ';' : ':'}${process.env['PATH']}` + if (WIN32) { + await cmdShim( + path.join(rootDistPath, `${binName}-cli.js`), + path.join(realBinPath, binName) + ) + } + process.env['PATH'] = + `${realBinPath}${WIN32 ? ';' : ':'}${process.env['PATH']}` } return binPath } diff --git a/src/shadow/npm-cli.ts b/src/shadow/npm-cli.ts deleted file mode 100755 index fdf945a..0000000 --- a/src/shadow/npm-cli.ts +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env node - -import { realpathSync } from 'node:fs' -import path from 'node:path' - -import spawn from '@npmcli/promise-spawn' - -import constants from '../constants' -import { installLinks } from './link' -import { findRoot } from '../utils/path-resolve' - -const { distPath, execPath, shadowBinPath } = constants - -const npmPath = installLinks(shadowBinPath, 'npm') -const injectionPath = path.join(distPath, 'npm-injection.js') - -// Adding the `--quiet` and `--no-progress` flags when the `proc-log` module -// is found to fix a UX issue when running the command with recent versions of -// npm (input swallowed by the standard npm spinner) -const npmArgs: string[] = process.argv.slice(2) -if ( - npmArgs.includes('install') && - !npmArgs.includes('--no-progress') && - !npmArgs.includes('--quiet') -) { - const npmEntrypoint = realpathSync(npmPath) - const npmRootPath = findRoot(path.dirname(npmEntrypoint)) - if (npmRootPath === undefined) { - process.exit(127) - } - const npmDepPath = path.join(npmRootPath, 'node_modules') - let procLog - try { - procLog = require(path.join(npmDepPath, 'proc-log/lib/index.js')).log - } catch {} - if (procLog) { - npmArgs.push('--no-progress', '--quiet') - } -} - -process.exitCode = 1 -const spawnPromise = spawn( - execPath, - [ - // Lazily access constants.nodeNoWarningsFlags. - ...constants.nodeNoWarningsFlags, - '--require', - injectionPath, - npmPath, - ...npmArgs - ], - { stdio: 'inherit' } -) -spawnPromise.process.on('exit', (code, signal) => { - if (signal) { - process.kill(process.pid, signal) - } else if (code !== null) { - process.exit(code) - } -}) -void spawnPromise diff --git a/src/shadow/npm-injection.ts b/src/shadow/npm-injection.ts index 263ece2..0b57127 100644 --- a/src/shadow/npm-injection.ts +++ b/src/shadow/npm-injection.ts @@ -1,9 +1,3 @@ -import constants from '../constants' import { installSafeArborist } from './arborist' -import { installLinks } from './link' -const { shadowBinPath } = constants - -// Shadow `npm` and `npx` to mitigate subshells. -installLinks(shadowBinPath, 'npm') installSafeArborist() diff --git a/src/shadow/npx-cli.ts b/src/shadow/npx-cli.ts deleted file mode 100755 index a2066c5..0000000 --- a/src/shadow/npx-cli.ts +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env node - -import path from 'node:path' - -import spawn from '@npmcli/promise-spawn' - -import constants from '../constants' -import { installLinks } from './link' - -const { distPath, execPath, shadowBinPath } = constants - -const npxPath = installLinks(shadowBinPath, 'npx') -const injectionPath = path.join(distPath, 'npm-injection.js') - -process.exitCode = 1 -const spawnPromise = spawn( - execPath, - [ - // Lazily access constants.nodeNoWarningsFlags. - ...constants.nodeNoWarningsFlags, - '--require', - injectionPath, - npxPath, - ...process.argv.slice(2) - ], - { stdio: 'inherit' } -) -spawnPromise.process.on('exit', (code, signal) => { - if (signal) { - process.kill(process.pid, signal) - } else if (code !== null) { - process.exit(code) - } -}) -void spawnPromise diff --git a/src/shadow/shadow-bin.ts b/src/shadow/shadow-bin.ts new file mode 100755 index 0000000..f107ff5 --- /dev/null +++ b/src/shadow/shadow-bin.ts @@ -0,0 +1,63 @@ +import { realpathSync } from 'node:fs' +import path from 'node:path' + +import spawn from '@npmcli/promise-spawn' + +import constants from '../constants' +import { installLinks } from './link' +import { findRoot } from '../utils/path-resolve' + +const { distPath, execPath, shadowBinPath } = constants + +const injectionPath = path.join(distPath, 'npm-injection.js') + +export default async function shadow(binName: 'npm' | 'npx') { + const binPath = await installLinks(shadowBinPath, binName) + // Adding the `--quiet` and `--no-progress` flags when the `proc-log` module + // is found to fix a UX issue when running the command with recent versions of + // npm (input swallowed by the standard npm spinner) + const binArgs: string[] = process.argv.slice(2) + + if ( + binName === 'npm' && + binArgs.includes('install') && + !binArgs.includes('--no-progress') && + !binArgs.includes('--quiet') + ) { + const npmEntrypoint = realpathSync(binPath) + const npmRootPath = findRoot(path.dirname(npmEntrypoint)) + if (npmRootPath === undefined) { + process.exit(127) + } + const npmDepPath = path.join(npmRootPath, 'node_modules') + let procLog + try { + procLog = require(path.join(npmDepPath, 'proc-log/lib/index.js')).log + } catch {} + if (procLog) { + binArgs.push('--no-progress', '--quiet') + } + } + + process.exitCode = 1 + const spawnPromise = spawn( + execPath, + [ + // Lazily access constants.nodeNoWarningsFlags. + ...constants.nodeNoWarningsFlags, + '--require', + injectionPath, + binPath, + ...binArgs + ], + { stdio: 'inherit' } + ) + spawnPromise.process.on('exit', (code, signal) => { + if (signal) { + process.kill(process.pid, signal) + } else if (code !== null) { + process.exit(code) + } + }) + await spawnPromise +} diff --git a/test/socket-cdxgen.test.ts b/test/socket-cdxgen.test.ts index 8428e06..25a59d6 100644 --- a/test/socket-cdxgen.test.ts +++ b/test/socket-cdxgen.test.ts @@ -4,15 +4,15 @@ import { describe, it } from 'node:test' import spawn from '@npmcli/promise-spawn' -import constants from '../dist/constants' +import constants from '../dist/constants.js' type PromiseSpawnOptions = Exclude[2], undefined> & { encoding?: BufferEncoding | undefined } -const { distPath, execPath } = constants +const { execPath, rootBinPath } = constants -const entryPath = path.join(distPath, 'cli.js') +const entryPath = path.join(rootBinPath, 'cli.js') const testPath = __dirname const npmFixturesPath = path.join(testPath, 'socket-npm-fixtures') diff --git a/test/socket-npm.test.cjs b/test/socket-npm.test.cjs index a69657e..017a021 100644 --- a/test/socket-npm.test.cjs +++ b/test/socket-npm.test.cjs @@ -7,10 +7,10 @@ const { describe, it } = require('node:test') const spawn = require('@npmcli/promise-spawn') -const constants = require('../scripts/constants') -const { distPath, execPath } = constants +const constants = require('../dist/constants.js') +const { execPath, rootBinPath } = constants -const entryPath = path.join(distPath, 'cli.js') +const entryPath = path.join(rootBinPath, 'cli.js') const testPath = __dirname const npmFixturesPath = path.join(testPath, 'socket-npm-fixtures')