Skip to content

Commit

Permalink
feat: add internal lockfileCheck option for lockfile only diff instal…
Browse files Browse the repository at this point in the history
…ls (#6404)
  • Loading branch information
gluxon committed Apr 16, 2023
1 parent 49b15ac commit 6706a7d
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .changeset/fuzzy-spiders-begin.md
@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-installation": patch
"@pnpm/core": patch
---

Add lockfileCheck option for lockfile only diff installs
1 change: 1 addition & 0 deletions pkg-manager/core/src/install/extendInstallOptions.ts
Expand Up @@ -42,6 +42,7 @@ export interface StrictInstallOptions {
ignorePackageManifest: boolean
preferFrozenLockfile: boolean
saveWorkspaceProtocol: boolean | 'rolling'
lockfileCheck?: (prev: Lockfile, next: Lockfile) => void
lockfileIncludeTarballUrl: boolean
preferWorkspacePackages: boolean
preserveWorkspaceProtocol: boolean
Expand Down
26 changes: 24 additions & 2 deletions pkg-manager/core/src/install/index.ts
Expand Up @@ -65,6 +65,7 @@ import pLimit from 'p-limit'
import pMapValues from 'p-map-values'
import flatten from 'ramda/src/flatten'
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 pickBy from 'ramda/src/pickBy'
Expand Down Expand Up @@ -801,6 +802,19 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
})
}

// The wanted lockfile is mutated during installation. To compare changes, a
// deep copy before installation is needed. This copy should represent the
// original wanted lockfile on disk as close as possible.
//
// This object can be quite large. Intentionally avoiding an expensive copy
// if no lockfileCheck option was passed in.
const originalLockfileForCheck = opts.lockfileCheck != null
? clone(ctx.wantedLockfile)
: null

// Aliasing for clarity in boolean expressions below.
const isInstallationOnlyForLockfileCheck = opts.lockfileCheck != null

ctx.wantedLockfile.importers = ctx.wantedLockfile.importers || {}
for (const { id } of projects) {
if (!ctx.wantedLockfile.importers[id]) {
Expand Down Expand Up @@ -965,7 +979,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
useGitBranchLockfile: opts.useGitBranchLockfile,
mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
}
if (!opts.lockfileOnly && opts.enableModulesDir) {
if (!opts.lockfileOnly && !isInstallationOnlyForLockfileCheck && opts.enableModulesDir) {
const result = await linkPackages(
projects,
dependenciesGraph,
Expand Down Expand Up @@ -1199,7 +1213,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
}
} else {
await finishLockfileUpdates()
if (opts.useLockfile) {
if (opts.useLockfile && !isInstallationOnlyForLockfileCheck) {
await writeWantedLockfile(ctx.lockfileDir, newLockfile, lockfileOpts)
}

Expand All @@ -1223,6 +1237,14 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
strictPeerDependencies: opts.strictPeerDependencies,
})

// Similar to the sequencing for when the original wanted lockfile is
// copied, the new lockfile passed here should be as close as possible to
// what will eventually be written to disk. Ex: peers should be resolved,
// the afterAllResolved hook has been applied, etc.
if (originalLockfileForCheck != null) {
opts.lockfileCheck?.(originalLockfileForCheck, newLockfile)
}

return {
newLockfile,
projects: projects.map(({ id, manifest, rootDir }) => ({
Expand Down
12 changes: 12 additions & 0 deletions pkg-manager/plugin-commands-installation/src/installDeps.ts
Expand Up @@ -7,6 +7,7 @@ import { type Config } from '@pnpm/config'
import { PnpmError } from '@pnpm/error'
import { filterPkgsBySelectorObjects } from '@pnpm/filter-workspace-packages'
import { arrayOfWorkspacePackagesToMap, findWorkspacePackages } from '@pnpm/find-workspace-packages'
import { type Lockfile } from '@pnpm/lockfile-types'
import { rebuildProjects } from '@pnpm/plugin-commands-rebuild'
import { createOrConnectStoreController, type CreateStoreControllerOptions } from '@pnpm/store-connection-manager'
import { type IncludedDependencies, type Project, type ProjectsGraph } from '@pnpm/types'
Expand Down Expand Up @@ -88,6 +89,17 @@ export type InstallDepsOptions = Pick<Config,
include?: IncludedDependencies
includeDirect?: IncludedDependencies
latest?: boolean
/**
* If specified, the installation will only be performed for comparison of the
* wanted lockfile. The wanted lockfile will not be updated on disk and no
* modules will be linked.
*
* The given callback is passed the wanted lockfile before installation and
* after. This allows functions to reasonably determine whether the wanted
* lockfile will change on disk after installation. The lockfile arguments
* passed to this callback should not be mutated.
*/
lockfileCheck?: (prev: Lockfile, next: Lockfile) => void
update?: boolean
updateMatching?: (pkgName: string) => boolean
updatePackageManifest?: boolean
Expand Down

0 comments on commit 6706a7d

Please sign in to comment.