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)

feat(core): update new generator schema for bun and fix workspaces setup for bun

fix(core): update ci-workflow snapshot
feat(core): check lock file for bunlock on push

Signed-off-by: Jordan Hall <Jordan@libertyware.co.uk>

fix(core): add bun as a valid option of packageManager for preset generator

fix(repo): make registry optional because of bun

fix: handle where get registry is optional
  • Loading branch information
Jordan-Hall committed May 21, 2024
1 parent 08ef0e4 commit d424f93
Show file tree
Hide file tree
Showing 32 changed files with 475 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 d424f93

Please sign in to comment.