Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bun as packageManager option #1309

Merged
merged 16 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]>",
"license": "Apache-2.0",
"contributors": [
"Raine Revere (https://github.com/raineorshine)"
"Raine Revere (https://github.com/raineorshine)",
"Imamuzzaki Abu Salam <[email protected]>"
],
"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
Loading
Loading