diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 661d8952..00000000 --- a/commitlint.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: ["@commitlint/config-conventional"] -}; diff --git a/src/parser.ts b/src/parser.ts index 0ca32283..5524cdff 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -2,6 +2,7 @@ import { existsSync } from "fs" import * as path from "path" import Shellwords from "shellwords-ts" import { PackageJson } from "./package" +import { getBinaries } from "./pnp" export enum CommandType { script = "script", @@ -102,12 +103,16 @@ export class CommandParser { [["pnpm", "run"], [CommandType.script]], ] - bins: string[] = [] + binPath: string[] = [] + binsPnp = new Set() constructor(public pkg: PackageJson, cwd = process.cwd()) { while (cwd != "/") { const p = path.resolve(cwd, "./node_modules/.bin") - if (existsSync(p)) this.bins.push(p) + if (existsSync(p)) this.binPath.push(p) + if (existsSync(path.resolve(cwd, ".pnp.js"))) { + this.binsPnp = new Set(getBinaries(cwd, pkg.name)) + } const up = path.resolve(cwd, "../") if (up == cwd) break cwd = up @@ -221,10 +226,12 @@ export class CommandParser { } private getBin(name: string) { - for (const dir of this.bins) { + for (const dir of this.binPath) { const bin = path.resolve(dir, name) if (existsSync(bin)) return bin } + // Special syntax for pnp binaries. Handles by spawn.ts + if (this.binsPnp.has(name)) return `yarn:${name}` } private isBin(name: string) { diff --git a/src/pnp.ts b/src/pnp.ts new file mode 100644 index 00000000..bee8e52e --- /dev/null +++ b/src/pnp.ts @@ -0,0 +1,51 @@ +import { readFileSync } from "fs" +import { resolve } from "path" +import v8 from "v8" +import zlib from "zlib" + +type InstallState = { + storedResolutions: Map + storedPackages: Map< + string, + { + name: string + scope?: string + reference: string + locatorHash: string + bin: Map + dependencies?: Map + } + > +} + +export function getBinaries(workspaceRoot: string, packageName: string) { + const binaries = new Set() + + const serializedState = readFileSync( + resolve(workspaceRoot, ".yarn", "install-state.gz") + ) + const installState = v8.deserialize( + zlib.gunzipSync(serializedState) + ) as InstallState + + const hashes = new Set() + + for (const p of installState.storedPackages.values()) { + const pkgName = p.scope ? `@${p.scope}/${p.name}` : p.name + if (packageName == pkgName) { + hashes.add(p.locatorHash) + p.dependencies?.forEach((dep) => { + const h = installState.storedResolutions.get(dep.descriptorHash) + if (h) hashes.add(h) + }) + } + } + + for (const h of hashes) { + const p = installState.storedPackages.get(h) + if (p?.bin.size) { + ;[...p.bin.keys()].forEach((b) => binaries.add(b)) + } + } + return [...binaries] +} diff --git a/src/spawn.ts b/src/spawn.ts index 2baf5449..3dd313a3 100644 --- a/src/spawn.ts +++ b/src/spawn.ts @@ -40,6 +40,13 @@ export class Spawner { FORCE_COLOR: `${chalk.level}`, ...this.env, } + + // Special handling for yarn pnp binaries + if (this.cmd.startsWith("yarn:")) { + this.args.unshift(this.cmd.slice(5)) + this.args.unshift("run") + this.cmd = "yarn" + } const child = spawn(this.cmd, this.args, { env, stdio: raw ? "inherit" : "pipe", diff --git a/tsconfig.json b/tsconfig.json index b0cb4a90..a368dfcc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,8 @@ "compilerOptions": { "declaration": true, "noEmit": true, - "allowJs": true, - "checkJs": true, + "allowJs": false, + "checkJs": false, "importHelpers": true, // Use tslib instead of adding helpers to every emitted js file "module": "CommonJS", // Should be CommonJS for NodeJS apps "strict": true,