diff --git a/common/changes/@microsoft/rush/bugfix-yaml_dump_error_2024-12-23-08-19.json b/common/changes/@microsoft/rush/bugfix-yaml_dump_error_2024-12-23-08-19.json new file mode 100644 index 00000000000..18f2c148b4d --- /dev/null +++ b/common/changes/@microsoft/rush/bugfix-yaml_dump_error_2024-12-23-08-19.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Fix an issue where the lockfile object has a nullish value causing yaml.dump to report an error.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkWrapFileConverters.ts b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkWrapFileConverters.ts index 236afa90610..ca57585da1d 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkWrapFileConverters.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkWrapFileConverters.ts @@ -24,6 +24,7 @@ import type { ProjectSnapshot, ResolvedDependencies } from '@pnpm/lockfile.types'; +import { removeNullishProps } from '../../utilities/objectUtilities'; type DepPath = string & { __brand: 'DepPath' }; // eslint-disable-next-line @typescript-eslint/typedef @@ -56,11 +57,13 @@ function revertProjectSnapshot(from: InlineSpecifiersProjectSnapshot): ProjectSn from.optionalDependencies == null ? from.optionalDependencies : moveSpecifiers(from.optionalDependencies); return { - ...from, - specifiers, - dependencies, - devDependencies, - optionalDependencies + ...removeNullishProps({ + ...from, + dependencies, + devDependencies, + optionalDependencies + }), + specifiers }; } diff --git a/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapConverters.test.ts b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapConverters.test.ts index 7234f7721fc..673bf4826ff 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapConverters.test.ts +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapConverters.test.ts @@ -38,4 +38,15 @@ describe(convertLockfileV9ToLockfileObject.name, () => { 'pad-left': '^2.1.0' }); }); + + it("no nullish values", () => { + const importers = new Map(Object.entries(lockfile.importers || {})); + + const currentPackage = importers.get('.'); + const props = Object.keys(currentPackage || {}); + expect(props).toContain('dependencies'); + expect(props).toContain('specifiers'); + expect(props).not.toContain('optionalDependencies'); + expect(props).not.toContain('devDependencies'); + }); }); diff --git a/libraries/rush-lib/src/utilities/objectUtilities.ts b/libraries/rush-lib/src/utilities/objectUtilities.ts index 092ba9ddc18..6224b94dc30 100644 --- a/libraries/rush-lib/src/utilities/objectUtilities.ts +++ b/libraries/rush-lib/src/utilities/objectUtilities.ts @@ -153,3 +153,21 @@ function isStrictComparable(value: T): boolean { value === value && !(value !== null && value !== undefined && (type === 'object' || type === 'function')) ); } + +/** + * Removes `undefined` and `null` direct properties from an object. + * + * @remarks + * Note that this does not recurse through sub-objects. + */ +export function removeNullishProps(obj: T): Partial { + const result: Partial = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + if (obj[key] !== undefined && obj[key] !== null) { + result[key] = obj[key]; + } + } + } + return result; +} diff --git a/libraries/rush-lib/src/utilities/test/objectUtilities.test.ts b/libraries/rush-lib/src/utilities/test/objectUtilities.test.ts index 6b8fef7ad51..9235fd63ab8 100644 --- a/libraries/rush-lib/src/utilities/test/objectUtilities.test.ts +++ b/libraries/rush-lib/src/utilities/test/objectUtilities.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { objectsAreDeepEqual, cloneDeep, merge } from '../objectUtilities'; +import { objectsAreDeepEqual, cloneDeep, merge, removeNullishProps } from '../objectUtilities'; describe('objectUtilities', () => { describe(objectsAreDeepEqual.name, () => { @@ -147,4 +147,12 @@ describe('objectUtilities', () => { expect(merge({ a: { b: 1 } }, { a: { c: 2 } })).toEqual({ a: { b: 1, c: 2 } }); }); }); + + describe(removeNullishProps.name, () => { + it('can remove undefined and null properties', () => { + expect(removeNullishProps({ a: 1, b: undefined })).toEqual({ a: 1 }); + expect(removeNullishProps({ a: 1, b: null })).toEqual({ a: 1 }); + expect(removeNullishProps({ a: 1, b: undefined, c: null })).toEqual({ a: 1 }); + }); + }); });