Skip to content

Commit 6f85753

Browse files
authored
fix #1534 - Error with relative dependency path and --peer and yarn v4 (#1537)
1 parent 5054297 commit 6f85753

File tree

11 files changed

+112
-17
lines changed

11 files changed

+112
-17
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
strategy:
2727
fail-fast: false
2828
matrix:
29-
node: [18, 20]
29+
node: [18, 20, 22]
3030
os: [ubuntu-latest, windows-latest]
3131

3232
steps:
@@ -39,6 +39,9 @@ jobs:
3939
node-version: ${{ matrix.node }}
4040
cache: npm
4141

42+
- name: Enable corepack
43+
run: corepack enable
44+
4245
- name: Install npm dependencies
4346
run: npm ci
4447

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"prepare": "src/scripts/install-hooks",
3434
"prepublishOnly": "npm run build",
3535
"prettier": "prettier . --check",
36+
"prettier:fix": "prettier . --write",
3637
"test": "npm run test:unit && npm run test:e2e",
3738
"test:bun": "test/bun-install.sh && mocha test/bun",
3839
"test:unit": "mocha test test/package-managers/*",

src/lib/getPeerDependenciesFromRegistry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async function getPeerDependenciesFromRegistry(packageMap: Index<Version>, optio
6060
}
6161

6262
return Object.entries(packageMap).reduce(async (accumPromise, [pkg, version]) => {
63-
const dep = await packageManager.getPeerDependencies!(pkg, version)
63+
const dep = await packageManager.getPeerDependencies!(pkg, version, { cwd: options.cwd })
6464
if (bar) {
6565
bar.tick()
6666
}

src/package-managers/npm.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { NpmConfig } from '../types/NpmConfig'
2222
import { NpmOptions } from '../types/NpmOptions'
2323
import { Options } from '../types/Options'
2424
import { Packument } from '../types/Packument'
25+
import { SpawnOptions } from '../types/SpawnOptions'
2526
import { SpawnPleaseOptions } from '../types/SpawnPleaseOptions'
2627
import { Version } from '../types/Version'
2728
import { VersionResult } from '../types/VersionResult'
@@ -678,11 +679,16 @@ export const greatest: GetVersion = async (
678679
*
679680
* @param packageName
680681
* @param version
682+
* @param spawnOptions
681683
* @returns Promised {packageName: version} collection
682684
*/
683-
export const getPeerDependencies = async (packageName: string, version: Version): Promise<Index<Version>> => {
685+
export const getPeerDependencies = async (
686+
packageName: string,
687+
version: Version,
688+
spawnOptions: SpawnOptions,
689+
): Promise<Index<Version>> => {
684690
const args = ['view', `${packageName}@${version}`, 'peerDependencies']
685-
const result = await spawnNpm(args, {}, { rejectOnError: false })
691+
const result = await spawnNpm(args, {}, { rejectOnError: false }, spawnOptions)
686692
return result ? parseJson(result, { command: [...args, '--json'].join(' ') }) : {}
687693
}
688694

src/package-managers/yarn.ts

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,30 @@ function parseJsonLines(result: string): Promise<{ dependencies: Index<ParsedDep
181181
})
182182
}
183183

184+
/**
185+
* Extract first json line from muli line yarn output
186+
*
187+
* @param result Output from yarn command to be parsed
188+
*/
189+
function extractFirstJsonLine(result: string): Promise<string> {
190+
return new Promise((resolve, reject) => {
191+
const parser = jsonlines.parse()
192+
let firstFound = false
193+
194+
parser.on('data', value => {
195+
if (!firstFound) {
196+
firstFound = true
197+
resolve(JSON.stringify(value))
198+
}
199+
})
200+
parser.on('error', reject)
201+
202+
parser.write(result)
203+
204+
parser.end()
205+
})
206+
}
207+
184208
const cmd = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'
185209

186210
/**
@@ -297,20 +321,49 @@ export const semver = withNpmConfigFromYarn(npm.semver)
297321
*
298322
* @param packageName
299323
* @param version
324+
* @param spawnOptions
300325
* @returns Promised {packageName: version} collection
301326
*/
302-
export const getPeerDependencies = async (packageName: string, version: Version): Promise<Index<Version>> => {
303-
const { stdout: yarnVersion } = await spawn(cmd, ['--version'], { rejectOnError: false }, {})
327+
export const getPeerDependencies = async (
328+
packageName: string,
329+
version: Version,
330+
spawnOptions: SpawnOptions,
331+
): Promise<Index<Version>> => {
332+
const { stdout: yarnVersion } = await spawn(cmd, ['--version'], { rejectOnError: false }, spawnOptions)
304333
if (yarnVersion.startsWith('1')) {
305334
const args = ['--json', 'info', `${packageName}@${version}`, 'peerDependencies']
306-
const { stdout } = await spawn(cmd, args, { rejectOnError: false }, {})
335+
const { stdout } = await spawn(cmd, args, { rejectOnError: false }, spawnOptions)
307336
return stdout ? npm.parseJson<{ data?: Index<Version> }>(stdout, { command: args.join(' ') }).data || {} : {}
308337
} else {
309338
const args = ['--json', 'npm', 'info', `${packageName}@${version}`, '--fields', 'peerDependencies']
310-
const { stdout } = await spawn(cmd, args, { rejectOnError: false }, {})
311-
return stdout
312-
? npm.parseJson<{ peerDependencies?: Index<Version> }>(stdout, { command: args.join(' ') }).peerDependencies || {}
313-
: {}
339+
const { stdout } = await spawn(cmd, args, { rejectOnError: false }, spawnOptions)
340+
if (!stdout) {
341+
return {}
342+
}
343+
try {
344+
return (
345+
npm.parseJson<{ peerDependencies?: Index<Version> }>(stdout, { command: args.join(' ') }).peerDependencies || {}
346+
)
347+
} catch (parseError) {
348+
/*
349+
If package does not exist, yarn returns multiple json errors. As such, we want to extract just the first one, instead of crashing.
350+
Example response:
351+
{"type":"error","name":35,"displayName":"YN0035","indent":"","data":"Package not found"}
352+
{"type":"error","name":35,"displayName":"YN0035","indent":"","data":" \u001b[96mResponse Code\u001b[39m: \u001b]8;;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404\u0007\u001b[93m404\u001b[39m (Not Found)\u001b]8;;\u0007"}
353+
{"type":"error","name":35,"displayName":"YN0035","indent":"","data":" \u001b[96mRequest Method\u001b[39m: GET"}
354+
{"type":"error","name":35,"displayName":"YN0035","indent":"","data":" \u001b[96mRequest URL\u001b[39m: \u001b[95mhttps://registry.yarnpkg.com/fffffffffffff\u001b[39m"}
355+
*/
356+
try {
357+
const firstObj = await extractFirstJsonLine(stdout)
358+
if (firstObj) {
359+
return (
360+
npm.parseJson<{ peerDependencies?: Index<Version> }>(firstObj, { command: args.join(' ') })
361+
.peerDependencies || {}
362+
)
363+
}
364+
} catch {}
365+
throw parseError
366+
}
314367
}
315368
}
316369

src/types/PackageManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { GetVersion } from './GetVersion'
22
import { Index } from './IndexType'
33
import { NpmConfig } from './NpmConfig'
44
import { Options } from './Options'
5+
import { SpawnOptions } from './SpawnOptions'
56
import { Version } from './Version'
67
import { VersionSpec } from './VersionSpec'
78

@@ -21,7 +22,7 @@ export interface PackageManager {
2122
to: VersionSpec,
2223
options?: Options,
2324
) => Promise<boolean>
24-
getPeerDependencies?: (packageName: string, version: Version) => Promise<Index<Version>>
25+
getPeerDependencies?: (packageName: string, version: Version, spawnOptions: SpawnOptions) => Promise<Index<Version>>
2526
getEngines?: (
2627
packageName: string,
2728
version: Version,

test/package-managers/npm/index.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ describe('npm', function () {
2626
})
2727

2828
it('getPeerDependencies', async () => {
29-
await npm.getPeerDependencies('ncu-test-return-version', '1.0.0').should.eventually.deep.equal({})
30-
await npm.getPeerDependencies('ncu-test-peer', '1.0.0').should.eventually.deep.equal({
29+
const spawnOptions = { cwd: __dirname }
30+
await npm.getPeerDependencies('ncu-test-return-version', '1.0.0', spawnOptions).should.eventually.deep.equal({})
31+
await npm.getPeerDependencies('ncu-test-peer', '1.0.0', spawnOptions).should.eventually.deep.equal({
3132
'ncu-test-return-version': '1.x',
3233
})
3334
})

test/package-managers/yarn/default/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"license": "MIT",
3+
"packageManager": "yarn@1.22.22",
34
"dependencies": {
45
"chalk": "^3.0.0"
56
}

test/package-managers/yarn/index.test.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ const localYarnSpawnOptions = {
1616
},
1717
}
1818

19+
const filteredPath = (process.env.PATH || '')
20+
.split(path.delimiter)
21+
.filter(p => !p.includes(path.join('node_modules', '.bin'))) // Avoid running yarn form the node module bin
22+
.join(path.delimiter)
23+
const cleanEnv = {
24+
...process.env,
25+
PATH: filteredPath,
26+
}
27+
1928
describe('yarn', function () {
2029
it('list', async () => {
2130
const testDir = path.join(__dirname, 'default')
@@ -46,11 +55,24 @@ describe('yarn', function () {
4655
await yarn.list({ cwd: testDir }, localYarnSpawnOptions).should.eventually.be.rejectedWith(lockFileErrorMessage)
4756
})
4857

49-
it('getPeerDependencies', async () => {
50-
await yarn.getPeerDependencies('ncu-test-return-version', '1.0.0').should.eventually.deep.equal({})
51-
await yarn.getPeerDependencies('ncu-test-peer', '1.0.0').should.eventually.deep.equal({
58+
it('getPeerDependencies v1', async () => {
59+
const testDir = path.join(__dirname, 'default')
60+
const spawnOptions = { cwd: testDir, env: cleanEnv }
61+
await yarn.getPeerDependencies('ncu-test-return-version', '1.0.0', spawnOptions).should.eventually.deep.equal({})
62+
await yarn.getPeerDependencies('ncu-test-peer', '1.0.0', spawnOptions).should.eventually.deep.equal({
63+
'ncu-test-return-version': '1.x',
64+
})
65+
await yarn.getPeerDependencies('fffffffffffff', '1.0.0', spawnOptions).should.eventually.deep.equal({})
66+
})
67+
68+
it('getPeerDependencies v4', async () => {
69+
const testDir = path.join(__dirname, 'v4')
70+
const spawnOptions = { cwd: testDir, env: cleanEnv }
71+
await yarn.getPeerDependencies('ncu-test-return-version', '1.0.0', spawnOptions).should.eventually.deep.equal({})
72+
await yarn.getPeerDependencies('ncu-test-peer', '1.0.0', spawnOptions).should.eventually.deep.equal({
5273
'ncu-test-return-version': '1.x',
5374
})
75+
await yarn.getPeerDependencies('fffffffffffff', '1.0.0', spawnOptions).should.eventually.deep.equal({})
5476
})
5577

5678
describe('npmAuthTokenKeyValue', () => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"license": "MIT",
3+
"packageManager": "yarn@4.9.2",
4+
"dependencies": {
5+
"chalk": "^3.0.0"
6+
}
7+
}

0 commit comments

Comments
 (0)