mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
fix(lockfile): better lockfile stringify error
This commit is contained in:
5
.changeset/odd-ears-judge.md
Normal file
5
.changeset/odd-ears-judge.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/lockfile-file": patch
|
||||
---
|
||||
|
||||
Print a better when stringifying a lockfile object fails.
|
||||
@@ -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),
|
||||
|
||||
@@ -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'))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user