fix(lockfile): better lockfile stringify error

This commit is contained in:
Zoltan Kochan
2020-10-31 19:03:19 +02:00
parent 41e52b661b
commit aa6bc4f952
3 changed files with 90 additions and 3 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/lockfile-file": patch
---
Print a better when stringifying a lockfile object fails.

View File

@@ -1,3 +1,4 @@
import PnpmError from '@pnpm/error'
import logger from './logger'
import { DEPENDENCIES_FIELDS } from '@pnpm/types'
import { Lockfile, ProjectSnapshot } from '@pnpm/lockfile-types'
@@ -56,11 +57,48 @@ function writeLockfile (
return rimraf(lockfilePath)
}
const yamlDoc = yaml.safeDump(normalizeLockfile(wantedLockfile, opts?.forceSharedFormat === true), LOCKFILE_YAML_FORMAT)
const yamlDoc = yamlStringify(wantedLockfile, opts?.forceSharedFormat === true)
return writeFileAtomic(lockfilePath, yamlDoc)
}
function yamlStringify (lockfile: Lockfile, forceSharedFormat: boolean) {
try {
return yaml.safeDump(
normalizeLockfile(lockfile, forceSharedFormat),
LOCKFILE_YAML_FORMAT
)
} catch (err) {
if (err.message.includes('[object Undefined]')) {
const brokenValuePath = findBrokenRecord(lockfile)
if (brokenValuePath) {
throw new PnpmError('LOCKFILE_STRINGIFY', `Failed to stringify the lockfile object. Undefined value at: ${brokenValuePath}`)
}
}
throw err
}
}
function findBrokenRecord (obj: Object): string | null {
for (let [key, value] of Object.entries(obj)) {
if (key === '.') key = '[.]'
switch (typeof value) {
case 'undefined': {
return key
}
case 'object': {
const brokenKey = findBrokenRecord(value)
if (!brokenKey) break
if (brokenKey.startsWith('[')) {
return `${key}${brokenKey}`
}
return `${key}.${brokenKey}`
}
}
}
return null
}
function isEmptyLockfile (lockfile: Lockfile) {
return R.values(lockfile.importers).every((importer) => R.isEmpty(importer.specifiers ?? {}) && R.isEmpty(importer.dependencies ?? {}))
}
@@ -128,7 +166,7 @@ export default function writeLockfiles (
}
const forceSharedFormat = opts?.forceSharedFormat === true
const yamlDoc = yaml.safeDump(normalizeLockfile(opts.wantedLockfile, forceSharedFormat), LOCKFILE_YAML_FORMAT)
const yamlDoc = yamlStringify(opts.wantedLockfile, forceSharedFormat)
// in most cases the `pnpm-lock.yaml` and `node_modules/.pnpm-lock.yaml` are equal
// in those cases the YAML document can be stringified only once for both files
@@ -148,7 +186,7 @@ export default function writeLockfiles (
prefix: opts.wantedLockfileDir,
})
const currentYamlDoc = yaml.safeDump(normalizeLockfile(opts.currentLockfile, forceSharedFormat), LOCKFILE_YAML_FORMAT)
const currentYamlDoc = yamlStringify(opts.currentLockfile, forceSharedFormat)
return Promise.all([
writeFileAtomic(wantedLockfilePath, yamlDoc),

View File

@@ -1,4 +1,5 @@
import { LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants'
import PnpmError from '@pnpm/error'
import {
readCurrentLockfile,
readWantedLockfile,
@@ -138,3 +139,46 @@ test('write does not use yaml anchors/aliases', async () => {
expect(lockfileContent).not.toMatch('&')
expect(lockfileContent).not.toMatch('*')
})
test('writeLockfiles() fails with meaningful error, when an invalid lockfile object is passed in', async () => {
const projectPath = tempy.directory()
const wantedLockfile = {
importers: {
'.': {
dependencies: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
// eslint-disable-next-line
specifiers: undefined as any,
},
},
lockfileVersion: LOCKFILE_VERSION,
packages: {
'/is-negative/1.0.0': {
dependencies: {
'is-positive': '2.0.0',
},
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
'/is-positive/1.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
'/is-positive/2.0.0': {
resolution: {
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
},
},
},
}
expect(() => writeLockfiles({
currentLockfile: wantedLockfile,
currentLockfileDir: projectPath,
wantedLockfile,
wantedLockfileDir: projectPath,
})).toThrow(new PnpmError('LOCKFILE_STRINGIFY', 'Failed to stringify the lockfile object. Undefined value at: importers[.].specifiers'))
})