Skip to content

Commit

Permalink
feat(core): add bun package manager (#22602)
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)

Other option is to use the -y command on add and install. This will
create a yarn lock file and then createLockFile can just modify the
yarn.lock file instead?

This is the PR made from #19113 and pushed due to #22402 being closed.

PS Bun feels more stable since the PR was first created!

This PR will resolve #22283 and start of #21075
  • Loading branch information
Jordan-Hall authored May 22, 2024
1 parent 383be1f commit 80702b5
Show file tree
Hide file tree
Showing 32 changed files with 490 additions and 35 deletions.
2 changes: 1 addition & 1 deletion docs/generated/cli/create-nx-workspace.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Type alias: PackageManager

Ƭ **PackageManager**: `"yarn"` \| `"pnpm"` \| `"npm"`
Ƭ **PackageManager**: `"yarn"` \| `"pnpm"` \| `"npm"` \| `"bun"`
Original file line number Diff line number Diff line change
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/packages/workspace/generators/new.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"packageManager": {
"description": "The package manager used to install dependencies.",
"type": "string",
"enum": ["npm", "yarn", "pnpm"]
"enum": ["npm", "yarn", "pnpm", "bun"]
},
"framework": {
"description": "The framework which the application is using",
Expand Down
2 changes: 1 addition & 1 deletion docs/generated/packages/workspace/generators/preset.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"packageManager": {
"description": "The package manager used to install dependencies.",
"type": "string",
"enum": ["npm", "yarn", "pnpm"]
"enum": ["npm", "yarn", "pnpm", "bun"]
},
"framework": {
"description": "The framework which the application is using",
Expand Down
13 changes: 13 additions & 0 deletions e2e/utils/command-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,19 @@ export function getPackageManagerCommand({
list: 'pnpm ls --depth 10',
runLerna: `pnpm exec lerna`,
},
bun: {
createWorkspace: `bunx create-nx-workspace@${publishedVersion}`,
run: (script: string, args: string) => `bun run ${script} -- ${args}`,
runNx: `bunx nx`,
runNxSilent: `bunx nx`,
runUninstalledPackage: `bunx --yes`,
install: 'bun install',
ciInstall: 'bun install --no-cache',
addProd: 'bun install',
addDev: 'bun install -D',
list: 'bun pm ls',
runLerna: `bunx lerna`,
},
}[packageManager.trim() as PackageManager];
}

Expand Down
11 changes: 8 additions & 3 deletions e2e/utils/create-project-utils.ts
Original file line number Diff line number Diff line change
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
9 changes: 6 additions & 3 deletions e2e/utils/get-env-info.ts
Original file line number Diff line number Diff line change
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 Expand Up @@ -108,6 +110,7 @@ export const packageManagerLockFile = {
npm: 'package-lock.json',
yarn: 'yarn.lock',
pnpm: 'pnpm-lock.yaml',
bun: 'bun.lockb',
};

export function ensureCypressInstallation() {
Expand Down
24 changes: 20 additions & 4 deletions e2e/workspace-create/src/create-nx-workspace.test.ts
Original file line number Diff line number Diff line change
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,21 @@ describe('create-nx-workspace', () => {
checkFilesExist(packageManagerLockFile['pnpm']);
checkFilesDoNotExist(
packageManagerLockFile['yarn'],
packageManagerLockFile['npm']
packageManagerLockFile['npm'],
packageManagerLockFile['bun']
);
process.env.SELECTED_PM = packageManager;
}, 90000);
}

if (packageManager === 'bun') {
it('should use bun when invoked with bunx', () => {
setupProject('bun');
checkFilesExist(packageManagerLockFile['bun']);
checkFilesDoNotExist(
packageManagerLockFile['yarn'],
packageManagerLockFile['npm'],
packageManagerLockFile['pnpm']
);
process.env.SELECTED_PM = packageManager;
}, 90000);
Expand All @@ -414,7 +429,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
Original file line number Diff line number Diff line change
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
5 changes: 5 additions & 0 deletions packages/create-nx-workspace/src/utils/nx/ab-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ export async function recordStat(opts: {

function shouldRecordStats(): boolean {
const pmc = getPackageManagerCommand();
if (!pmc.getRegistryUrl) {
// Fallback on true as Package management doesn't support reading config for registry.
// currently Bun doesn't support fetching config settings https://github.com/oven-sh/bun/issues/7140
return true;
}
try {
const stdout = execSync(pmc.getRegistryUrl, { encoding: 'utf-8' });
const url = new URL(stdout.trim());
Expand Down
16 changes: 13 additions & 3 deletions packages/create-nx-workspace/src/utils/package-manager.ts
Original file line number Diff line number Diff line change
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 All @@ -38,7 +40,8 @@ export function getPackageManagerCommand(
exec: string;
preInstall?: string;
globalAdd: string;
getRegistryUrl: string;
// Make this required once bun adds programatically support for reading config https://github.com/oven-sh/bun/issues/7140
getRegistryUrl?: string;
} {
const pmVersion = getPackageManagerVersion(packageManager);
const [pmMajor, pmMinor] = pmVersion.split('.');
Expand Down Expand Up @@ -79,6 +82,13 @@ export function getPackageManagerCommand(
globalAdd: 'npm i -g',
getRegistryUrl: 'npm config get registry',
};
case 'bun':
// bun doesn't current support programatically reading config https://github.com/oven-sh/bun/issues/7140
return {
install: 'bun install --silent --ignore-scripts',
exec: 'bunx',
globalAdd: 'bun install -g',
};
}
}

Expand Down
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,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
Original file line number Diff line number Diff line change
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(`bun ${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(`bun ${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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 80702b5

Please sign in to comment.