Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support lockfile v6.1 in pnpm v7 #6611

Merged
merged 6 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/fast-news-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@pnpm/lockfile-types": minor
"@pnpm/lockfile-file": minor
"@pnpm/core": minor
"pnpm": minor
---

Some settings influence the structure of the lockfile, so we cannot reuse the lockfile if those settings change. As a result, we need to store such settings in the lockfile. This way we will know with which settings the lockfile has been created.

A new field will now be present in the lockfile: `settings`. It will store the values of two settings: `autoInstallPeers` and `excludeLinksFromLockfile`. If someone tries to perform a `frozen-lockfile` installation and their active settings don't match the ones in the lockfile, then an error message will be thrown.

The lockfile format version is bumped from v6.0 to v6.1.

Related PR: [#6557](https://github.com/pnpm/pnpm/pull/6557)
Related issue: [#6312](https://github.com/pnpm/pnpm/issues/6312)
5 changes: 5 additions & 0 deletions .changeset/itchy-moons-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pnpm/lockfile-file": patch
---

Convertion should work for all lockfile v6 formats, not just 6.0.
5 changes: 5 additions & 0 deletions .changeset/sharp-trees-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pnpm/constants": minor
---

Bump lockfile v6 version to v6.1.
5 changes: 5 additions & 0 deletions .changeset/short-coats-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pnpm/plugin-commands-patching": patch
---

Don't run install with the `frozen-lockfile=true` setting.
7 changes: 7 additions & 0 deletions .changeset/tender-candles-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@pnpm/plugin-commands-rebuild": major
"@pnpm/plugin-commands-store": major
"@pnpm/get-context": major
---

New required options added: autoInstallPeers and excludeLinksFromLockfile.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { type Registries } from '@pnpm/types'
import loadJsonFile from 'load-json-file'

export interface StrictRebuildOptions {
autoInstallPeers: boolean
cacheDir: string
childConcurrency: number
excludeLinksFromLockfile: boolean
extraBinPaths: string[]
extraEnv: Record<string, string>
lockfileDir: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function revertFromInlineSpecifiersFormat (lockfile: InlineSpecifiersLock

let revertedImporters = mapValues(importers, revertProjectSnapshot)
let packages = lockfile.packages
if (originalVersion === 6) {
if (originalVersionStr.startsWith('6.')) {
revertedImporters = Object.fromEntries(
Object.entries(revertedImporters ?? {})
.map(([importerId, pkgSnapshot]: [string, ProjectSnapshot]) => {
Expand Down Expand Up @@ -150,7 +150,7 @@ export function revertFromInlineSpecifiersFormat (lockfile: InlineSpecifiersLock
packages,
importers: revertedImporters,
}
if (originalVersion === 6 && newLockfile.time) {
if (originalVersionStr.startsWith('6.') && newLockfile.time) {
newLockfile.time = Object.fromEntries(
Object.entries(newLockfile.time)
.map(([depPath, time]) => [convertLockfileV6DepPathToV5DepPath(depPath), time])
Expand Down
5 changes: 5 additions & 0 deletions lockfile/lockfile-file/src/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export function createLockfileObject (
importerIds: string[],
opts: {
lockfileVersion: number | string
autoInstallPeers: boolean
}
) {
const importers = importerIds.reduce((acc, importerId) => {
Expand All @@ -148,6 +149,10 @@ export function createLockfileObject (
return {
importers,
lockfileVersion: opts.lockfileVersion || LOCKFILE_VERSION,
settings: {
autoInstallPeers: opts.autoInstallPeers,
excludeLinksFromLockfile: false,
},
}
}

Expand Down
11 changes: 6 additions & 5 deletions lockfile/lockfile-file/src/sortLockfileKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ const ORDERED_KEYS = {

const ROOT_KEYS_ORDER = {
lockfileVersion: 1,
settings: 2,
// only and never are conflict options.
neverBuiltDependencies: 2,
onlyBuiltDependencies: 2,
overrides: 3,
packageExtensionsChecksum: 4,
patchedDependencies: 5,
neverBuiltDependencies: 3,
onlyBuiltDependencies: 3,
overrides: 4,
packageExtensionsChecksum: 5,
patchedDependencies: 6,
specifiers: 10,
dependencies: 11,
optionalDependencies: 12,
Expand Down
6 changes: 5 additions & 1 deletion lockfile/lockfile-file/src/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,12 @@ export function normalizeLockfile (lockfile: Lockfile, opts: NormalizeLockfileOp
delete lockfileToSave.packages
}
}
const isLockfileV6 = lockfileToSave.lockfileVersion.toString().startsWith('6.')
if (!isLockfileV6) {
delete lockfileToSave['settings']
}
if (lockfileToSave.time) {
lockfileToSave.time = (lockfileToSave.lockfileVersion.toString().startsWith('6.') ? pruneTimeInLockfileV6 : pruneTime)(lockfileToSave.time, lockfile.importers)
lockfileToSave.time = (isLockfileV6 ? pruneTimeInLockfileV6 : pruneTime)(lockfileToSave.time, lockfile.importers)
}
if ((lockfileToSave.overrides != null) && isEmpty(lockfileToSave.overrides)) {
delete lockfileToSave.overrides
Expand Down
6 changes: 6 additions & 0 deletions lockfile/lockfile-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { type DependenciesMeta, type PatchFile } from '@pnpm/types'

export type { PatchFile }

export interface LockfileSettings {
autoInstallPeers?: boolean
excludeLinksFromLockfile?: boolean
}

export interface Lockfile {
importers: Record<string, ProjectSnapshot>
lockfileVersion: number | string
Expand All @@ -12,6 +17,7 @@ export interface Lockfile {
overrides?: Record<string, string>
packageExtensionsChecksum?: string
patchedDependencies?: Record<string, PatchFile>
settings?: LockfileSettings
}

export interface ProjectSnapshot {
Expand Down
2 changes: 1 addition & 1 deletion packages/constants/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const WANTED_LOCKFILE = 'pnpm-lock.yaml'
export const LOCKFILE_VERSION = 5.4
export const LOCKFILE_VERSION_V6 = '6.0'
export const LOCKFILE_VERSION_V6 = '6.1'

export const ENGINE_NAME = `${process.platform}-${process.arch}-node-${process.version.split('.')[0]}`
export const LAYOUT_VERSION = 5
Expand Down
8 changes: 7 additions & 1 deletion patching/plugin-commands-patching/src/patchCommit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ export async function handler (opts: install.InstallCommandOptions & Pick<Config
opts.allProjectsGraph[lockfileDir].package.manifest = rootProjectManifest
}

return install.handler(opts)
return install.handler({
...opts,
rawLocalConfig: {
...opts.rawLocalConfig,
'frozen-lockfile': false,
},
})
}

async function diffFolders (folderA: string, folderB: string) {
Expand Down
30 changes: 18 additions & 12 deletions patching/plugin-commands-patching/test/patch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,18 +516,24 @@ describe('patch and commit in workspaces', () => {
})

describe('patch with custom modules-dir and virtual-store-dir', () => {
const customModulesDirFixture = tempDir()
f.copy('custom-modules-dir', customModulesDirFixture)
const cacheDir = path.resolve(customModulesDirFixture, 'cache')
const storeDir = path.resolve(customModulesDirFixture, 'store')
const defaultPatchOption = {
...basePatchOption,
cacheDir,
dir: customModulesDirFixture,
storeDir,
modulesDir: 'fake_modules',
virtualStoreDir: 'fake_modules/.fake_store',
}
let defaultPatchOption: patch.PatchCommandOptions
let customModulesDirFixture: string
let cacheDir: string
let storeDir: string
beforeAll(() => {
customModulesDirFixture = tempDir()
f.copy('custom-modules-dir', customModulesDirFixture)
cacheDir = path.resolve(customModulesDirFixture, 'cache')
storeDir = path.resolve(customModulesDirFixture, 'store')
defaultPatchOption = {
...basePatchOption,
cacheDir,
dir: customModulesDirFixture,
storeDir,
modulesDir: 'fake_modules',
virtualStoreDir: 'fake_modules/.fake_store',
}
})

test('should work with custom modules-dir and virtual-store-dir', async () => {
const manifest = fs.readFileSync(path.join(customModulesDirFixture, 'package.json'), 'utf8')
Expand Down
2 changes: 1 addition & 1 deletion pkg-manager/core/src/getPeerDependencyIssues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type ListMissingPeersOptions = Partial<GetContextOptions>
| 'useGitBranchLockfile'
| 'workspacePackages'
>
& Pick<GetContextOptions, 'storeDir'>
& Pick<GetContextOptions, 'autoInstallPeers' | 'storeDir'>

export async function getPeerDependencyIssues (
projects: ProjectOptions[],
Expand Down
70 changes: 58 additions & 12 deletions pkg-manager/core/src/install/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ import { getAllUniqueSpecs, getPreferredVersionsFromLockfileAndManifests } from
import { linkPackages } from './link'
import { reportPeerDependencyIssues } from './reportPeerDependencyIssues'

class LockfileConfigMismatchError extends PnpmError {
constructor (outdatedLockfileSettingName: string) {
super('LOCKFILE_CONFIG_MISMATCH',
`Cannot proceed with the frozen installation. The current "${outdatedLockfileSettingName!}" configuration doesn't match the value found in the lockfile`, {
hint: 'Update your lockfile using "pnpm install --no-frozen-lockfile"',
})
}
}

const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([
'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE',
'ERR_PNPM_TARBALL_INTEGRITY',
Expand Down Expand Up @@ -311,25 +320,42 @@ export async function mutateModules (
if (opts.useLockfileV6 == null) {
opts.useLockfileV6 = ctx.wantedLockfile.lockfileVersion.toString().startsWith('6.')
}
let needsFullResolution = !maybeOpts.ignorePackageManifest &&
lockfileIsNotUpToDate(ctx.wantedLockfile, {
const frozenLockfile = opts.frozenLockfile ||
opts.frozenLockfileIfExists && ctx.existsWantedLockfile
let outdatedLockfileSettings = false
if (!opts.ignorePackageManifest) {
const outdatedLockfileSettingName = getOutdatedLockfileSetting(ctx.wantedLockfile, {
autoInstallPeers: opts.autoInstallPeers,
overrides: opts.overrides,
neverBuiltDependencies: opts.neverBuiltDependencies,
onlyBuiltDependencies: opts.onlyBuiltDependencies,
packageExtensionsChecksum,
patchedDependencies,
}) ||
})
outdatedLockfileSettings = outdatedLockfileSettingName != null
if (frozenLockfile && outdatedLockfileSettings) {
throw new LockfileConfigMismatchError(outdatedLockfileSettingName!)
}
}
let needsFullResolution = outdatedLockfileSettings ||
opts.fixLockfile ||
opts.useLockfileV6 && !ctx.wantedLockfile.lockfileVersion.toString().startsWith('6.')
if (needsFullResolution) {
ctx.wantedLockfile.settings = {
autoInstallPeers: opts.autoInstallPeers,
excludeLinksFromLockfile: false,
}
ctx.wantedLockfile.overrides = opts.overrides
ctx.wantedLockfile.neverBuiltDependencies = opts.neverBuiltDependencies
ctx.wantedLockfile.onlyBuiltDependencies = opts.onlyBuiltDependencies
ctx.wantedLockfile.packageExtensionsChecksum = packageExtensionsChecksum
ctx.wantedLockfile.patchedDependencies = patchedDependencies
} else if (!frozenLockfile) {
ctx.wantedLockfile.settings = {
autoInstallPeers: opts.autoInstallPeers,
excludeLinksFromLockfile: false,
}
}
const frozenLockfile = opts.frozenLockfile ||
opts.frozenLockfileIfExists && ctx.existsWantedLockfile
if (
!ctx.lockfileHadConflicts &&
!opts.fixLockfile &&
Expand Down Expand Up @@ -619,26 +645,46 @@ async function calcPatchHashes (patches: Record<string, string>, lockfileDir: st
}, patches)
}

function lockfileIsNotUpToDate (
function getOutdatedLockfileSetting (
lockfile: Lockfile,
{
neverBuiltDependencies,
onlyBuiltDependencies,
overrides,
packageExtensionsChecksum,
patchedDependencies,
autoInstallPeers,
}: {
neverBuiltDependencies?: string[]
onlyBuiltDependencies?: string[]
overrides?: Record<string, string>
packageExtensionsChecksum?: string
patchedDependencies?: Record<string, PatchFile>
}) {
return !equals(lockfile.overrides ?? {}, overrides ?? {}) ||
!equals((lockfile.neverBuiltDependencies ?? []).sort(), (neverBuiltDependencies ?? []).sort()) ||
!equals(onlyBuiltDependencies?.sort(), lockfile.onlyBuiltDependencies) ||
lockfile.packageExtensionsChecksum !== packageExtensionsChecksum ||
!equals(lockfile.patchedDependencies ?? {}, patchedDependencies ?? {})
autoInstallPeers?: boolean
}
) {
if (!equals(lockfile.overrides ?? {}, overrides ?? {})) {
return 'overrides'
}
if (!equals((lockfile.neverBuiltDependencies ?? []).sort(), (neverBuiltDependencies ?? []).sort())) {
return 'neverBuiltDependencies'
}
if (!equals(onlyBuiltDependencies?.sort(), lockfile.onlyBuiltDependencies)) {
return 'onlyBuiltDependencies'
}
if (lockfile.packageExtensionsChecksum !== packageExtensionsChecksum) {
return 'packageExtensionsChecksum'
}
if (!equals(lockfile.patchedDependencies ?? {}, patchedDependencies ?? {})) {
return 'patchedDependencies'
}
if ((lockfile.settings?.autoInstallPeers != null && lockfile.settings.autoInstallPeers !== autoInstallPeers)) {
return 'settings.autoInstallPeers'
}
if (lockfile.settings?.excludeLinksFromLockfile) {
return 'settings.excludeLinksFromLockfile'
}
return null
}

export function createObjectChecksum (obj: unknown) {
Expand Down
2 changes: 2 additions & 0 deletions pkg-manager/core/src/link/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
import { type ReporterFunction } from '../types'

interface StrictLinkOptions {
autoInstallPeers: boolean
binsDir: string
excludeLinksFromLockfile: boolean
force: boolean
forceSharedLockfile: boolean
useLockfile: boolean
Expand Down
15 changes: 15 additions & 0 deletions pkg-manager/core/test/install/frozenLockfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,18 @@ test('prefer-frozen-lockfile: should prefer frozen-lockfile when package has lin
await projects['p1'].has('p2')
await projects['p2'].has('is-negative')
})

test('frozen-lockfile: installation fails if the value of auto-install-peers changes', async () => {
prepareEmpty()
const manifest = {
dependencies: {
'is-positive': '^3.0.0',
},
}

await install(manifest, await testDefaults({ autoInstallPeers: true, useLockfileV6: true }))

await expect(
install(manifest, await testDefaults({ frozenLockfile: true, autoInstallPeers: false, useLockfileV6: true }))
).rejects.toThrow('Cannot proceed with the frozen installation. The current "settings.autoInstallPeers" configuration doesn\'t match the value found in the lockfile')
})
4 changes: 2 additions & 2 deletions pkg-manager/core/test/install/overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ test('versions are replaced with versions specified through overrides option', a
rootDir: process.cwd(),
}, await testDefaults({ frozenLockfile: true, overrides }))
).rejects.toThrow(
new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE',
'Cannot perform a frozen installation because the lockfile needs updates'
new PnpmError('LOCKFILE_CONFIG_MISMATCH',
'Cannot proceed with the frozen installation. The current "overrides" configuration doesn\'t match the value found in the lockfile'
)
)
})
Expand Down
4 changes: 2 additions & 2 deletions pkg-manager/core/test/install/packageExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ test('manifests are extended with fields specified by packageExtensions', async
rootDir: process.cwd(),
}, await testDefaults({ frozenLockfile: true, packageExtensions }))
).rejects.toThrow(
new PnpmError('FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE',
'Cannot perform a frozen installation because the lockfile needs updates'
new PnpmError('LOCKFILE_CONFIG_MISMATCH',
'Cannot proceed with the frozen installation. The current "packageExtensionsChecksum" configuration doesn\'t match the value found in the lockfile'
)
)
})
Expand Down