mirror of
https://github.com/proxysoul/Empryo.git
synced 2026-11-07 06:25:57 -10:00
fix: detect package manager from monorepo root lockfile
Walk up to 5 directories to find pnpm-lock.yaml or yarn.lock so sub-packages in monorepos get the correct pm. Also fix typecheck detection to prefer scripts.typecheck over tsconfig.json presence.
This commit is contained in:
2 files changed
+129
-17
No files matched your search
+41
-17
@@ -1,5 +1,5 @@
|
||||
import { access, readdir, readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { dirname, join } from "node:path";
|
||||
import type { ToolResult } from "../../types/index.js";
|
||||
import { compressShellOutputFull } from "./shell-compress.js";
|
||||
import { saveTee, truncateWithTee } from "./tee.js";
|
||||
@@ -75,11 +75,11 @@ export async function detectProfile(cwd: string): Promise<ProjectProfile> {
|
||||
profile.test = scripts.test ?? "bun test";
|
||||
profile.build = scripts.build ? `bun run build` : null;
|
||||
profile.lint = scripts.lint ? "bun run lint" : await detectJsLinter(cwd, "bunx");
|
||||
profile.typecheck = (await has("tsconfig.json"))
|
||||
? scripts.typecheck
|
||||
? "bun run typecheck"
|
||||
: "bunx tsc --noEmit"
|
||||
: null;
|
||||
profile.typecheck = scripts.typecheck
|
||||
? "bun run typecheck"
|
||||
: (await has("tsconfig.json"))
|
||||
? "bunx tsc --noEmit"
|
||||
: null;
|
||||
profile.run = scripts.dev ? "bun run dev" : scripts.start ? "bun run start" : null;
|
||||
profile.format = scripts.format ? "bun run format" : await detectJsFormatter(cwd, "bunx");
|
||||
profile.formatAndLint = await detectFormatAndLint(cwd, "bunx");
|
||||
@@ -99,16 +99,16 @@ export async function detectProfile(cwd: string): Promise<ProjectProfile> {
|
||||
|
||||
// JS/TS — pnpm/yarn/npm
|
||||
if (await has("package.json")) {
|
||||
const pm = (await has("pnpm-lock.yaml")) ? "pnpm" : (await has("yarn.lock")) ? "yarn" : "npm";
|
||||
const pm = await detectJsPm(cwd);
|
||||
const run = pm === "npm" ? "npm run" : pm;
|
||||
profile.test = scripts.test ? `${run} test` : null;
|
||||
profile.build = scripts.build ? `${run} build` : null;
|
||||
profile.lint = scripts.lint ? `${run} lint` : await detectJsLinter(cwd, "npx");
|
||||
profile.typecheck = (await has("tsconfig.json"))
|
||||
? scripts.typecheck
|
||||
? `${run} typecheck`
|
||||
: "npx tsc --noEmit"
|
||||
: null;
|
||||
profile.typecheck = scripts.typecheck
|
||||
? `${run} typecheck`
|
||||
: (await has("tsconfig.json"))
|
||||
? "npx tsc --noEmit"
|
||||
: null;
|
||||
profile.run = scripts.dev ? `${run} dev` : scripts.start ? `${run} start` : null;
|
||||
profile.format = scripts.format ? `${run} format` : await detectJsFormatter(cwd, "npx");
|
||||
profile.formatAndLint = await detectFormatAndLint(cwd, "npx");
|
||||
@@ -360,6 +360,27 @@ export async function detectProfile(cwd: string): Promise<ProjectProfile> {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/** Detect JS package manager by walking up to find a lockfile (handles monorepo sub-packages). */
|
||||
async function detectJsPm(cwd: string): Promise<"pnpm" | "yarn" | "npm"> {
|
||||
const lockfiles: [string, "pnpm" | "yarn"][] = [
|
||||
["pnpm-lock.yaml", "pnpm"],
|
||||
["yarn.lock", "yarn"],
|
||||
];
|
||||
let dir = cwd;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
for (const [file, pm] of lockfiles) {
|
||||
try {
|
||||
await access(join(dir, file));
|
||||
return pm;
|
||||
} catch {}
|
||||
}
|
||||
const parent = dirname(dir);
|
||||
if (parent === dir) break;
|
||||
dir = parent;
|
||||
}
|
||||
return "npm";
|
||||
}
|
||||
|
||||
async function readPackageScripts(cwd: string): Promise<Record<string, string>> {
|
||||
try {
|
||||
const pkg = JSON.parse(await readFile(join(cwd, "package.json"), "utf-8"));
|
||||
@@ -452,8 +473,8 @@ async function detectJsTypecheck(cwd: string): Promise<string | null> {
|
||||
if (!(await has("tsconfig.json"))) return null;
|
||||
if ((await has("bun.lock")) || (await has("bun.lockb"))) return "bunx tsc --noEmit";
|
||||
if ((await has("deno.json")) || (await has("deno.lock"))) return "deno check .";
|
||||
if (await has("pnpm-lock.yaml")) return "pnpm tsc --noEmit";
|
||||
return "npx tsc --noEmit";
|
||||
const pm = await detectJsPm(cwd);
|
||||
return pm === "pnpm" ? "pnpm tsc --noEmit" : "npx tsc --noEmit";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -473,12 +494,15 @@ export async function detectNativeChecks(cwd: string): Promise<string | null> {
|
||||
const cmds: string[] = [];
|
||||
|
||||
// JS/TS — detect runner for local tool resolution
|
||||
const pm = await detectJsPm(cwd);
|
||||
const runner =
|
||||
(await has("bun.lock")) || (await has("bun.lockb"))
|
||||
? "bunx"
|
||||
: (await has("package.json"))
|
||||
? "npx"
|
||||
: "";
|
||||
: pm === "pnpm"
|
||||
? "pnpm exec"
|
||||
: (await has("package.json"))
|
||||
? "npx"
|
||||
: "";
|
||||
const jsLint = await detectJsLinter(cwd, runner);
|
||||
if (jsLint) cmds.push(jsLint);
|
||||
const jsTc = await detectJsTypecheck(cwd);
|
||||
|
||||
@@ -413,6 +413,94 @@ describe(".NET project", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── Monorepo (Turbo/pnpm/yarn) ──────────────────────────────
|
||||
|
||||
describe("pnpm monorepo (turbo)", () => {
|
||||
it("detects typecheck from scripts even without tsconfig.json in root", async () => {
|
||||
const cwd = makeProject("pnpm-turbo-root", {
|
||||
"package.json": JSON.stringify({
|
||||
scripts: { build: "turbo build", lint: "turbo lint", typecheck: "turbo typecheck", dev: "turbo dev" },
|
||||
}),
|
||||
"pnpm-lock.yaml": "",
|
||||
"pnpm-workspace.yaml": "packages:\n - 'apps/*'\n - 'packages/*'",
|
||||
"turbo.json": "{}",
|
||||
});
|
||||
const p = await detectProfile(cwd);
|
||||
expect(p.typecheck).toBe("pnpm typecheck");
|
||||
expect(p.build).toBe("pnpm build");
|
||||
expect(p.lint).toBe("pnpm lint");
|
||||
expect(p.run).toBe("pnpm dev");
|
||||
});
|
||||
|
||||
it("sub-package detects pnpm from root lockfile", async () => {
|
||||
const root = makeProject("pnpm-turbo-sub", {
|
||||
"package.json": JSON.stringify({ scripts: { dev: "turbo dev" } }),
|
||||
"pnpm-lock.yaml": "",
|
||||
"pnpm-workspace.yaml": "packages:\n - 'apps/*'",
|
||||
"apps/web/package.json": JSON.stringify({
|
||||
scripts: { build: "next build", dev: "next dev", lint: "eslint", typecheck: "tsc --noEmit" },
|
||||
}),
|
||||
"apps/web/tsconfig.json": "{}",
|
||||
});
|
||||
const p = await detectProfile(join(root, "apps/web"));
|
||||
expect(p.build).toBe("pnpm build");
|
||||
expect(p.lint).toBe("pnpm lint");
|
||||
expect(p.typecheck).toBe("pnpm typecheck");
|
||||
expect(p.run).toBe("pnpm dev");
|
||||
});
|
||||
|
||||
it("sub-package without scripts falls back to pnpm tsc", async () => {
|
||||
const root = makeProject("pnpm-turbo-sub-noscript", {
|
||||
"pnpm-lock.yaml": "",
|
||||
"apps/api/package.json": JSON.stringify({ scripts: { dev: "wrangler dev" } }),
|
||||
"apps/api/tsconfig.json": "{}",
|
||||
});
|
||||
const p = await detectProfile(join(root, "apps/api"));
|
||||
expect(p.typecheck).toBe("npx tsc --noEmit");
|
||||
expect(p.run).toBe("pnpm dev");
|
||||
});
|
||||
});
|
||||
|
||||
describe("yarn monorepo", () => {
|
||||
it("sub-package detects yarn from root lockfile", async () => {
|
||||
const root = makeProject("yarn-mono-sub", {
|
||||
"package.json": JSON.stringify({ workspaces: ["packages/*"] }),
|
||||
"yarn.lock": "",
|
||||
"packages/ui/package.json": JSON.stringify({
|
||||
scripts: { build: "tsc", test: "jest", lint: "eslint ." },
|
||||
}),
|
||||
"packages/ui/tsconfig.json": "{}",
|
||||
});
|
||||
const p = await detectProfile(join(root, "packages/ui"));
|
||||
expect(p.test).toBe("yarn test");
|
||||
expect(p.build).toBe("yarn build");
|
||||
expect(p.lint).toBe("yarn lint");
|
||||
});
|
||||
|
||||
it("sub-package without lockfile does not fall back to npm", async () => {
|
||||
const root = makeProject("yarn-mono-no-npm", {
|
||||
"yarn.lock": "",
|
||||
"packages/core/package.json": JSON.stringify({ scripts: { build: "tsc" } }),
|
||||
});
|
||||
const p = await detectProfile(join(root, "packages/core"));
|
||||
expect(p.build).toBe("yarn build");
|
||||
});
|
||||
});
|
||||
|
||||
describe("bun monorepo", () => {
|
||||
it("detects typecheck from scripts without tsconfig.json", async () => {
|
||||
const cwd = makeProject("bun-turbo-root", {
|
||||
"bun.lock": "",
|
||||
"package.json": JSON.stringify({
|
||||
scripts: { typecheck: "turbo typecheck", build: "turbo build" },
|
||||
}),
|
||||
});
|
||||
const p = await detectProfile(cwd);
|
||||
expect(p.typecheck).toBe("bun run typecheck");
|
||||
expect(p.build).toBe("bun run build");
|
||||
});
|
||||
});
|
||||
|
||||
// ── Edge Cases ──────────────────────────────────────────────
|
||||
|
||||
describe("edge cases", () => {
|
||||
|
||||
Reference in new issue
Block a user