Skip to content

Commit

Permalink
feat: Add Bun as supported package manager
Browse files Browse the repository at this point in the history
  • Loading branch information
colinhacks committed Aug 8, 2023
1 parent 4cf3b18 commit a28a319
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 0 deletions.
1 change: 1 addition & 0 deletions actions/new.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const askForPackageManager = async (): Promise<Answers> => {
PackageManager.NPM,
PackageManager.YARN,
PackageManager.PNPM,
PackageManager.BUN,
]),
];
const prompt = inquirer.createPromptModule();
Expand Down
27 changes: 27 additions & 0 deletions lib/package-managers/bun.package-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Runner, RunnerFactory } from '../runners';
import { BunRunner } from '../runners/bun.runner';
import { AbstractPackageManager } from './abstract.package-manager';
import { PackageManager } from './package-manager';
import { PackageManagerCommands } from './package-manager-commands';

export class BunPackageManager extends AbstractPackageManager {
constructor() {
super(RunnerFactory.create(Runner.BUN) as BunRunner);
}

public get name() {
return PackageManager.BUN.toUpperCase();
}

get cli(): PackageManagerCommands {
return {
install: 'install',
add: 'add',
update: 'install --force',
remove: 'remove',
saveFlag: '',
saveDevFlag: '--development',
silentFlag: '--silent',
};
}
}
1 change: 1 addition & 0 deletions lib/package-managers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export * from './abstract.package-manager';
export * from './npm.package-manager';
export * from './yarn.package-manager';
export * from './pnpm.package-manager';
export * from './bun.package-manager';
export * from './project.dependency';
export * from './package-manager-commands';
8 changes: 8 additions & 0 deletions lib/package-managers/package-manager.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NpmPackageManager } from './npm.package-manager';
import { PackageManager } from './package-manager';
import { YarnPackageManager } from './yarn.package-manager';
import { PnpmPackageManager } from './pnpm.package-manager';
import { BunPackageManager } from './bun.package-manager';

export class PackageManagerFactory {
public static create(name: PackageManager | string): AbstractPackageManager {
Expand All @@ -14,6 +15,8 @@ export class PackageManagerFactory {
return new YarnPackageManager();
case PackageManager.PNPM:
return new PnpmPackageManager();
case PackageManager.BUN:
return new BunPackageManager();
default:
throw new Error(`Package manager ${name} is not managed.`);
}
Expand All @@ -35,6 +38,11 @@ export class PackageManagerFactory {
return this.create(PackageManager.PNPM);
}

const hasBunLockFile = files.includes('bun.lockb');
if (hasBunLockFile) {
return this.create(PackageManager.BUN);
}

return this.create(DEFAULT_PACKAGE_MANAGER);
} catch (error) {
return this.create(DEFAULT_PACKAGE_MANAGER);
Expand Down
1 change: 1 addition & 0 deletions lib/package-managers/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum PackageManager {
NPM = 'npm',
YARN = 'yarn',
PNPM = 'pnpm',
BUN = 'bun',
}
7 changes: 7 additions & 0 deletions lib/runners/bun.runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AbstractRunner } from './abstract.runner';

export class BunRunner extends AbstractRunner {
constructor() {
super('bun');
}
}
4 changes: 4 additions & 0 deletions lib/runners/runner.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Runner } from './runner';
import { SchematicRunner } from './schematic.runner';
import { YarnRunner } from './yarn.runner';
import { PnpmRunner } from './pnpm.runner';
import { BunRunner } from './bun.runner';

export class RunnerFactory {
public static create(runner: Runner) {
Expand All @@ -20,6 +21,9 @@ export class RunnerFactory {
case Runner.PNPM:
return new PnpmRunner();

case Runner.BUN:
return new BunRunner();

default:
console.info(chalk.yellow(`[WARN] Unsupported runner: ${runner}`));
}
Expand Down
1 change: 1 addition & 0 deletions lib/runners/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export enum Runner {
NPM,
YARN,
PNPM,
BUN,
}
125 changes: 125 additions & 0 deletions test/lib/package-managers/bun.package-manager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { join } from 'path';
import {
PackageManagerCommands,
BunPackageManager,
} from '../../../lib/package-managers';
import { BunRunner } from '../../../lib/runners/bun.runner';

jest.mock('../../../lib/runners/bun.runner');

describe('BunPackageManager', () => {
let packageManager: BunPackageManager;
beforeEach(() => {
(BunRunner as any).mockClear();
(BunRunner as any).mockImplementation(() => {
return {
run: (): Promise<void> => Promise.resolve(),
};
});
packageManager = new BunPackageManager();
});
it('should be created', () => {
expect(packageManager).toBeInstanceOf(BunPackageManager);
});
it('should have the correct cli commands', () => {
const expectedValues: PackageManagerCommands = {
install: 'install',
add: 'add',
update: 'install --force',
remove: 'remove',
saveFlag: '',
saveDevFlag: '--development',
silentFlag: '--silent',
};
expect(packageManager.cli).toMatchObject(expectedValues);
});
describe('install', () => {
it('should use the proper command for installing', () => {
const spy = jest.spyOn((packageManager as any).runner, 'run');
const dirName = '/tmp';
const testDir = join(process.cwd(), dirName);
packageManager.install(dirName, 'bun');
expect(spy).toBeCalledWith('install --silent', true, testDir);
});
});
describe('addProduction', () => {
it('should use the proper command for adding production dependencies', () => {
const spy = jest.spyOn((packageManager as any).runner, 'run');
const dependencies = ['@nestjs/common', '@nestjs/core'];
const tag = '5.0.0';
const command = `add ${dependencies
.map((dependency) => `${dependency}@${tag}`)
.join(' ')}`;
packageManager.addProduction(dependencies, tag);
expect(spy).toBeCalledWith(command, true);
});
});
describe('addDevelopment', () => {
it('should use the proper command for adding development dependencies', () => {
const spy = jest.spyOn((packageManager as any).runner, 'run');
const dependencies = ['@nestjs/common', '@nestjs/core'];
const tag = '5.0.0';
const command = `add --development ${dependencies
.map((dependency) => `${dependency}@${tag}`)
.join(' ')}`;
packageManager.addDevelopment(dependencies, tag);
expect(spy).toBeCalledWith(command, true);
});
});
describe('updateProduction', () => {
it('should use the proper command for updating production dependencies', () => {
const spy = jest.spyOn((packageManager as any).runner, 'run');
const dependencies = ['@nestjs/common', '@nestjs/core'];
const command = `install --force ${dependencies.join(' ')}`;
packageManager.updateProduction(dependencies);
expect(spy).toBeCalledWith(command, true);
});
});
describe('updateDevelopment', () => {
it('should use the proper command for updating development dependencies', () => {
const spy = jest.spyOn((packageManager as any).runner, 'run');
const dependencies = ['@nestjs/common', '@nestjs/core'];
const command = `install --force ${dependencies.join(' ')}`;
packageManager.updateDevelopment(dependencies);
expect(spy).toBeCalledWith(command, true);
});
});
describe('upgradeProduction', () => {
it('should use the proper command for upgrading production dependencies', () => {
const spy = jest.spyOn((packageManager as any).runner, 'run');
const dependencies = ['@nestjs/common', '@nestjs/core'];
const tag = '5.0.0';
const uninstallCommand = `remove ${dependencies.join(' ')}`;

const installCommand = `add ${dependencies
.map((dependency) => `${dependency}@${tag}`)
.join(' ')}`;

return packageManager.upgradeProduction(dependencies, tag).then(() => {
expect(spy.mock.calls).toEqual([
[uninstallCommand, true],
[installCommand, true],
]);
});
});
});
describe('upgradeDevelopment', () => {
it('should use the proper command for upgrading production dependencies', () => {
const spy = jest.spyOn((packageManager as any).runner, 'run');
const dependencies = ['@nestjs/common', '@nestjs/core'];
const tag = '5.0.0';
const uninstallCommand = `remove --development ${dependencies.join(' ')}`;

const installCommand = `add --development ${dependencies
.map((dependency) => `${dependency}@${tag}`)
.join(' ')}`;

return packageManager.upgradeDevelopment(dependencies, tag).then(() => {
expect(spy.mock.calls).toEqual([
[uninstallCommand, true],
[installCommand, true],
]);
});
});
});
});
10 changes: 10 additions & 0 deletions test/lib/package-managers/package-manager.factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PackageManagerFactory,
PnpmPackageManager,
YarnPackageManager,
BunPackageManager,
} from '../../../lib/package-managers';

jest.mock('fs', () => ({
Expand Down Expand Up @@ -45,6 +46,15 @@ describe('PackageManagerFactory', () => {
);
});

it('should return BunPackageManager when "bun.lockb" file is found', async () => {
(fs.promises.readdir as jest.Mock).mockResolvedValue(['bun.lockb']);

const whenPackageManager = PackageManagerFactory.find();
await expect(whenPackageManager).resolves.toBeInstanceOf(
BunPackageManager,
);
});

describe('when there are all supported lock files', () => {
it('should prioritize "yarn.lock" file over all the others lock files', async () => {
(fs.promises.readdir as jest.Mock).mockResolvedValue([
Expand Down

0 comments on commit a28a319

Please sign in to comment.