diff --git a/.changeset/nervous-baboons-grow.md b/.changeset/nervous-baboons-grow.md new file mode 100644 index 0000000000..19e27c20c6 --- /dev/null +++ b/.changeset/nervous-baboons-grow.md @@ -0,0 +1,5 @@ +--- +"@pnpm/lockfile.settings-checker": major +--- + +Initial Release diff --git a/lockfile/settings-checker/README.md b/lockfile/settings-checker/README.md new file mode 100644 index 0000000000..f2bf87e78d --- /dev/null +++ b/lockfile/settings-checker/README.md @@ -0,0 +1,13 @@ +# @pnpm/lockfile.settings-checker + +> Utilities to check if lockfile settings are out-of-date. + +## Installation + +```sh +pnpm add @pnpm/lockfile.settings-checker +``` + +## License + +MIT diff --git a/lockfile/settings-checker/jest.config.js b/lockfile/settings-checker/jest.config.js new file mode 100644 index 0000000000..9b65513eba --- /dev/null +++ b/lockfile/settings-checker/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../jest.config.js'); + +module.exports = Object.assign({}, config, {}); diff --git a/lockfile/settings-checker/package.json b/lockfile/settings-checker/package.json new file mode 100644 index 0000000000..9d04a4d033 --- /dev/null +++ b/lockfile/settings-checker/package.json @@ -0,0 +1,49 @@ +{ + "name": "@pnpm/lockfile.settings-checker", + "version": "0.0.0", + "description": "Utilities to check if lockfile settings are out-of-date", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib", + "!*.map" + ], + "scripts": { + "lint": "eslint \"src/**/*.ts\"", + "test": "pnpm run compile", + "prepublishOnly": "pnpm run compile", + "compile": "tsc --build && pnpm run lint --fix" + }, + "repository": "https://github.com/pnpm/pnpm/blob/main/lockfile/settings-checker", + "keywords": [ + "pnpm9", + "hash", + "crypto", + "base32" + ], + "engines": { + "node": ">=18.12" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/pnpm/pnpm/issues" + }, + "homepage": "https://github.com/pnpm/pnpm/blob/main/lockfile/settings-checker#readme", + "dependencies": { + "@pnpm/crypto.base32-hash": "workspace:*", + "@pnpm/lockfile.types": "workspace:*", + "@pnpm/parse-overrides": "workspace:*", + "p-map-values": "catalog:", + "ramda": "catalog:", + "sort-keys": "catalog:" + }, + "devDependencies": { + "@pnpm/lockfile.settings-checker": "workspace:*", + "@pnpm/prepare": "workspace:*", + "@types/ramda": "catalog:" + }, + "funding": "https://opencollective.com/pnpm", + "exports": { + ".": "./lib/index.js" + } +} diff --git a/lockfile/settings-checker/src/calcPatchHashes.ts b/lockfile/settings-checker/src/calcPatchHashes.ts new file mode 100644 index 0000000000..0715748293 --- /dev/null +++ b/lockfile/settings-checker/src/calcPatchHashes.ts @@ -0,0 +1,13 @@ +import path from 'path' +import pMapValues from 'p-map-values' +import { createBase32HashFromFile } from '@pnpm/crypto.base32-hash' +import { type PatchFile } from '@pnpm/lockfile.types' + +export async function calcPatchHashes (patches: Record, lockfileDir: string): Promise> { + return pMapValues(async (patchFilePath) => { + return { + hash: await createBase32HashFromFile(patchFilePath), + path: path.relative(lockfileDir, patchFilePath).replaceAll('\\', '/'), + } + }, patches) +} diff --git a/lockfile/settings-checker/src/createOverridesMapFromParsed.ts b/lockfile/settings-checker/src/createOverridesMapFromParsed.ts new file mode 100644 index 0000000000..c5439b9645 --- /dev/null +++ b/lockfile/settings-checker/src/createOverridesMapFromParsed.ts @@ -0,0 +1,10 @@ +import { type VersionOverride } from '@pnpm/parse-overrides' + +export function createOverridesMapFromParsed (parsedOverrides: VersionOverride[] | undefined): Record { + if (!parsedOverrides) return {} + const overridesMap: Record = {} + for (const { selector, newPref } of parsedOverrides) { + overridesMap[selector] = newPref + } + return overridesMap +} diff --git a/lockfile/settings-checker/src/getOutdatedLockfileSetting.ts b/lockfile/settings-checker/src/getOutdatedLockfileSetting.ts new file mode 100644 index 0000000000..ed05cbbe30 --- /dev/null +++ b/lockfile/settings-checker/src/getOutdatedLockfileSetting.ts @@ -0,0 +1,64 @@ +import { type Lockfile, type PatchFile } from '@pnpm/lockfile.types' +import equals from 'ramda/src/equals' + +export type ChangedField = + | 'patchedDependencies' + | 'overrides' + | 'packageExtensionsChecksum' + | 'ignoredOptionalDependencies' + | 'settings.autoInstallPeers' + | 'settings.excludeLinksFromLockfile' + | 'settings.peersSuffixMaxLength' + | 'pnpmfileChecksum' + +export function getOutdatedLockfileSetting ( + lockfile: Lockfile, + { + overrides, + packageExtensionsChecksum, + ignoredOptionalDependencies, + patchedDependencies, + autoInstallPeers, + excludeLinksFromLockfile, + peersSuffixMaxLength, + pnpmfileChecksum, + }: { + overrides?: Record + packageExtensionsChecksum?: string + patchedDependencies?: Record + ignoredOptionalDependencies?: string[] + autoInstallPeers?: boolean + excludeLinksFromLockfile?: boolean + peersSuffixMaxLength?: number + pnpmfileChecksum?: string + } +): ChangedField | null { + if (!equals(lockfile.overrides ?? {}, overrides ?? {})) { + return 'overrides' + } + if (lockfile.packageExtensionsChecksum !== packageExtensionsChecksum) { + return 'packageExtensionsChecksum' + } + if (!equals(lockfile.ignoredOptionalDependencies?.sort() ?? [], ignoredOptionalDependencies?.sort() ?? [])) { + return 'ignoredOptionalDependencies' + } + if (!equals(lockfile.patchedDependencies ?? {}, patchedDependencies ?? {})) { + return 'patchedDependencies' + } + if ((lockfile.settings?.autoInstallPeers != null && lockfile.settings.autoInstallPeers !== autoInstallPeers)) { + return 'settings.autoInstallPeers' + } + if (lockfile.settings?.excludeLinksFromLockfile != null && lockfile.settings.excludeLinksFromLockfile !== excludeLinksFromLockfile) { + return 'settings.excludeLinksFromLockfile' + } + if ( + lockfile.settings?.peersSuffixMaxLength != null && lockfile.settings.peersSuffixMaxLength !== peersSuffixMaxLength || + lockfile.settings?.peersSuffixMaxLength == null && peersSuffixMaxLength !== 1000 + ) { + return 'settings.peersSuffixMaxLength' + } + if (lockfile.pnpmfileChecksum !== pnpmfileChecksum) { + return 'pnpmfileChecksum' + } + return null +} diff --git a/lockfile/settings-checker/src/index.ts b/lockfile/settings-checker/src/index.ts new file mode 100644 index 0000000000..28f0a9e761 --- /dev/null +++ b/lockfile/settings-checker/src/index.ts @@ -0,0 +1,3 @@ +export { calcPatchHashes } from './calcPatchHashes' +export { createOverridesMapFromParsed } from './createOverridesMapFromParsed' +export { type ChangedField, getOutdatedLockfileSetting } from './getOutdatedLockfileSetting' diff --git a/lockfile/settings-checker/tsconfig.json b/lockfile/settings-checker/tsconfig.json new file mode 100644 index 0000000000..2c88a5b13b --- /dev/null +++ b/lockfile/settings-checker/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "@pnpm/tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts", + "../../__typings__/**/*.d.ts" + ], + "references": [ + { + "path": "../../__utils__/prepare" + }, + { + "path": "../../config/parse-overrides" + }, + { + "path": "../../packages/crypto.base32-hash" + }, + { + "path": "../types" + } + ] +} diff --git a/lockfile/settings-checker/tsconfig.lint.json b/lockfile/settings-checker/tsconfig.lint.json new file mode 100644 index 0000000000..1bbe711971 --- /dev/null +++ b/lockfile/settings-checker/tsconfig.lint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "../../__typings__/**/*.d.ts" + ] +} diff --git a/pkg-manager/core/package.json b/pkg-manager/core/package.json index 3ebbbee41f..f4e7ea72ca 100644 --- a/pkg-manager/core/package.json +++ b/pkg-manager/core/package.json @@ -39,6 +39,7 @@ "@pnpm/lockfile.fs": "workspace:*", "@pnpm/lockfile.preferred-versions": "workspace:*", "@pnpm/lockfile.pruner": "workspace:*", + "@pnpm/lockfile.settings-checker": "workspace:*", "@pnpm/lockfile.utils": "workspace:*", "@pnpm/lockfile.verification": "workspace:*", "@pnpm/lockfile.walker": "workspace:*", @@ -68,7 +69,6 @@ "normalize-path": "catalog:", "p-filter": "catalog:", "p-limit": "catalog:", - "p-map-values": "catalog:", "path-exists": "catalog:", "ramda": "catalog:", "run-groups": "catalog:", diff --git a/pkg-manager/core/src/install/index.ts b/pkg-manager/core/src/install/index.ts index 784e4c4f7b..a290415ef4 100644 --- a/pkg-manager/core/src/install/index.ts +++ b/pkg-manager/core/src/install/index.ts @@ -14,7 +14,11 @@ import { stageLogger, summaryLogger, } from '@pnpm/core-loggers' -import { createBase32HashFromFile } from '@pnpm/crypto.base32-hash' +import { + calcPatchHashes, + createOverridesMapFromParsed, + getOutdatedLockfileSetting, +} from '@pnpm/lockfile.settings-checker' import { PnpmError } from '@pnpm/error' import { getContext, type PnpmContext } from '@pnpm/get-context' import { headlessInstall, type InstallationResultStats } from '@pnpm/headless' @@ -71,10 +75,8 @@ import isInnerLink from 'is-inner-link' import isSubdir from 'is-subdir' import pFilter from 'p-filter' import pLimit from 'p-limit' -import pMapValues from 'p-map-values' import mapValues from 'ramda/src/map' import clone from 'ramda/src/clone' -import equals from 'ramda/src/equals' import isEmpty from 'ramda/src/isEmpty' import pipeWith from 'ramda/src/pipeWith' import props from 'ramda/src/props' @@ -342,7 +344,7 @@ export async function mutateModules ( const frozenLockfile = opts.frozenLockfile || opts.frozenLockfileIfExists && ctx.existsNonEmptyWantedLockfile let outdatedLockfileSettings = false - const overridesMap = Object.fromEntries((opts.parsedOverrides ?? []).map(({ selector, newPref }) => [selector, newPref])) + const overridesMap = createOverridesMapFromParsed(opts.parsedOverrides) if (!opts.ignorePackageManifest) { const outdatedLockfileSettingName = getOutdatedLockfileSetting(ctx.wantedLockfile, { autoInstallPeers: opts.autoInstallPeers, @@ -742,82 +744,6 @@ Note that in CI environments, this setting is enabled by default.`, } } -interface PatchHash { - hash: string - path: string -} - -async function calcPatchHashes (patches: Record, lockfileDir: string): Promise> { - return pMapValues(async (patchFilePath) => { - return { - hash: await createBase32HashFromFile(patchFilePath), - path: path.relative(lockfileDir, patchFilePath).replaceAll('\\', '/'), - } - }, patches) -} - -type ChangedField = - | 'patchedDependencies' - | 'overrides' - | 'packageExtensionsChecksum' - | 'ignoredOptionalDependencies' - | 'settings.autoInstallPeers' - | 'settings.excludeLinksFromLockfile' - | 'settings.peersSuffixMaxLength' - | 'pnpmfileChecksum' - -function getOutdatedLockfileSetting ( - lockfile: Lockfile, - { - overrides, - packageExtensionsChecksum, - ignoredOptionalDependencies, - patchedDependencies, - autoInstallPeers, - excludeLinksFromLockfile, - peersSuffixMaxLength, - pnpmfileChecksum, - }: { - overrides?: Record - packageExtensionsChecksum?: string - patchedDependencies?: Record - ignoredOptionalDependencies?: string[] - autoInstallPeers?: boolean - excludeLinksFromLockfile?: boolean - peersSuffixMaxLength?: number - pnpmfileChecksum?: string - } -): ChangedField | null { - if (!equals(lockfile.overrides ?? {}, overrides ?? {})) { - return 'overrides' - } - if (lockfile.packageExtensionsChecksum !== packageExtensionsChecksum) { - return 'packageExtensionsChecksum' - } - if (!equals(lockfile.ignoredOptionalDependencies?.sort() ?? [], ignoredOptionalDependencies?.sort() ?? [])) { - return 'ignoredOptionalDependencies' - } - if (!equals(lockfile.patchedDependencies ?? {}, patchedDependencies ?? {})) { - return 'patchedDependencies' - } - if ((lockfile.settings?.autoInstallPeers != null && lockfile.settings.autoInstallPeers !== autoInstallPeers)) { - return 'settings.autoInstallPeers' - } - if (lockfile.settings?.excludeLinksFromLockfile != null && lockfile.settings.excludeLinksFromLockfile !== excludeLinksFromLockfile) { - return 'settings.excludeLinksFromLockfile' - } - if ( - lockfile.settings?.peersSuffixMaxLength != null && lockfile.settings.peersSuffixMaxLength !== peersSuffixMaxLength || - lockfile.settings?.peersSuffixMaxLength == null && peersSuffixMaxLength !== 1000 - ) { - return 'settings.peersSuffixMaxLength' - } - if (lockfile.pnpmfileChecksum !== pnpmfileChecksum) { - return 'pnpmfileChecksum' - } - return null -} - export function createObjectChecksum (obj: Record): string { const s = JSON.stringify(sortKeys(obj, { deep: true })) return crypto.createHash('md5').update(s).digest('hex') diff --git a/pkg-manager/core/tsconfig.json b/pkg-manager/core/tsconfig.json index 2a7c884121..bcc0a5c9ec 100644 --- a/pkg-manager/core/tsconfig.json +++ b/pkg-manager/core/tsconfig.json @@ -78,6 +78,9 @@ { "path": "../../lockfile/pruner" }, + { + "path": "../../lockfile/settings-checker" + }, { "path": "../../lockfile/types" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81de079e06..0ddef7ed80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3240,6 +3240,37 @@ importers: specifier: 'catalog:' version: 1.1.0 + lockfile/settings-checker: + dependencies: + '@pnpm/crypto.base32-hash': + specifier: workspace:* + version: link:../../packages/crypto.base32-hash + '@pnpm/lockfile.types': + specifier: workspace:* + version: link:../types + '@pnpm/parse-overrides': + specifier: workspace:* + version: link:../../config/parse-overrides + p-map-values: + specifier: 'catalog:' + version: 1.0.0 + ramda: + specifier: 'catalog:' + version: '@pnpm/ramda@0.28.1' + sort-keys: + specifier: 'catalog:' + version: 4.2.0 + devDependencies: + '@pnpm/lockfile.settings-checker': + specifier: workspace:* + version: 'link:' + '@pnpm/prepare': + specifier: workspace:* + version: link:../../__utils__/prepare + '@types/ramda': + specifier: 'catalog:' + version: 0.29.12 + lockfile/types: dependencies: '@pnpm/patching.types': @@ -4076,6 +4107,9 @@ importers: '@pnpm/lockfile.pruner': specifier: workspace:* version: link:../../lockfile/pruner + '@pnpm/lockfile.settings-checker': + specifier: workspace:* + version: link:../../lockfile/settings-checker '@pnpm/lockfile.utils': specifier: workspace:* version: link:../../lockfile/utils @@ -4166,9 +4200,6 @@ importers: p-limit: specifier: 'catalog:' version: 3.1.0 - p-map-values: - specifier: 'catalog:' - version: 1.0.0 path-exists: specifier: 'catalog:' version: 4.0.0