Skip to content

Commit

Permalink
feat: include some settings as fields in the lockfile (#6557)
Browse files Browse the repository at this point in the history
ref #6312
  • Loading branch information
zkochan committed May 20, 2023
1 parent bac9712 commit 9c4ae87
Show file tree
Hide file tree
Showing 55 changed files with 347 additions and 70 deletions.
15 changes: 15 additions & 0 deletions .changeset/fast-news-deliver.md
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
2 changes: 1 addition & 1 deletion __fixtures__/empty/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __fixtures__/local-pkg/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __fixtures__/local-scoped-pkg/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __fixtures__/multiple-scripts-error-exit/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __fixtures__/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __fixtures__/tar-pkg/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion __fixtures__/workspace-with-lockfile-dupes/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
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
6 changes: 6 additions & 0 deletions lockfile/lockfile-file/src/read.ts
Expand Up @@ -136,6 +136,8 @@ export function createLockfileObject (
importerIds: string[],
opts: {
lockfileVersion: number | string
autoInstallPeers: boolean
excludeLinksFromLockfile: boolean
}
) {
const importers = importerIds.reduce((acc, importerId) => {
Expand All @@ -148,6 +150,10 @@ export function createLockfileObject (
return {
importers,
lockfileVersion: opts.lockfileVersion || LOCKFILE_VERSION,
settings: {
autoInstallPeers: opts.autoInstallPeers,
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
},
}
}

Expand Down
11 changes: 6 additions & 5 deletions lockfile/lockfile-file/src/sortLockfileKeys.ts
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
7 changes: 7 additions & 0 deletions lockfile/lockfile-types/src/index.ts
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 All @@ -33,6 +39,7 @@ export interface LockfileV6 {
overrides?: Record<string, string>
packageExtensionsChecksum?: string
patchedDependencies?: Record<string, PatchFile>
settings?: LockfileSettings
}

export interface ProjectSnapshotV6 {
Expand Down
2 changes: 1 addition & 1 deletion packages/constants/src/index.ts
@@ -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
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
Expand Up @@ -532,18 +532,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
Expand Up @@ -20,7 +20,7 @@ export type ListMissingPeersOptions = Partial<GetContextOptions>
| 'useGitBranchLockfile'
| 'workspacePackages'
>
& Pick<GetContextOptions, 'storeDir'>
& Pick<GetContextOptions, 'autoInstallPeers' | 'excludeLinksFromLockfile' | 'storeDir'>

export async function getPeerDependencyIssues (
projects: ProjectOptions[],
Expand Down
73 changes: 61 additions & 12 deletions pkg-manager/core/src/install/index.ts
Expand Up @@ -84,6 +84,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 @@ -317,26 +326,44 @@ export async function mutateModules (
path: path.join(opts.lockfileDir, patchFile.path),
}), patchedDependencies)
: undefined
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,
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
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 ||
!ctx.wantedLockfile.lockfileVersion.toString().startsWith('6.') ||
opts.forceFullResolution
if (needsFullResolution) {
ctx.wantedLockfile.settings = {
autoInstallPeers: opts.autoInstallPeers,
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
}
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: opts.excludeLinksFromLockfile,
}
}
const frozenLockfile = opts.frozenLockfile ||
opts.frozenLockfileIfExists && ctx.existsWantedLockfile
if (
!ctx.lockfileHadConflicts &&
!opts.fixLockfile &&
Expand Down Expand Up @@ -645,26 +672,48 @@ async function calcPatchHashes (patches: Record<string, string>, lockfileDir: st
}, patches)
}

function lockfileIsNotUpToDate (
function getOutdatedLockfileSetting (
lockfile: Lockfile,
{
neverBuiltDependencies,
onlyBuiltDependencies,
overrides,
packageExtensionsChecksum,
patchedDependencies,
autoInstallPeers,
excludeLinksFromLockfile,
}: {
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
excludeLinksFromLockfile?: 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 != null && lockfile.settings.excludeLinksFromLockfile !== 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
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
8 changes: 8 additions & 0 deletions pkg-manager/core/test/install/aliases.ts
Expand Up @@ -13,6 +13,10 @@ test('installing aliased dependency', async () => {
expect(typeof project.requireModule('positive')).toBe('function')

expect(await project.readLockfile()).toStrictEqual({
settings: {
autoInstallPeers: true,
excludeLinksFromLockfile: false,
},
dependencies: {
negative: {
specifier: 'npm:is-negative@1.0.0',
Expand Down Expand Up @@ -70,6 +74,10 @@ test('a dependency has an aliased subdependency', async () => {
expect(project.requireModule('@pnpm.e2e/pkg-with-1-aliased-dep')().name).toEqual('@pnpm.e2e/dep-of-pkg-with-1-dep')

expect(await project.readLockfile()).toStrictEqual({
settings: {
autoInstallPeers: true,
excludeLinksFromLockfile: false,
},
dependencies: {
'@pnpm.e2e/pkg-with-1-aliased-dep': {
specifier: '^100.0.0',
Expand Down

0 comments on commit 9c4ae87

Please sign in to comment.