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

npx-import

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

npx-import - npm Package Compare versions

Comparing version 1.0.0 to 1.0.1

10

lib/index.js

@@ -60,3 +60,3 @@ import semver from 'semver';

for (const p of Array.isArray(pkg) ? pkg : [pkg]) {
const { name, version, path } = parseAndValidate(p);
const { name, version, path, exact } = parseAndValidate(p);
if (packages[name])

@@ -70,2 +70,3 @@ throw new Error(`npx-import cannot import the same package twice! Got: '${p}' but already saw '${name}' earlier!`);

version,
exact,
path,

@@ -90,3 +91,3 @@ imported,

}
return { name, version, path };
return { name, version, path, exact: semver.parse(version) !== null };
}

@@ -112,3 +113,6 @@ async function tryImport(packageWithPath) {

async function installAndReturnDir(packages, logger) {
const installPackage = `npx -y ${packages.map((p) => `-p ${formatForCLI(p)}`).join(' ')}`;
const offline = packages.every((p) => p.exact);
const installPackage = `npx --prefer-${offline ? 'offline' : 'online'} -y ${packages
.map((p) => `-p ${formatForCLI(p)}`)
.join(' ')}`;
logger(`Installing... (${installPackage})`);

@@ -115,0 +119,0 @@ const emitPath = `node -e 'console.log(process.env.PATH)'`;

{
"name": "npx-import",
"version": "1.0.0",
"version": "1.0.1",
"description": "Runtime dependencies, installed as if by magic ✨",

@@ -22,2 +22,3 @@ "main": "lib/index.js",

"@types/validate-npm-package-name": "^4.0.0",
"chalk": "^5.0.1",
"prettier": "^2.7.1",

@@ -24,0 +25,0 @@ "typescript": "^4.7.4",

@@ -5,3 +5,3 @@ # 🧙‍♂️ `npx-import` 🧙‍♀️

[![twitter](https://img.shields.io/badge/author-@glenmaddern-blue.svg?style=flat)](https://twitter.com/glenmaddern) [![npm](https://img.shields.io/npm/v/npx-import)](https://www.npmjs.com/package/npx-import) [![GitHub last commit](https://img.shields.io/github/last-commit/geelen/npx-import)](https://github.com/geelen/npx-import)
[![twitter](https://img.shields.io/badge/@glenmaddern-blue.svg?style=flat&logo=twitter&label=​)](https://twitter.com/glenmaddern)  [![GitHub last commit](https://img.shields.io/github/last-commit/geelen/npx-import?logo=github&style=flat&label=​)](https://github.com/geelen/npx-import)  [![npm](https://img.shields.io/npm/v/npx-import?label=​&logo=npm)](https://www.npmjs.com/package/npx-import)

@@ -97,5 +97,5 @@ `npx-import` can be used as a drop-in replacement for [dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import):

import { npxImport } from 'npx-import'
import type BigDep from 'big-dep'
type BigDepType = typeof import('big-dep')
const bigDep = await npxImport<BigDep>('big-dep')
const bigDep = await npxImport<BigDepType>('big-dep')
```

@@ -111,4 +111,6 @@

Any package specifier that's valid in `package.json` will work here: e.g. `^1.0.0`, `~2.3.0`, `>4.0.0`
Any package specifier that's valid in `package.json` will work here: e.g. `^1.0.0`, `~2.3.0`, `>4.0.0`, `@latest`, `@next`, etc.
Note: there is a speed benefit from using exact versions. `npxImport(pkg-a@1.2.3)` will run `npx --prefer-offline` under the hood, making it faster after the first run (since it doesn't first check the NPM registry for newer versions).
* You can also install multiple packages at once:

@@ -171,4 +173,13 @@

### 😵‍💫 What about multiple projects? Won't you get conflicting versions?
### 🤪 Doesn't this mean dependencies gets repeatedly downloaded & installed?
No! `npx` maintains a cache in the user's home directory. If a cached package is found, `npx` will (by default) hit NPM to check if there's any new versions for that specifier, and if not, return the cache. `npxImport` adds a small optimisation—if you specify an exact package version (e.g. `@7.8.2`), it'll run `npx --prefer-offline` to skip the NPM check.
So new packages are only downloaded & installed when:
* It's the first time a particular package/version combo is seen (see next section)
* No locked version was provided and there's a new version on NPM
### 😵‍💫 What about multiple projects? Doesn't the cache mean projects can clobber/overwrite/conflict with each other?
As it turns out, no! While I wasn't paying attention, `npx` got really smart! To understand why, we need to look at how `npx` works:

@@ -259,3 +270,3 @@

So `npx` is doing exactly the same as an `npm install`, with a `package.json`, `package-lock.json`, `node_modules` etc. It's just dynamically creating directories based on some hash of its inputs. It's super clever!
So `npx` is doing exactly the same as an `npm install`, with a `package.json`, `package-lock.json`, `node_modules` etc. It's just dynamically creating directories based on some hash of its inputs. So the only way two projects can use the same package in the cache is if they _both_ ask for _exactly_ the same packages & versions. It's super clever!

@@ -262,0 +273,0 @@ ### 😐 But what about transitive deps? Won't you get duplication?

@@ -14,2 +14,3 @@ import semver from 'semver'

version: string
exact: boolean
path: string

@@ -85,3 +86,3 @@ imported: typeof NOT_IMPORTABLE | any

for (const p of Array.isArray(pkg) ? pkg : [pkg]) {
const { name, version, path } = parseAndValidate(p)
const { name, version, path, exact } = parseAndValidate(p)
if (packages[name])

@@ -97,2 +98,3 @@ throw new Error(

version,
exact,
path,

@@ -122,3 +124,3 @@ imported,

}
return { name, version, path }
return { name, version, path, exact: semver.parse(version) !== null }
}

@@ -147,3 +149,6 @@

async function installAndReturnDir(packages: Package[], logger: Logger) {
const installPackage = `npx -y ${packages.map((p) => `-p ${formatForCLI(p)}`).join(' ')}`
const offline = packages.every((p) => p.exact)
const installPackage = `npx --prefer-${offline ? 'offline' : 'online'} -y ${packages
.map((p) => `-p ${formatForCLI(p)}`)
.join(' ')}`
logger(`Installing... (${installPackage})`)

@@ -150,0 +155,0 @@ const emitPath = `node -e 'console.log(process.env.PATH)'`

import { npxImport, npxResolve } from '../lib/index.js'
import chalk from 'chalk'
console.log(`${chalk.green('❯')} node ./index.js --filename=image.png\n`)
try {

@@ -8,6 +11,5 @@ const leftPad = await import('left-pad')

console.log(`This is a PNG! We'll have to compile imagemagick!`)
const leftPad = await npxImport('left-pad@>1.0.0')
await npxImport('left-pad@^1.1.0', log => console.log(' ' + chalk.gray(log.replace(/left-pad/,'imagemagick-utils'))))
}
console.log({ location: await npxResolve('left-pad@3.4.0') })
console.log(`Done!`)

@@ -165,3 +165,3 @@ import { afterEach, describe, expect, test, vi } from 'vitest'

expectExecaCommand(
`npx -y -p broken-install@^2.0.0 node -e 'console.log(process.env.PATH)'`,
`npx --prefer-online -y -p broken-install@^2.0.0 node -e 'console.log(process.env.PATH)'`,
{ shell: true }

@@ -183,3 +183,3 @@ ).returning(new Error('EXPLODED TRYING TO INSTALL'))

expectExecaCommand(
`npx -y -p left-pad@this-tag-no-exist node -e 'console.log(process.env.PATH)'`,
`npx --prefer-online -y -p left-pad@this-tag-no-exist node -e 'console.log(process.env.PATH)'`,
{ shell: true }

@@ -203,3 +203,3 @@ ).returning(new Error('No matching version found for left-pad@this-tag-no-exist.'))

expectExecaCommand('npx --version').returning({ stdout: '8.1.2' })
expectExecaCommand(`npx -y -p @org/pkg@my-tag node -e 'console.log(process.env.PATH)'`, {
expectExecaCommand(`npx --prefer-online -y -p @org/pkg@my-tag node -e 'console.log(process.env.PATH)'`, {
shell: true,

@@ -228,3 +228,3 @@ }).returning({ stdout: getNpxPath(npxDirectoryHash) })

expectExecaCommand('npx --version').returning({ stdout: '8.1.2' })
expectExecaCommand(`npx -y -p @org/pkg@my-tag node -e 'console.log(process.env.PATH)'`, {
expectExecaCommand(`npx --prefer-online -y -p @org/pkg@my-tag node -e 'console.log(process.env.PATH)'`, {
shell: true,

@@ -238,3 +238,3 @@ }).returning({ stdout: getNpxPath(npxDirectoryHash) })

`@org/pkg/lib/index.js not available locally. Attempting to use npx to install temporarily.`,
`Installing... (npx -y -p @org/pkg@my-tag)`,
`Installing... (npx --prefer-online -y -p @org/pkg@my-tag)`,
`Installed into ${basePath}.`,

@@ -247,2 +247,24 @@ `To skip this step in future, run: pnpm add -D @org/pkg@my-tag`

test(`Should prefer offline for exact versions`, async () => {
const npxDirectoryHash = randomString(12)
const basePath = `/Users/glen/.npm/_npx/${npxDirectoryHash}/node_modules`
expectExecaCommand('npx --version').returning({ stdout: '8.1.2' })
expectExecaCommand(`npx --prefer-offline -y -p @org/pkg@3.0.1 node -e 'console.log(process.env.PATH)'`, {
shell: true,
}).returning({ stdout: getNpxPath(npxDirectoryHash) })
expectRelativeImport(basePath, '@org/pkg/lib/index.js').returning({ foo: 1, bar: 2 })
const imported = await npxImportSucceeded(
'@org/pkg@3.0.1/lib/index.js',
matchesAllLines(
`@org/pkg/lib/index.js not available locally. Attempting to use npx to install temporarily.`,
`Installing... (npx --prefer-offline -y -p @org/pkg@3.0.1)`,
`Installed into ${basePath}.`,
`To skip this step in future, run: pnpm add -D @org/pkg@3.0.1`
)
)
expect(imported).toStrictEqual({ foo: 1, bar: 2 })
})
test(`Should install two packages`, async () => {

@@ -254,3 +276,3 @@ const npxDirectoryHash = randomString(12)

expectExecaCommand(
`npx -y -p pkg-a@latest -p pkg-b@latest node -e 'console.log(process.env.PATH)'`,
`npx --prefer-online -y -p pkg-a@latest -p pkg-b@latest node -e 'console.log(process.env.PATH)'`,
{

@@ -267,3 +289,3 @@ shell: true,

'Packages pkg-a, pkg-b not available locally. Attempting to use npx to install temporarily.',
'Installing... (npx -y -p pkg-a@latest -p pkg-b@latest)',
'Installing... (npx --prefer-online -y -p pkg-a@latest -p pkg-b@latest)',
`Installed into ${basePath}.`,

@@ -287,3 +309,3 @@ 'To skip this step in future, run: pnpm add -D pkg-a@latest pkg-b@latest'

expectExecaCommand('npx --version').returning({ stdout: '8.1.2' })
expectExecaCommand(`npx -y -p pkg-b@latest node -e 'console.log(process.env.PATH)'`, {
expectExecaCommand(`npx --prefer-offline -y -p pkg-b@1.2.3 node -e 'console.log(process.env.PATH)'`, {
shell: true,

@@ -294,3 +316,3 @@ }).returning({ stdout: getNpxPath(npxDirectoryHash) })

const logs: string[] = []
const imported = await npxImport(['pkg-a', 'pkg-b'], (msg: string) => logs.push(msg))
const imported = await npxImport(['pkg-a', 'pkg-b@1.2.3'], (msg: string) => logs.push(msg))
expect(_import).toHaveBeenCalledTimes(2)

@@ -301,5 +323,5 @@

'pkg-b not available locally. Attempting to use npx to install temporarily.',
'Installing... (npx -y -p pkg-b@latest)',
'Installing... (npx --prefer-offline -y -p pkg-b@1.2.3)',
`Installed into ${basePath}.`,
'To skip this step in future, run: pnpm add -D pkg-b@latest'
'To skip this step in future, run: pnpm add -D pkg-b@1.2.3'
)

@@ -319,3 +341,3 @@ )

expectExecaCommand(
`npx -y -p 'pkg-a@>1.0.0' -p 'pkg-b@*' node -e 'console.log(process.env.PATH)'`,
`npx --prefer-online -y -p 'pkg-a@>1.0.0' -p 'pkg-b@*' node -e 'console.log(process.env.PATH)'`,
{

@@ -332,3 +354,3 @@ shell: true,

'Packages pkg-a, pkg-b not available locally. Attempting to use npx to install temporarily.',
`Installing... (npx -y -p 'pkg-a@>1.0.0' -p 'pkg-b@*')`,
`Installing... (npx --prefer-online -y -p 'pkg-a@>1.0.0' -p 'pkg-b@*')`,
`Installed into ${basePath}.`,

@@ -360,3 +382,3 @@ `To skip this step in future, run: pnpm add -D 'pkg-a@>1.0.0' 'pkg-b@*'`

expectExecaCommand('npx --version').returning({ stdout: '8.1.2' })
expectExecaCommand(`npx -y -p pkg-b@latest node -e 'console.log(process.env.PATH)'`, {
expectExecaCommand(`npx --prefer-online -y -p pkg-b@latest node -e 'console.log(process.env.PATH)'`, {
shell: true,

@@ -363,0 +385,0 @@ }).returning({ stdout: getNpxPath(npxDirectoryHash) })

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