Skip to content

Commit

Permalink
feat: bun as packageManager option (#1309)
Browse files Browse the repository at this point in the history
Co-authored-by: Raine Revere <raine@cybersemics.org>
  • Loading branch information
ImBIOS and raineorshine committed Sep 13, 2023
1 parent 79fc218 commit 0c25e3a
Show file tree
Hide file tree
Showing 21 changed files with 260 additions and 34 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- sensible defaults
- lots of options for custom behavior
- CLI and module usage
- compatible with `npm`, `yarn`, and `pnpm`
- compatible with `npm`, `yarn`, `pnpm`, `bun`

![npm-check-updates-screenshot](https://github.com/raineorshine/npm-check-updates/blob/main/.github/screenshot.png?raw=true)

Expand Down Expand Up @@ -277,7 +277,7 @@ Options that take no arguments can be negated by prefixing them with `--no-`, e.
</tr>
<tr>
<td>-p, --packageManager <s></td>
<td>npm, yarn, pnpm, deno (default: npm).</td>
<td>npm, yarn, pnpm, deno, bun, staticRegistry (default: npm).</td>
</tr>
<tr>
<td>--peer</td>
Expand Down Expand Up @@ -522,6 +522,7 @@ Specifies the package manager to use when looking up versions.
<tr><td>npm</td><td>System-installed npm. Default.</td></tr>
<tr><td>yarn</td><td>System-installed yarn. Automatically used if yarn.lock is present.</td></tr>
<tr><td>pnpm</td><td>System-installed pnpm. Automatically used if pnpm-lock.yaml is present.</td></tr>
<tr><td>bun</td><td>System-installed bun. Automatically used if bun.lockb is present.</td></tr>
<tr><td>staticRegistry</td><td>Deprecated. Use --registryType json.</td></tr>
</table>
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"author": "Tomas Junnonen <tomas1@gmail.com>",
"license": "Apache-2.0",
"contributors": [
"Raine Revere (https://github.com/raineorshine)"
"Raine Revere (https://github.com/raineorshine)",
"Imamuzzaki Abu Salam <imamuzzaki@gmail.com>"
],
"description": "Find newer versions of dependencies than what your package.json allows",
"keywords": [
Expand All @@ -19,7 +20,10 @@
"updater",
"version",
"management",
"ncu"
"ncu",
"bun",
"yarn",
"pnpm"
],
"engines": {
"node": ">=14.14"
Expand All @@ -33,7 +37,7 @@
"lint:lockfile": "lockfile-lint",
"lint:markdown": "markdownlint \"**/*.md\" --ignore node_modules --ignore build --config .markdownlint.js",
"lint:src": "eslint --cache --cache-location node_modules/.cache/.eslintcache --ignore-path .gitignore --report-unused-disable-directives .",
"prepare": "husky install",
"prepare": "husky install && bash test/bun-setup.sh",
"prepublishOnly": "npm run build",
"prettier": "prettier .",
"test": "mocha test test/package-managers/*",
Expand Down Expand Up @@ -80,6 +84,7 @@
"semver-utils": "^1.1.4",
"source-map-support": "^0.5.21",
"spawn-please": "^2.0.1",
"strip-ansi": "^7.1.0",
"strip-json-comments": "^5.0.1",
"untildify": "^4.0.0",
"update-notifier": "^6.0.2"
Expand Down Expand Up @@ -134,7 +139,6 @@
"prettier": "^2.8.8",
"should": "^13.2.3",
"sinon": "^15.2.0",
"strip-ansi": "^7.1.0",
"ts-node": "^10.9.1",
"typescript": "5.1.6",
"typescript-json-schema": "0.59.0",
Expand Down
5 changes: 3 additions & 2 deletions src/cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ const extendedHelpPackageManager: ExtendedHelp = ({ markdown }) => {
['npm', `System-installed npm. Default.`],
['yarn', `System-installed yarn. Automatically used if yarn.lock is present.`],
['pnpm', `System-installed pnpm. Automatically used if pnpm-lock.yaml is present.`],
['bun', `System-installed bun. Automatically used if bun.lockb is present.`],
['staticRegistry', `Deprecated. Use --registryType json.`],
],
})
Expand Down Expand Up @@ -614,9 +615,9 @@ const cliOptions: CLIOption[] = [
long: 'packageManager',
short: 'p',
arg: 's',
description: 'npm, yarn, pnpm, deno (default: npm).',
description: 'npm, yarn, pnpm, deno, bun, staticRegistry (default: npm).',
help: extendedHelpPackageManager,
type: `'npm' | 'yarn' | 'pnpm' | 'deno' | 'staticRegistry'`,
type: `'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun' | 'staticRegistry'`,
},
{
long: 'peer',
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function checkIfVolta(options: Options): void {
}
}

/** Returns the package manager that should be used to install packages after running "ncu -u". Detects pnpm via pnpm-lock.yarn. */
/** Returns the package manager that should be used to install packages after running "ncu -u". Detects pnpm via pnpm-lock.yaml. This is the one place that pnpm needs to be detected, since otherwise it is backwards compatible with npm. */
const getPackageManagerForInstall = async (options: Options, pkgFile: string) => {
// when packageManager is set to staticRegistry, we need to infer the package manager from lock files
if (options.packageManager === 'staticRegistry') determinePackageManager({ ...options, packageManager: undefined })
Expand Down
1 change: 1 addition & 0 deletions src/lib/determinePackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const packageManagerLockfileMap: Index<PackageManagerName> = {
yarn: 'yarn',
'pnpm-lock': 'pnpm',
deno: 'deno',
bun: 'bun',
}

/**
Expand Down
29 changes: 21 additions & 8 deletions src/lib/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'fs/promises'
import { rimraf } from 'rimraf'
import spawn from 'spawn-please'
import { printUpgrades } from '../lib/logging'
import spawnBun from '../package-managers/bun'
import spawnNpm from '../package-managers/npm'
import spawnPnpm from '../package-managers/pnpm'
import spawnYarn from '../package-managers/yarn'
Expand All @@ -17,7 +18,7 @@ import upgradePackageData from './upgradePackageData'

type Run = (options?: Options) => Promise<PackageFile | Index<VersionSpec> | void>

/** Run npm, yarn, or pnpm. */
/** Run npm, yarn, pnpm, or bun. */
const npm = (
args: string[],
options: Options,
Expand Down Expand Up @@ -45,11 +46,15 @@ const npm = (
...(options.prefix ? { prefix: options.prefix } : null),
}

return (options.packageManager === 'pnpm' ? spawnPnpm : options.packageManager === 'yarn' ? spawnYarn : spawnNpm)(
args,
npmOptions,
spawnOptionsMerged as any,
)
return (
options.packageManager === 'pnpm'
? spawnPnpm
: options.packageManager === 'yarn'
? spawnYarn
: options.packageManager === 'bun'
? spawnBun
: spawnNpm
)(args, npmOptions, spawnOptionsMerged as any)
}

/** Load and validate package file and tests. */
Expand Down Expand Up @@ -91,6 +96,8 @@ const doctor = async (run: Run, options: Options): Promise<void> => {
? 'yarn.lock'
: options.packageManager === 'pnpm'
? 'pnpm-lock.yaml'
: options.packageManager === 'bun'
? 'bun.lockb'
: 'package-lock.json'
const { pkg, pkgFile }: PackageInfo = await loadPackageFileForDoctor(options)

Expand Down Expand Up @@ -256,7 +263,9 @@ const doctor = async (run: Run, options: Options): Promise<void> => {
// install single dependency
await npm(
[
...(options.packageManager === 'yarn' || options.packageManager === 'pnpm'
...(options.packageManager === 'yarn' ||
options.packageManager === 'pnpm' ||
options.packageManager === 'bun'
? ['add']
: ['install', '--no-save']),
`${name}@${version}`,
Expand Down Expand Up @@ -299,7 +308,11 @@ const doctor = async (run: Run, options: Options): Promise<void> => {
await fs.writeFile(lockFileName, lockFile)

// restore package.json since yarn and pnpm do not have the --no-save option
if (options.packageManager === 'yarn' || options.packageManager === 'pnpm') {
if (
options.packageManager === 'yarn' ||
options.packageManager === 'pnpm' ||
options.packageManager === 'bun'
) {
await fs.writeFile('package.json', lastPkgFile)
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/lib/findLockfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path'
import { Options } from '../types/Options'

/**
* Goes up the filesystem tree until it finds a package-lock.json or yarn.lock.
* Goes up the filesystem tree until it finds a package-lock.json, yarn.lock, pnpm-lock.yaml, deno.json, deno.jsonc, or bun.lockb file.
*
* @param readdir This is only a parameter so that it can be used in tests.
* @returns The path of the directory that contains the lockfile and the
Expand Down Expand Up @@ -33,6 +33,8 @@ export default async function findLockfile(
return { directoryPath: currentPath, filename: 'deno.json' }
} else if (files.includes('deno.jsonc')) {
return { directoryPath: currentPath, filename: 'deno.jsonc' }
} else if (files.includes('bun.lockb')) {
return { directoryPath: currentPath, filename: 'bun.lockb' }
}

const pathParent = path.resolve(currentPath, '..')
Expand Down
2 changes: 2 additions & 0 deletions src/lib/runGlobal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ async function runGlobal(options: Options): Promise<Index<string> | void> {
? 'yarn global upgrade'
: options.packageManager === 'pnpm'
? 'pnpm -g add'
: options.packageManager === 'bun'
? 'bun add -g'
: 'npm -g install'

print(
Expand Down
4 changes: 4 additions & 0 deletions src/package-managers/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Package Managers

## How to add a new package manager

To add support for another package manager, drop in a module with the following interface.

```js
Expand Down
79 changes: 79 additions & 0 deletions src/package-managers/bun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Options } from 'pacote'
import spawn from 'spawn-please'
import keyValueBy from '../lib/keyValueBy'
import programError from '../lib/programError'
import { Index } from '../types/IndexType'
import { NpmOptions } from '../types/NpmOptions'
import * as npm from './npm'

/** Spawn bun. */
async function spawnBun(
args: string | string[],
npmOptions: NpmOptions = {},
spawnOptions: Index<any> = {},
): Promise<string> {
// Bun not yet supported on Windows.
// @see https://github.com/oven-sh/bun/issues/43
if (process.platform === 'win32') {
programError(npmOptions, 'Bun not yet supported on Windows')
}

args = Array.isArray(args) ? args : [args]

const fullArgs = [
...args,
...(npmOptions.prefix ? `--prefix=${npmOptions.prefix}` : []),
...(npmOptions.location === 'global' ? ['--global'] : []),
]

return spawn('bun', fullArgs, spawnOptions)
}
/**
* (Bun) Fetches the list of all installed packages.
*/
export const list = async (options: Options = {}): Promise<Index<string | undefined>> => {
const { default: stripAnsi } = await import('strip-ansi')

// bun pm ls
const stdout = await spawnBun(
['pm', 'ls'],
{
...(options.global ? { location: 'global' } : null),
...(options.prefix ? { prefix: options.prefix } : null),
},
{
env: {
...process.env,
// Disable color to ensure the output is parsed correctly.
// However, this may be ineffective in some environments (see stripAnsi below).
// https://bun.sh/docs/runtime/configuration#environment-variables
NO_COLOR: '1',
},
...(options.cwd ? { cwd: options.cwd } : null),
rejectOnError: false,
},
)

// Parse the output of `bun pm ls` into an object { [name]: version }.
// When bun is spawned in the GitHub Actions environment, it outputs ANSI color. Unfortunately, it does not respect the `NO_COLOR` envirionment variable. Therefore, we have to manually strip ansi.
const lines = stripAnsi(stdout).split('\n')
const dependencies = keyValueBy(lines, line => {
const match = line.match(/.* (.*?)@(.+)/)
if (match) {
const [, name, version] = match
return { [name]: version }
}
return null
})

return dependencies
}

export const greatest = npm.greatest
export const latest = npm.latest
export const newest = npm.newest
export const semver = npm.semver
export const minor = npm.minor
export const patch = npm.patch

export default spawnBun
2 changes: 2 additions & 0 deletions src/package-managers/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Index } from '../types/IndexType'
import { PackageManager } from '../types/PackageManager'
import * as bun from './bun'
import * as gitTags from './gitTags'
import * as npm from './npm'
import * as pnpm from './pnpm'
Expand All @@ -10,6 +11,7 @@ export default {
npm,
pnpm,
yarn,
bun,
gitTags,
staticRegistry,
} as Index<PackageManager>
2 changes: 1 addition & 1 deletion src/types/PackageManagerName.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/** A valid package manager. */
export type PackageManagerName = 'npm' | 'yarn' | 'pnpm' | 'deno' | 'staticRegistry'
export type PackageManagerName = 'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun' | 'staticRegistry'
4 changes: 2 additions & 2 deletions src/types/RunOptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,8 @@
"type": "string"
},
"packageManager": {
"description": "npm, yarn, pnpm, deno (default: npm). Run \"ncu --help --packageManager\" for details.",
"enum": ["deno", "npm", "pnpm", "staticRegistry", "yarn"],
"description": "npm, yarn, pnpm, deno, bun, staticRegistry (default: npm). Run \"ncu --help --packageManager\" for details.",
"enum": ["bun", "deno", "npm", "pnpm", "staticRegistry", "yarn"],
"type": "string"
},
"peer": {
Expand Down
4 changes: 2 additions & 2 deletions src/types/RunOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ export interface RunOptions {
/** Package file(s) location. (default: ./package.json) */
packageFile?: string

/** npm, yarn, pnpm, deno (default: npm). Run "ncu --help --packageManager" for details. */
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'deno' | 'staticRegistry'
/** npm, yarn, pnpm, deno, bun, staticRegistry (default: npm). Run "ncu --help --packageManager" for details. */
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun' | 'staticRegistry'

/** Check peer dependencies of installed packages and filter updates to compatible versions. Run "ncu --help --peer" for details. */
peer?: boolean
Expand Down
12 changes: 12 additions & 0 deletions test/bun-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Install bun if not installed.
# Must be run in a prepare script instead of devDependencies to avoid npm install failing on Windows.
bun -v &> /dev/null
BUN_EXISTS="$?"

if [ $BUN_EXISTS -ne 0 ]; then
npm install -g bun
fi

# Always return success, even if the install script fails.
# Windows is expected to fail and the bun tests will be skipped.
exit 0

0 comments on commit 0c25e3a

Please sign in to comment.