Skip to content
Open
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
11 changes: 11 additions & 0 deletions change/change-bun-workspace-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"type": "patch",
"comment": "Add bun as a supported workspace manager in workspace-tools, including manager detection and workspace fixture/test coverage.",
"packageName": "workspace-tools",
"email": "email not defined",
"dependentChangeType": "patch"
}
]
}
3 changes: 2 additions & 1 deletion packages/workspace-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
A collection of utilities that are useful in a git-controlled monorepo managed by one of these tools:

- lerna
- bun workspaces
- npm workspaces
- pnpm workspaces
- rush
Expand All @@ -20,7 +21,7 @@ Override the `maxBuffer` value for git processes, for example if the repo is ver

### PREFERRED_WORKSPACE_MANAGER

Sometimes if multiple workspace/monorepo manager files are checked in, it's necessary to hint which manager is used: `npm`, `yarn`, `pnpm`, `rush`, or `lerna`. Some APIs also accept a `manager` parameter, which is now the preferred method when available.
Sometimes if multiple workspace/monorepo manager files are checked in, it's necessary to hint which manager is used: `bun`, `npm`, `yarn`, `pnpm`, `rush`, or `lerna`. Some APIs also accept a `manager` parameter, which is now the preferred method when available.

### VERBOSE

Expand Down
2 changes: 1 addition & 1 deletion packages/workspace-tools/etc/workspace-tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ export function stageAndCommit(patterns: string[], message: string, cwd: string,
export type WorkspaceInfos = WorkspacePackageInfo[];

// @public (undocumented)
export type WorkspaceManager = "yarn" | "pnpm" | "rush" | "npm" | "lerna";
export type WorkspaceManager = "yarn" | "pnpm" | "rush" | "npm" | "lerna" | "bun";

// @public (undocumented)
interface WorkspaceManagerAndRoot {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This fixture is intended to match the other `monorepo-basic-*` fixtures (bun):

- Workspaces: `["packages/*", "individual"]`
- Same basic dependencies at root
- `package-a` depends on `react` and `react-dom` (to introduce a `peerDependency`)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "individual",
"license": "MIT",
"version": "0.1.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "monorepo-basic-bun",
"description": "Basic monorepo with bun (workspaces and deps should match other monorepo-basic-*)",
"license": "MIT",
"version": "0.1.0",
"private": true,
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^4.0.0"
},
"workspaces": {
"packages": [
"packages/*",
"individual"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "package-a",
"license": "MIT",
"version": "0.1.0",
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "package-b",
"license": "MIT",
"version": "0.1.0"
}
1 change: 1 addition & 0 deletions packages/workspace-tools/src/__tests__/setupFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type RealFixtureName =
| "extra-yarn-1"
| "extra-yarn-berry"
| "monorepo-basic-npm"
| "monorepo-basic-bun"
| "monorepo-basic-pnpm"
| "monorepo-basic-yarn-1"
| "monorepo-basic-yarn-berry"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe("getWorkspaceManagerAndRoot", () => {
{ desc: "yarn", manager: "yarn", fixtureName: "monorepo-basic-yarn-1" },
{ desc: "yarn berry", manager: "yarn", fixtureName: "monorepo-basic-yarn-berry" },
{ desc: "pnpm", manager: "pnpm", fixtureName: "monorepo-basic-pnpm" },
{ desc: "bun", manager: "bun", fixtureName: "monorepo-basic-bun" },
{ desc: "rush", manager: "rush", fixtureName: "monorepo-rush-pnpm" },
{ desc: "npm", manager: "npm", fixtureName: "monorepo-basic-npm" },
{ desc: "lerna + npm", manager: "lerna", fixtureName: "monorepo-basic-lerna-npm" },
Expand All @@ -44,6 +45,17 @@ describe("getWorkspaceManagerAndRoot", () => {
});
});

it("detects bun workspace root with bun.lockb", () => {
const repoRoot = setupFixture("monorepo-basic-bun");
fs.rmSync(path.join(repoRoot, "bun.lock"));
fs.writeFileSync(path.join(repoRoot, "bun.lockb"), "");

expect(getWorkspaceManagerAndRoot(repoRoot)).toEqual({
root: repoRoot,
manager: "bun",
});
});

it("handles nested monorepo", () => {
// This fixture has a monorepo under the "monorepo" folder, not at the git root.
const repoRoot = setupFixture("monorepo-nested", { git: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe("getWorkspaceInfos", () => {
}>([
{ manager: "yarn", desc: "yarn", fixtureName: "monorepo-basic-yarn-1" },
{ manager: "pnpm", desc: "pnpm", fixtureName: "monorepo-basic-pnpm" },
{ manager: "bun", desc: "bun", fixtureName: "monorepo-basic-bun" },
{ manager: "rush", desc: "rush + pnpm", fixtureName: "monorepo-rush-pnpm" },
{ manager: "rush", desc: "rush + yarn", fixtureName: "monorepo-rush-yarn" },
{ manager: "npm", desc: "npm", fixtureName: "monorepo-basic-npm" },
Expand Down
2 changes: 1 addition & 1 deletion packages/workspace-tools/src/types/WorkspaceManager.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type WorkspaceManager = "yarn" | "pnpm" | "rush" | "npm" | "lerna";
export type WorkspaceManager = "yarn" | "pnpm" | "rush" | "npm" | "lerna" | "bun";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { getPackageJsonWorkspacePatterns } from "./getPackageJsonWorkspacePatterns.js";
import type { WorkspaceUtilities } from "./WorkspaceUtilities.js";

/**
* Bun uses the standard package.json "workspaces" field.
*/
export const bunUtilities: WorkspaceUtilities = {
getWorkspacePatterns: getPackageJsonWorkspacePatterns,
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ export const managerFiles = {
rush: "rush.json",
yarn: "yarn.lock",
pnpm: "pnpm-workspace.yaml",
bun: ["bun.lock", "bun.lockb"],
npm: "package-lock.json",
} as const;

function getManagerFileNames(manager: WorkspaceManager): string[] {
const fileOrFiles = managerFiles[manager];
return typeof fileOrFiles === "string" ? [fileOrFiles] : [...fileOrFiles];
}

/**
* Get the preferred workspace/monorepo manager based on `process.env.PREFERRED_WORKSPACE_MANAGER`
* (if valid).
Expand Down Expand Up @@ -57,15 +63,17 @@ export function getWorkspaceManagerAndRoot(
}

managerOverride ??= getPreferredWorkspaceManager();
const filesToSearch = managerOverride ? managerFiles[managerOverride] : Object.values(managerFiles);
const filesToSearch = managerOverride
? getManagerFileNames(managerOverride)
: Object.values(managerFiles).flatMap((files) => (Array.isArray(files) ? files : [files]));
const managerFile = searchUp(filesToSearch, cwd);

if (managerFile) {
const managerFileName = path.basename(managerFile);
cache.set(cwd, {
manager:
managerOverride ||
(Object.keys(managerFiles) as WorkspaceManager[]).find((name) => managerFiles[name] === managerFileName)!,
(Object.keys(managerFiles) as WorkspaceManager[]).find((name) => getManagerFileNames(name).includes(managerFileName))!,
root: path.dirname(managerFile),
});
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const utils: Partial<Record<WorkspaceManager, WorkspaceUtilities>> = {};
*/
export function getWorkspaceUtilities(manager: WorkspaceManager): WorkspaceUtilities {
switch (manager) {
case "bun":
// eslint-disable-next-line @typescript-eslint/consistent-type-imports, @typescript-eslint/no-require-imports
utils.bun ??= (require("./bun") as typeof import("./bun")).bunUtilities;
break;

case "npm":
// eslint-disable-next-line @typescript-eslint/consistent-type-imports, @typescript-eslint/no-require-imports
utils.npm ??= (require("./npm") as typeof import("./npm")).npmUtilities;
Expand Down