Skip to content

Commit

Permalink
feat(core): add bun package manager
Browse files Browse the repository at this point in the history
Bun uses yarn lock for it's binary file. Running the binary will produce the content of a yarn lock file (v1)
  • Loading branch information
Jordan-Hall committed Apr 2, 2024
1 parent 120cde6 commit 99421c3
Show file tree
Hide file tree
Showing 17 changed files with 146 additions and 26 deletions.
2 changes: 1 addition & 1 deletion docs/generated/cli/create-nx-workspace.md
Expand Up @@ -129,7 +129,7 @@ Do you want Nx Cloud to make your CI fast?

Type: `string`

Choices: [npm, pnpm, yarn]
Choices: [bun, npm, pnpm, yarn]

Default: `npm`

Expand Down
2 changes: 1 addition & 1 deletion docs/generated/devkit/PackageManager.md
@@ -1,3 +1,3 @@
# Type alias: PackageManager

Ƭ **PackageManager**: `"yarn"` \| `"pnpm"` \| `"npm"`
Ƭ **PackageManager**: `"yarn"` \| `"pnpm"` \| `"npm"` \| `"bun"`
Expand Up @@ -129,7 +129,7 @@ Do you want Nx Cloud to make your CI fast?

Type: `string`

Choices: [npm, pnpm, yarn]
Choices: [bun, npm, pnpm, yarn]

Default: `npm`

Expand Down
11 changes: 8 additions & 3 deletions e2e/utils/create-project-utils.ts
Expand Up @@ -78,7 +78,7 @@ export function newProject({
packages,
}: {
name?: string;
packageManager?: 'npm' | 'yarn' | 'pnpm';
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'bun';
unsetProjectNameAndRootFormat?: boolean;
readonly packages?: Array<NxPackage>;
} = {}): string {
Expand Down Expand Up @@ -240,7 +240,7 @@ export function runCreateWorkspace(
appName?: string;
style?: string;
base?: string;
packageManager?: 'npm' | 'yarn' | 'pnpm';
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'bun';
extraArgs?: string;
useDetectedPm?: boolean;
cwd?: string;
Expand Down Expand Up @@ -358,7 +358,7 @@ export function runCreatePlugin(
extraArgs,
useDetectedPm = false,
}: {
packageManager?: 'npm' | 'yarn' | 'pnpm';
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'bun';
extraArgs?: string;
useDetectedPm?: boolean;
}
Expand Down Expand Up @@ -543,6 +543,11 @@ export function newLernaWorkspace({
...json.resolutions,
...overrides,
};
} else if (packageManager === 'bun') {
json.overrides = {
...json.resolutions,
...overrides,
};
} else {
json.overrides = overrides;
}
Expand Down
8 changes: 5 additions & 3 deletions e2e/utils/get-env-info.ts
Expand Up @@ -23,7 +23,9 @@ export function getPublishedVersion(): string {
}

export function detectPackageManager(dir: string = ''): PackageManager {
return existsSync(join(dir, 'yarn.lock'))
return existsSync(join(dir, 'bun.lockb'))
? 'bun'
: existsSync(join(dir, 'yarn.lock'))
? 'yarn'
: existsSync(join(dir, 'pnpm-lock.yaml')) ||
existsSync(join(dir, 'pnpm-workspace.yaml'))
Expand Down Expand Up @@ -64,8 +66,8 @@ export function isVerboseE2ERun() {

export const e2eCwd = `${e2eRoot}/nx`;

export function getSelectedPackageManager(): 'npm' | 'yarn' | 'pnpm' {
return (process.env.SELECTED_PM as 'npm' | 'yarn' | 'pnpm') || 'npm';
export function getSelectedPackageManager(): 'npm' | 'yarn' | 'pnpm' | 'bun' {
return (process.env.SELECTED_PM as 'npm' | 'yarn' | 'pnpm' | 'bun') || 'npm';
}

export function getNpmMajorVersion(): string | undefined {
Expand Down
11 changes: 7 additions & 4 deletions e2e/workspace-create/src/create-nx-workspace.test.ts
Expand Up @@ -374,7 +374,7 @@ describe('create-nx-workspace', () => {
});

describe('Use detected package manager', () => {
function setupProject(envPm: 'npm' | 'yarn' | 'pnpm') {
function setupProject(envPm: 'npm' | 'yarn' | 'pnpm' | 'bun') {
process.env.SELECTED_PM = envPm;
runCreateWorkspace(uniq('pm'), {
preset: 'apps',
Expand All @@ -389,7 +389,8 @@ describe('create-nx-workspace', () => {
checkFilesExist(packageManagerLockFile['npm']);
checkFilesDoNotExist(
packageManagerLockFile['yarn'],
packageManagerLockFile['pnpm']
packageManagerLockFile['pnpm'],
packageManagerLockFile['bun']
);
process.env.SELECTED_PM = packageManager;
}, 90000);
Expand All @@ -401,7 +402,8 @@ describe('create-nx-workspace', () => {
checkFilesExist(packageManagerLockFile['pnpm']);
checkFilesDoNotExist(
packageManagerLockFile['yarn'],
packageManagerLockFile['npm']
packageManagerLockFile['npm'],
packageManagerLockFile['bun']
);
process.env.SELECTED_PM = packageManager;
}, 90000);
Expand All @@ -414,7 +416,8 @@ describe('create-nx-workspace', () => {
checkFilesExist(packageManagerLockFile['yarn']);
checkFilesDoNotExist(
packageManagerLockFile['pnpm'],
packageManagerLockFile['npm']
packageManagerLockFile['npm'],
packageManagerLockFile['bun']
);
process.env.SELECTED_PM = packageManager;
}, 90000);
Expand Down
1 change: 1 addition & 0 deletions packages/create-nx-workspace/src/internal-utils/prompts.ts
Expand Up @@ -110,6 +110,7 @@ export async function determinePackageManager(
{ name: 'npm', message: 'NPM' },
{ name: 'yarn', message: 'Yarn' },
{ name: 'pnpm', message: 'PNPM' },
{ name: 'bun', message: 'Bun' },
],
},
])
Expand Down
12 changes: 10 additions & 2 deletions packages/create-nx-workspace/src/utils/package-manager.ts
Expand Up @@ -7,12 +7,14 @@ import { join } from 'path';
* we duplicate the helper functions from @nx/workspace in this file.
*/

export const packageManagerList = ['pnpm', 'yarn', 'npm'] as const;
export const packageManagerList = ['pnpm', 'yarn', 'npm', 'bun'] as const;

export type PackageManager = typeof packageManagerList[number];

export function detectPackageManager(dir: string = ''): PackageManager {
return existsSync(join(dir, 'yarn.lock'))
return existsSync(join(dir, 'bun.lockb'))
? 'bun'
: existsSync(join(dir, 'yarn.lock'))
? 'yarn'
: existsSync(join(dir, 'pnpm-lock.yaml'))
? 'pnpm'
Expand Down Expand Up @@ -73,6 +75,12 @@ export function getPackageManagerCommand(
exec: 'npx',
globalAdd: 'npm i -g',
};
case 'bun':
return {
install: 'bun install --silent',
exec: 'bunx',
globalAdd: 'bun install -g',
};
}
}

Expand Down
Expand Up @@ -14,6 +14,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
npm: 'package-lock.json',
yarn: 'yarn.lock',
pnpm: 'pnpm-lock.yaml',
bun: 'bun.lockb',
};
const packageManager = detectPackageManager(host.root);
const packageLockFile = packageManagerLockFile[packageManager];
Expand Down
Expand Up @@ -19,6 +19,7 @@ export default function update(tree: Tree) {
npm: 'package-lock.json',
yarn: 'yarn.lock',
pnpm: 'pnpm-lock.yaml',
bun: 'bun.lockb',
};

for (const [name, config] of projects.entries()) {
Expand Down
2 changes: 1 addition & 1 deletion packages/nx/schemas/nx-schema.json
Expand Up @@ -366,7 +366,7 @@
"packageManager": {
"type": "string",
"description": "The default package manager to use.",
"enum": ["yarn", "pnpm", "npm"]
"enum": ["yarn", "pnpm", "npm", "bun"]
}
}
},
Expand Down
Expand Up @@ -262,6 +262,7 @@ function moveFilesToTempWorkspace(options: NormalizedOptions) {
options.packageManager === 'yarn' ? 'yarn.lock' : null,
options.packageManager === 'pnpm' ? 'pnpm-lock.yaml' : null,
options.packageManager === 'npm' ? 'package-lock.json' : null,
options.packageManager === 'bun' ? 'bun.lockb' : null,
];

const optionalCraFiles = ['README.md'];
Expand Down
11 changes: 9 additions & 2 deletions packages/nx/src/plugins/js/index.ts
Expand Up @@ -24,6 +24,7 @@ import { hashArray } from '../../hasher/file-hasher';
import { detectPackageManager } from '../../utils/package-manager';
import { workspaceRoot } from '../../utils/workspace-root';
import { nxVersion } from '../../utils/versions';
import { execSync } from 'child_process';

export const name = 'nx/js/dependencies-and-lockfile';

Expand Down Expand Up @@ -51,7 +52,10 @@ export const createNodes: CreateNodes = [
}

const lockFilePath = join(workspaceRoot, lockFile);
const lockFileContents = readFileSync(lockFilePath).toString();
const lockFileContents =
packageManager !== 'bun'
? readFileSync(lockFilePath).toString()
: execSync(lockFilePath).toString();
const lockFileHash = getLockFileHash(lockFileContents);

if (!lockFileNeedsReprocessing(lockFileHash)) {
Expand Down Expand Up @@ -91,7 +95,10 @@ export const createDependencies: CreateDependencies = (
parsedLockFile.externalNodes
) {
const lockFilePath = join(workspaceRoot, getLockFileName(packageManager));
const lockFileContents = readFileSync(lockFilePath).toString();
const lockFileContents =
packageManager !== 'bun'
? readFileSync(lockFilePath).toString()
: execSync(lockFilePath).toString();
const lockFileHash = getLockFileHash(lockFileContents);

if (!lockFileNeedsReprocessing(lockFileHash)) {
Expand Down
33 changes: 32 additions & 1 deletion packages/nx/src/plugins/js/lock-file/lock-file.ts
Expand Up @@ -45,11 +45,18 @@ import {
const YARN_LOCK_FILE = 'yarn.lock';
const NPM_LOCK_FILE = 'package-lock.json';
const PNPM_LOCK_FILE = 'pnpm-lock.yaml';
export const LOCKFILES = [YARN_LOCK_FILE, NPM_LOCK_FILE, PNPM_LOCK_FILE];
const BUN_LOCK_FILE = 'bun.lockb';
export const LOCKFILES = [
YARN_LOCK_FILE,
NPM_LOCK_FILE,
PNPM_LOCK_FILE,
BUN_LOCK_FILE,
];

const YARN_LOCK_PATH = join(workspaceRoot, YARN_LOCK_FILE);
const NPM_LOCK_PATH = join(workspaceRoot, NPM_LOCK_FILE);
const PNPM_LOCK_PATH = join(workspaceRoot, PNPM_LOCK_FILE);
const BUN_LOCK_PATH = join(workspaceRoot, BUN_LOCK_FILE);

/**
* Parses lock file and maps dependencies and metadata to {@link LockFileGraph}
Expand All @@ -73,6 +80,11 @@ export function getLockFileNodes(
if (packageManager === 'npm') {
return getNpmLockfileNodes(contents, lockFileHash);
}
if (packageManager === 'bun') {
// bun uses yarn v1 for the file format
const packageJson = readJsonFile('package.json');
return getYarnLockfileNodes(contents, lockFileHash, packageJson);
}
} catch (e) {
if (!isPostInstallProcess()) {
output.error({
Expand Down Expand Up @@ -104,6 +116,10 @@ export function getLockFileDependencies(
if (packageManager === 'npm') {
return getNpmLockfileDependencies(contents, lockFileHash, context);
}
if (packageManager === 'bun') {
// bun uses yarn v1 for the file format
return getYarnLockfileDependencies(contents, lockFileHash, context);
}
} catch (e) {
if (!isPostInstallProcess()) {
output.error({
Expand All @@ -126,6 +142,9 @@ export function lockFileExists(packageManager: PackageManager): boolean {
if (packageManager === 'npm') {
return existsSync(NPM_LOCK_PATH);
}
if (packageManager === 'bun') {
return existsSync(BUN_LOCK_PATH);
}
throw new Error(
`Unknown package manager ${packageManager} or lock file missing`
);
Expand All @@ -146,6 +165,9 @@ export function getLockFileName(packageManager: PackageManager): string {
if (packageManager === 'npm') {
return NPM_LOCK_FILE;
}
if (packageManager === 'bun') {
return BUN_LOCK_FILE;
}
throw new Error(`Unknown package manager: ${packageManager}`);
}

Expand All @@ -159,6 +181,9 @@ function getLockFilePath(packageManager: PackageManager): string {
if (packageManager === 'npm') {
return NPM_LOCK_PATH;
}
if (packageManager === 'bun') {
return BUN_LOCK_PATH;
}
throw new Error(`Unknown package manager: ${packageManager}`);
}

Expand Down Expand Up @@ -191,6 +216,12 @@ export function createLockFile(
const prunedGraph = pruneProjectGraph(graph, packageJson);
return stringifyNpmLockfile(prunedGraph, content, normalizedPackageJson);
}
if (packageManager === 'bun') {
output.log({
title:
"Unable to create bun lock files. Run bun install it's just as quick",
});
}
} catch (e) {
if (!isPostInstallProcess()) {
const additionalInfo = [
Expand Down
29 changes: 28 additions & 1 deletion packages/nx/src/utils/package-manager.spec.ts
Expand Up @@ -30,6 +30,8 @@ describe('package-manager', () => {
return false;
case 'package-lock.json':
return false;
case 'bun.lockb':
return false;
default:
return jest.requireActual('fs').existsSync(p);
}
Expand All @@ -49,6 +51,8 @@ describe('package-manager', () => {
return true;
case 'package-lock.json':
return false;
case 'bun.lockb':
return false;
default:
return jest.requireActual('fs').existsSync(p);
}
Expand All @@ -58,6 +62,27 @@ describe('package-manager', () => {
expect(fs.existsSync).toHaveBeenCalledTimes(3);
});

it('should detect bun package manager from bun.lockb', () => {
jest.spyOn(configModule, 'readNxJson').mockReturnValueOnce({});
jest.spyOn(fs, 'existsSync').mockImplementation((p) => {
switch (p) {
case 'yarn.lock':
return false;
case 'pnpm-lock.yaml':
return false;
case 'package-lock.json':
return false;
case 'bun.lockb':
return true;
default:
return jest.requireActual('fs').existsSync(p);
}
});
const packageManager = detectPackageManager();
expect(packageManager).toEqual('bun');
expect(fs.existsSync).toHaveBeenCalledTimes(3);
});

it('should use npm package manager as default', () => {
jest.spyOn(configModule, 'readNxJson').mockReturnValueOnce({});
jest.spyOn(fs, 'existsSync').mockImplementation((p) => {
Expand All @@ -68,13 +93,15 @@ describe('package-manager', () => {
return false;
case 'package-lock.json':
return false;
case 'bun.lockb':
return false;
default:
return jest.requireActual('fs').existsSync(p);
}
});
const packageManager = detectPackageManager();
expect(packageManager).toEqual('npm');
expect(fs.existsSync).toHaveBeenCalledTimes(5);
expect(fs.existsSync).toHaveBeenCalledTimes(6);
});
});

Expand Down

0 comments on commit 99421c3

Please sign in to comment.