Skip to content

Instantly share code, notes, and snippets.

@cau777
Last active February 6, 2024 00:31
Show Gist options
  • Save cau777/b08ca0d164e6e5df8ecdbab1187fe45d to your computer and use it in GitHub Desktop.
Save cau777/b08ca0d164e6e5df8ecdbab1187fe45d to your computer and use it in GitHub Desktop.
Simple TS script that reads patches from `pnpm audit` and manually applies them to package.json overrides, so that `pnpm` recognizes them properly. Meant to be used with `ts-node`
import { readFileSync, writeFileSync } from 'fs'
import semver from 'semver'
import { exec } from 'child_process'
// Currently, pnpm (version 8.5.1) is not recognizing overrides properly on install
// This is a temporary solution that simplifies the overrides generated by pnpm audit -P --json
// It basically gets rid of the vulnerable version specifier
interface SimplifiedPackageConfig {
pnpm?: {
overrides?: Record<string, string>
}
}
interface SimplifiedAudit {
advisories: Record<
string,
{
module_name: string
patched_versions: string
}
>
}
const extractModuleName = (selector: string) => {
// Taking into account packages that start with @ like @babel/core
const separation = selector.lastIndexOf('@')
if (separation === 0 || separation === -1) return selector
return selector.substring(0, separation)
}
const chooseGreaterVersion = (oldVersion: string | undefined, newVersion: string) => {
if (!oldVersion) return newVersion
// If a range is passed to minVersion, it returns the minimum version that satisfies it
// Otherwise, the version itself is returned
const oldMin = semver.minVersion(oldVersion)
const newMin = semver.minVersion(newVersion)
// This shouldn't happen since pnpm is generating those versions
if (!oldMin) throw new Error(`Could not parse version ${oldMin}`)
if (!newMin) throw new Error(`Could not parse version ${newMin}`)
return semver.gt(newMin, oldMin) ? newVersion : oldVersion
}
const main = async () => {
const configJson = readFileSync('./package.json').toString()
const config = JSON.parse(configJson) as SimplifiedPackageConfig
const overrides: Record<string, string> = config.pnpm?.overrides ?? {}
const countBefore = Object.entries(overrides).length
const auditJson = await new Promise<string>((res, rej) => {
exec('pnpm audit --json -P', (error, output) => {
// Default behavior when there are vulnerabilities
if (!error || error.code === 1) res(output)
else rej(error)
})
})
const audit = JSON.parse(auditJson) as SimplifiedAudit
const entries = Object.values(audit.advisories)
console.log(`Processing ${entries.length} overrides`)
// Only process new overrides to avoid changing dependencies that are working
entries.forEach(({ module_name: moduleName, patched_versions: patchedVersions }) => {
// This means that there are no patch available
if (patchedVersions === '<0.0.0') return
const name = extractModuleName(moduleName)
const oldVersion = overrides[name]
overrides[name] = chooseGreaterVersion(oldVersion, patchedVersions)
})
config.pnpm ??= {}
config.pnpm.overrides = overrides
// Add newline so prettier does not complain
writeFileSync('./package.json', `${JSON.stringify(config, undefined, 2)}\n`)
console.log(
`Successfully added ${
Object.entries(overrides).length - countBefore
} overrides. Run pnpm install to apply the changes`
)
}
if (require.main === module) {
main().then(() => {
process.exit(0);
}, (err) => {
console.error(err)
process.exit(1)
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment