diff --git a/.changeset/fix-overrides-value-validation.md b/.changeset/fix-overrides-value-validation.md new file mode 100644 index 0000000000..2d44a7e8c0 --- /dev/null +++ b/.changeset/fix-overrides-value-validation.md @@ -0,0 +1,6 @@ +--- +"@pnpm/config.reader": patch +"pnpm": patch +--- + +Throw a pnpm error when `overrides` has an invalid shape or contains a non-string value. diff --git a/config/reader/src/getOptionsFromRootManifest.ts b/config/reader/src/getOptionsFromRootManifest.ts index efdef766ab..ff3a45cbbf 100644 --- a/config/reader/src/getOptionsFromRootManifest.ts +++ b/config/reader/src/getOptionsFromRootManifest.ts @@ -28,6 +28,7 @@ export type OptionsFromRootManifest = { export function getOptionsFromPnpmSettings (manifestDir: string | undefined, pnpmSettings: PnpmSettings, manifest?: ProjectManifest): OptionsFromRootManifest { const settings: OptionsFromRootManifest = replaceEnvInSettings(pnpmSettings) if (settings.overrides) { + assertValidOverrides(settings.overrides) if (Object.keys(settings.overrides).length === 0) { delete settings.overrides } else if (manifest) { @@ -45,6 +46,23 @@ export function getOptionsFromPnpmSettings (manifestDir: string | undefined, pnp return settings } +function assertValidOverrides (overrides: unknown): asserts overrides is Record { + if (overrides == null || typeof overrides !== 'object' || Array.isArray(overrides)) { + throw new PnpmError('INVALID_OVERRIDES', `The overrides field should be an object, but got ${renderReceivedType(overrides)}`) + } + for (const [selector, spec] of Object.entries(overrides)) { + if (typeof spec !== 'string') { + throw new PnpmError('INVALID_OVERRIDES', `The value of overrides.${selector} should be a string, but got ${renderReceivedType(spec)}`) + } + } +} + +function renderReceivedType (value: unknown): string { + if (value === null) return 'null' + if (Array.isArray(value)) return 'array' + return typeof value +} + function replaceEnvInSettings (settings: PnpmSettings): PnpmSettings { const newSettings: PnpmSettings = {} for (const [key, value] of Object.entries(settings)) { diff --git a/config/reader/test/getOptionsFromRootManifest.test.ts b/config/reader/test/getOptionsFromRootManifest.test.ts index 43f63ff6ea..10cf161662 100644 --- a/config/reader/test/getOptionsFromRootManifest.test.ts +++ b/config/reader/test/getOptionsFromRootManifest.test.ts @@ -33,3 +33,34 @@ test('getOptionsFromPnpmSettings() converts allowBuilds', () => { }, }) }) + +test('getOptionsFromPnpmSettings() rejects non-string overrides values', () => { + expect(() => getOptionsFromPnpmSettings(process.cwd(), { + overrides: { + foo: null, + } as unknown as Record, + })).toThrow(expect.objectContaining({ + code: 'ERR_PNPM_INVALID_OVERRIDES', + message: 'The value of overrides.foo should be a string, but got null', + })) +}) + +test('getOptionsFromPnpmSettings() rejects array overrides values', () => { + expect(() => getOptionsFromPnpmSettings(process.cwd(), { + overrides: { + foo: [], + } as unknown as Record, + })).toThrow(expect.objectContaining({ + code: 'ERR_PNPM_INVALID_OVERRIDES', + message: 'The value of overrides.foo should be a string, but got array', + })) +}) + +test('getOptionsFromPnpmSettings() rejects non-object overrides values', () => { + expect(() => getOptionsFromPnpmSettings(process.cwd(), { + overrides: [] as unknown as Record, + })).toThrow(expect.objectContaining({ + code: 'ERR_PNPM_INVALID_OVERRIDES', + message: 'The overrides field should be an object, but got array', + })) +})