Skip to content

Commit

Permalink
feat: ✨ added support for Yarn v2 with PnP (fixes #137)
Browse files Browse the repository at this point in the history
  • Loading branch information
folke committed Nov 25, 2020
1 parent 7edfc96 commit 27b51b0
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 8 deletions.
3 changes: 0 additions & 3 deletions commitlint.config.js

This file was deleted.

13 changes: 10 additions & 3 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -102,12 +103,16 @@ export class CommandParser {
[["pnpm", "run"], [CommandType.script]],
]

bins: string[] = []
binPath: string[] = []
binsPnp = new Set<string>()

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
Expand Down Expand 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) {
Expand Down
51 changes: 51 additions & 0 deletions src/pnp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { readFileSync } from "fs"
import { resolve } from "path"
import v8 from "v8"
import zlib from "zlib"

type InstallState = {
storedResolutions: Map<string, string>
storedPackages: Map<
string,
{
name: string
scope?: string
reference: string
locatorHash: string
bin: Map<string, string>
dependencies?: Map<string, { descriptorHash: string }>
}
>
}

export function getBinaries(workspaceRoot: string, packageName: string) {
const binaries = new Set<string>()

const serializedState = readFileSync(
resolve(workspaceRoot, ".yarn", "install-state.gz")
)
const installState = v8.deserialize(
zlib.gunzipSync(serializedState)
) as InstallState

const hashes = new Set<string>()

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]
}
7 changes: 7 additions & 0 deletions src/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 27b51b0

Please sign in to comment.