mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat: add git-branch-lockfile config to generate lockfile in each branch (#4475)
Co-authored-by: Zoltan Kochan <z@kochan.io> Co-authored-by: David Michon <dmichon@microsoft.com>
This commit is contained in:
14
.changeset/silent-swans-teach.md
Normal file
14
.changeset/silent-swans-teach.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
"@pnpm/core": minor
|
||||
"@pnpm/get-context": minor
|
||||
"@pnpm/git-utils": minor
|
||||
"@pnpm/headless": minor
|
||||
"@pnpm/lockfile-file": minor
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
"@pnpm/plugin-commands-publishing": minor
|
||||
"pnpm": minor
|
||||
"@pnpm/assert-project": minor
|
||||
---
|
||||
|
||||
New settings added: use-git-branch-lockfile, merge-git-branch-lockfiles, merge-git-branch-lockfiles-branch-pattern.
|
||||
2
fixtures/with-non-package-dep/pnpm-lock.yaml
generated
2
fixtures/with-non-package-dep/pnpm-lock.yaml
generated
@@ -1,4 +1,4 @@
|
||||
lockfileVersion: 5.3
|
||||
lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
camelcase: denolib/camelcase#aeb6b15f9c9957c8fa56f9731e914c4d8a6d2f2b
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
"dependencies": {
|
||||
"@pnpm/constants": "workspace:6.1.0",
|
||||
"@pnpm/error": "workspace:3.0.1",
|
||||
"@pnpm/git-utils": "workspace:0.0.0",
|
||||
"@pnpm/matcher": "workspace:3.0.0",
|
||||
"@pnpm/npm-conf": "1.0.4",
|
||||
"@pnpm/pnpmfile": "workspace:2.0.3",
|
||||
"@pnpm/read-project-manifest": "workspace:3.0.3",
|
||||
|
||||
@@ -130,6 +130,9 @@ export interface Config {
|
||||
modulesDir?: string
|
||||
sharedWorkspaceLockfile?: boolean
|
||||
useLockfile: boolean
|
||||
useGitBranchLockfile: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
mergeGitBranchLockfilesBranchPattern?: string[]
|
||||
globalPnpmfile?: string
|
||||
npmPath?: string
|
||||
gitChecks?: boolean
|
||||
|
||||
@@ -6,6 +6,8 @@ import loadNpmConf from '@pnpm/npm-conf'
|
||||
import npmTypes from '@pnpm/npm-conf/lib/types'
|
||||
import { requireHooks } from '@pnpm/pnpmfile'
|
||||
import { safeReadProjectManifestOnly } from '@pnpm/read-project-manifest'
|
||||
import { getCurrentBranch } from '@pnpm/git-utils'
|
||||
import matcher from '@pnpm/matcher'
|
||||
import camelcase from 'camelcase'
|
||||
import normalizeRegistryUrl from 'normalize-registry-url'
|
||||
import fromPairs from 'ramda/src/fromPairs'
|
||||
@@ -36,6 +38,8 @@ export const types = Object.assign({
|
||||
bail: Boolean,
|
||||
'cache-dir': String,
|
||||
'child-concurrency': Number,
|
||||
'merge-git-branch-lockfiles': Boolean,
|
||||
'merge-git-branch-lockfiles-branch-pattern': Array,
|
||||
color: ['always', 'auto', 'never'],
|
||||
'config-dir': String,
|
||||
dev: [null, true],
|
||||
@@ -53,6 +57,7 @@ export const types = Object.assign({
|
||||
'global-dir': String,
|
||||
'global-path': String,
|
||||
'global-pnpmfile': String,
|
||||
'git-branch-lockfile': Boolean,
|
||||
hoist: Boolean,
|
||||
'hoist-pattern': Array,
|
||||
'ignore-pnpmfile': Boolean,
|
||||
@@ -185,6 +190,7 @@ export default async (
|
||||
'bitbucket.org',
|
||||
],
|
||||
globalconfig: npmDefaults.globalconfig,
|
||||
'git-branch-lockfile': false,
|
||||
hoist: true,
|
||||
'hoist-pattern': ['*'],
|
||||
'ignore-workspace-root-check': false,
|
||||
@@ -261,6 +267,21 @@ export default async (
|
||||
if (typeof pnpmConfig['packageLock'] === 'boolean') return pnpmConfig['packageLock']
|
||||
return false
|
||||
})()
|
||||
pnpmConfig.useGitBranchLockfile = (() => {
|
||||
if (typeof pnpmConfig['gitBranchLockfile'] === 'boolean') return pnpmConfig['gitBranchLockfile']
|
||||
return false
|
||||
})()
|
||||
pnpmConfig.mergeGitBranchLockfiles = await (async () => {
|
||||
if (typeof pnpmConfig['mergeGitBranchLockfiles'] === 'boolean') return pnpmConfig['mergeGitBranchLockfiles']
|
||||
if (pnpmConfig['mergeGitBranchLockfilesBranchPattern'] != null && pnpmConfig['mergeGitBranchLockfilesBranchPattern'].length > 0) {
|
||||
const branch = await getCurrentBranch()
|
||||
if (branch) {
|
||||
const branchMatcher = matcher(pnpmConfig['mergeGitBranchLockfilesBranchPattern'])
|
||||
return branchMatcher(branch)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
})()
|
||||
pnpmConfig.pnpmHomeDir = getDataDir(process)
|
||||
|
||||
if (cliOptions['global']) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import PATH from 'path-name'
|
||||
import { getCurrentBranch } from '@pnpm/git-utils'
|
||||
import getConfig from '@pnpm/config'
|
||||
import PnpmError from '@pnpm/error'
|
||||
import loadNpmConf from '@pnpm/npm-conf'
|
||||
@@ -9,6 +10,8 @@ import prepare, { prepareEmpty } from '@pnpm/prepare'
|
||||
|
||||
import symlinkDir from 'symlink-dir'
|
||||
|
||||
jest.mock('@pnpm/git-utils', () => ({ getCurrentBranch: jest.fn() }))
|
||||
|
||||
// To override any local settings,
|
||||
// we force the default values of config
|
||||
delete process.env.npm_config_depth
|
||||
@@ -805,3 +808,92 @@ test('getConfig() should read cafile', async () => {
|
||||
expect(config.ca).toStrictEqual([`xxx
|
||||
-----END CERTIFICATE-----`])
|
||||
})
|
||||
|
||||
test('respect merge-git-branch-lockfiles-branch-pattern', async () => {
|
||||
{
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
})
|
||||
|
||||
expect(config.mergeGitBranchLockfilesBranchPattern).toBeUndefined()
|
||||
expect(config.mergeGitBranchLockfiles).toBeUndefined()
|
||||
}
|
||||
{
|
||||
prepareEmpty()
|
||||
|
||||
const npmrc = [
|
||||
'merge-git-branch-lockfiles-branch-pattern[]=main',
|
||||
'merge-git-branch-lockfiles-branch-pattern[]=release/**',
|
||||
].join('\n')
|
||||
|
||||
await fs.writeFile('.npmrc', npmrc, 'utf8')
|
||||
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {
|
||||
global: false,
|
||||
},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
})
|
||||
|
||||
expect(config.mergeGitBranchLockfilesBranchPattern).toEqual(['main', 'release/**'])
|
||||
}
|
||||
})
|
||||
|
||||
test('getConfig() sets merge-git-branch-lockfiles when branch matches merge-git-branch-lockfiles-branch-pattern', async () => {
|
||||
prepareEmpty()
|
||||
{
|
||||
const npmrc = [
|
||||
'merge-git-branch-lockfiles-branch-pattern[]=main',
|
||||
'merge-git-branch-lockfiles-branch-pattern[]=release/**',
|
||||
].join('\n')
|
||||
|
||||
await fs.writeFile('.npmrc', npmrc, 'utf8')
|
||||
|
||||
getCurrentBranch['mockReturnValue']('develop')
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {
|
||||
global: false,
|
||||
},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
})
|
||||
|
||||
expect(config.mergeGitBranchLockfilesBranchPattern).toEqual(['main', 'release/**'])
|
||||
expect(config.mergeGitBranchLockfiles).toBe(false)
|
||||
}
|
||||
{
|
||||
getCurrentBranch['mockReturnValue']('main')
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {
|
||||
global: false,
|
||||
},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
})
|
||||
expect(config.mergeGitBranchLockfiles).toBe(true)
|
||||
}
|
||||
{
|
||||
getCurrentBranch['mockReturnValue']('release/1.0.0')
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {
|
||||
global: false,
|
||||
},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
})
|
||||
expect(config.mergeGitBranchLockfiles).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
{
|
||||
"path": "../error"
|
||||
},
|
||||
{
|
||||
"path": "../git-utils"
|
||||
},
|
||||
{
|
||||
"path": "../matcher"
|
||||
},
|
||||
{
|
||||
"path": "../pnpmfile"
|
||||
},
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"@pnpm/cafs": "workspace:4.0.4",
|
||||
"@pnpm/client": "workspace:7.1.4",
|
||||
"@pnpm/core": "workspace:5.3.1",
|
||||
"@pnpm/git-utils": "workspace:0.0.0",
|
||||
"@pnpm/logger": "^4.0.0",
|
||||
"@pnpm/package-store": "workspace:13.0.7",
|
||||
"@pnpm/prepare": "workspace:*",
|
||||
|
||||
@@ -16,6 +16,7 @@ export type ListMissingPeersOptions = Partial<GetContextOptions>
|
||||
| 'preferWorkspacePackages'
|
||||
| 'saveWorkspaceProtocol'
|
||||
| 'storeController'
|
||||
| 'useGitBranchLockfile'
|
||||
| 'workspacePackages'
|
||||
>
|
||||
& Pick<GetContextOptions, 'storeDir'>
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface StrictInstallOptions {
|
||||
extraBinPaths: string[]
|
||||
hoistingLimits?: HoistingLimits
|
||||
useLockfile: boolean
|
||||
useGitBranchLockfile: boolean
|
||||
mergeGitBranchLockfiles: boolean
|
||||
linkWorkspacePackagesDepth: number
|
||||
lockfileOnly: boolean
|
||||
fixLockfile: boolean
|
||||
@@ -167,6 +169,8 @@ const defaults = async (opts: InstallOptions) => {
|
||||
process.getuid() !== 0,
|
||||
update: false,
|
||||
useLockfile: true,
|
||||
useGitBranchLockfile: false,
|
||||
mergeGitBranchLockfiles: false,
|
||||
userAgent: `${packageManager.name}/${packageManager.version} npm/? node/${process.version} ${process.platform} ${process.arch}`,
|
||||
verifyStoreIntegrity: true,
|
||||
workspacePackages: {},
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
writeCurrentLockfile,
|
||||
writeLockfiles,
|
||||
writeWantedLockfile,
|
||||
cleanGitBranchLockfiles,
|
||||
} from '@pnpm/lockfile-file'
|
||||
import { writePnpFile } from '@pnpm/lockfile-to-pnp'
|
||||
import { extendProjectsWithTargetDirs } from '@pnpm/lockfile-utils'
|
||||
@@ -202,6 +203,10 @@ export async function mutateModules (
|
||||
streamParser.removeListener('data', reporter)
|
||||
}
|
||||
|
||||
if (opts.mergeGitBranchLockfiles) {
|
||||
await cleanGitBranchLockfiles(ctx.lockfileDir)
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
async function _install (): Promise<UpdatedProject[]> {
|
||||
@@ -785,7 +790,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
}
|
||||
|
||||
const depsStateCache: DepsStateCache = {}
|
||||
const lockfileOpts = { forceSharedFormat: opts.forceSharedLockfile }
|
||||
const lockfileOpts = { forceSharedFormat: opts.forceSharedLockfile, useGitBranchLockfile: opts.useGitBranchLockfile, mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles }
|
||||
if (!opts.lockfileOnly && opts.enableModulesDir) {
|
||||
const result = await linkPackages(
|
||||
projects,
|
||||
|
||||
@@ -129,7 +129,7 @@ export default async function link (
|
||||
} else {
|
||||
newPkg = opts.manifest
|
||||
}
|
||||
const lockfileOpts = { forceSharedFormat: opts.forceSharedLockfile }
|
||||
const lockfileOpts = { forceSharedFormat: opts.forceSharedLockfile, useGitBranchLockfile: opts.useGitBranchLockfile, mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles }
|
||||
if (opts.useLockfile) {
|
||||
await writeLockfiles({
|
||||
currentLockfile: updatedCurrentLockfile,
|
||||
|
||||
@@ -29,6 +29,9 @@ interface StrictLinkOptions {
|
||||
|
||||
publicHoistPattern: string[] | undefined
|
||||
forcePublicHoistPattern: boolean
|
||||
|
||||
useGitBranchLockfile: boolean
|
||||
mergeGitBranchLockfiles: boolean
|
||||
}
|
||||
|
||||
export type LinkOptions =
|
||||
|
||||
146
packages/core/test/install/gitBranchLockfile.test.ts
Normal file
146
packages/core/test/install/gitBranchLockfile.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { prepareEmpty, preparePackages } from '@pnpm/prepare'
|
||||
import { install, mutateModules } from '@pnpm/core'
|
||||
import { testDefaults } from '../utils'
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { ProjectManifest } from '@pnpm/types'
|
||||
import { getCurrentBranch } from '@pnpm/git-utils'
|
||||
import writeYamlFile from 'write-yaml-file'
|
||||
|
||||
jest.mock('@pnpm/git-utils', () => ({ getCurrentBranch: jest.fn() }))
|
||||
|
||||
test('install with git-branch-lockfile = true', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
const branchName: string = 'main-branch'
|
||||
getCurrentBranch['mockReturnValue'](branchName)
|
||||
|
||||
const opts = await testDefaults({
|
||||
useGitBranchLockfile: true,
|
||||
})
|
||||
|
||||
await install({
|
||||
dependencies: {
|
||||
'is-positive': '^3.0.0',
|
||||
},
|
||||
}, opts)
|
||||
|
||||
expect(fs.existsSync(`pnpm-lock.${branchName}.yaml`)).toBe(true)
|
||||
expect(fs.existsSync(WANTED_LOCKFILE)).toBe(false)
|
||||
})
|
||||
|
||||
test('install with git-branch-lockfile = true and no lockfile changes', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
const branchName: string = 'main-branch'
|
||||
getCurrentBranch['mockReturnValue'](branchName)
|
||||
|
||||
const manifest: ProjectManifest = {
|
||||
dependencies: {
|
||||
'is-positive': '^3.0.0',
|
||||
},
|
||||
}
|
||||
|
||||
const opts1 = await testDefaults({
|
||||
useGitBranchLockfile: false,
|
||||
})
|
||||
await install(manifest, opts1)
|
||||
|
||||
expect(fs.existsSync(WANTED_LOCKFILE)).toBe(true)
|
||||
|
||||
const opts2 = await testDefaults({
|
||||
useGitBranchLockfile: true,
|
||||
})
|
||||
await install(manifest, opts2)
|
||||
expect(fs.existsSync(WANTED_LOCKFILE)).toBe(true)
|
||||
// Git branch lockfile is created only if there are changes in the lockfile
|
||||
expect(fs.existsSync(`pnpm-lock.${branchName}.yaml`)).toBe(false)
|
||||
})
|
||||
|
||||
test('install a workspace with git-branch-lockfile = true', async () => {
|
||||
const rootManifest: ProjectManifest = {
|
||||
name: 'root',
|
||||
}
|
||||
const project1Manifest: ProjectManifest = {
|
||||
name: 'project-1',
|
||||
dependencies: { 'is-positive': '1.0.0' },
|
||||
}
|
||||
const project2Manifest: ProjectManifest = {
|
||||
name: 'project-2',
|
||||
dependencies: { 'is-positive': '1.0.0' },
|
||||
}
|
||||
preparePackages([
|
||||
{
|
||||
location: '.',
|
||||
package: rootManifest,
|
||||
},
|
||||
{
|
||||
location: 'project-1',
|
||||
package: project1Manifest,
|
||||
},
|
||||
{
|
||||
location: 'project-2',
|
||||
package: project2Manifest,
|
||||
},
|
||||
])
|
||||
|
||||
const branchName: string = 'main-branch'
|
||||
getCurrentBranch['mockReturnValue'](branchName)
|
||||
|
||||
const opts = await testDefaults({
|
||||
useGitBranchLockfile: true,
|
||||
})
|
||||
|
||||
await mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: rootManifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: project1Manifest,
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('project-1'),
|
||||
},
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: project2Manifest,
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('project-2'),
|
||||
},
|
||||
], opts)
|
||||
|
||||
expect(fs.existsSync(`pnpm-lock.${branchName}.yaml`)).toBe(true)
|
||||
expect(fs.existsSync(WANTED_LOCKFILE)).toBe(false)
|
||||
})
|
||||
|
||||
test('install with --merge-git-branch-lockfiles', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
const branchName: string = 'main-branch'
|
||||
getCurrentBranch['mockReturnValue'](branchName)
|
||||
|
||||
const otherLockfilePath: string = path.resolve('pnpm-lock.other.yaml')
|
||||
await writeYamlFile(otherLockfilePath, {
|
||||
whatever: 'whatever',
|
||||
})
|
||||
|
||||
expect(fs.existsSync(otherLockfilePath)).toBe(true)
|
||||
expect(fs.existsSync(WANTED_LOCKFILE)).toBe(false)
|
||||
|
||||
const opts = await testDefaults({
|
||||
useGitBranchLockfile: true,
|
||||
mergeGitBranchLockfiles: true,
|
||||
})
|
||||
await install({
|
||||
dependencies: {
|
||||
'is-positive': '^3.0.0',
|
||||
},
|
||||
}, opts)
|
||||
|
||||
expect(fs.existsSync(otherLockfilePath)).toBe(false)
|
||||
expect(fs.existsSync(WANTED_LOCKFILE)).toBe(true)
|
||||
})
|
||||
@@ -51,6 +51,9 @@
|
||||
{
|
||||
"path": "../get-context"
|
||||
},
|
||||
{
|
||||
"path": "../git-utils"
|
||||
},
|
||||
{
|
||||
"path": "../headless"
|
||||
},
|
||||
|
||||
@@ -82,6 +82,8 @@ export interface GetContextOptions {
|
||||
registries: Registries
|
||||
storeDir: string
|
||||
useLockfile: boolean
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
virtualStoreDir?: string
|
||||
|
||||
hoistPattern?: string[] | undefined
|
||||
@@ -176,6 +178,8 @@ export default async function getContext<T> (
|
||||
projects: importersContext.projects,
|
||||
registry: opts.registries.default,
|
||||
useLockfile: opts.useLockfile,
|
||||
useGitBranchLockfile: opts.useGitBranchLockfile,
|
||||
mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
|
||||
virtualStoreDir,
|
||||
}),
|
||||
}
|
||||
@@ -376,6 +380,8 @@ export async function getContextForSingleImporter (
|
||||
registries: Registries
|
||||
storeDir: string
|
||||
useLockfile: boolean
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
virtualStoreDir?: string
|
||||
|
||||
hoistPattern?: string[] | undefined
|
||||
@@ -479,6 +485,8 @@ export async function getContextForSingleImporter (
|
||||
projects: [{ id: importerId, rootDir: opts.dir }],
|
||||
registry: opts.registries.default,
|
||||
useLockfile: opts.useLockfile,
|
||||
useGitBranchLockfile: opts.useGitBranchLockfile,
|
||||
mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
|
||||
virtualStoreDir,
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ export default async function (
|
||||
lockfileDir: string
|
||||
registry: string
|
||||
useLockfile: boolean
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
virtualStoreDir: string
|
||||
}
|
||||
): Promise<{
|
||||
@@ -49,6 +51,8 @@ export default async function (
|
||||
const lockfileOpts = {
|
||||
ignoreIncompatible: opts.force || isCI,
|
||||
wantedVersion: LOCKFILE_VERSION,
|
||||
useGitBranchLockfile: opts.useGitBranchLockfile,
|
||||
mergeGitBranchLockfiles: opts.mergeGitBranchLockfiles,
|
||||
}
|
||||
const fileReads = [] as Array<Promise<Lockfile | undefined | null>>
|
||||
let lockfileHadConflicts: boolean = false
|
||||
@@ -73,7 +77,7 @@ export default async function (
|
||||
fileReads.push(readWantedLockfile(opts.lockfileDir, lockfileOpts))
|
||||
}
|
||||
} else {
|
||||
if (await existsWantedLockfile(opts.lockfileDir)) {
|
||||
if (await existsWantedLockfile(opts.lockfileDir, lockfileOpts)) {
|
||||
logger.warn({
|
||||
message: `A ${WANTED_LOCKFILE} file exists. The current configuration prohibits to read or write a lockfile`,
|
||||
prefix: opts.lockfileDir,
|
||||
|
||||
32
packages/git-utils/README.md
Normal file
32
packages/git-utils/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# @pnpm/git-utils
|
||||
|
||||
> Utilities for git
|
||||
|
||||
<!--@shields('npm')-->
|
||||
[](https://www.npmjs.com/package/@pnpm/git-utils)
|
||||
<!--/@-->
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
pnpm add @pnpm/git-utils
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
<!--@example('./example.js')-->
|
||||
```js
|
||||
'use strict'
|
||||
const { getCurrentBranchName } = require('@pnpm-utils').default
|
||||
|
||||
main()
|
||||
async function main() {
|
||||
const branchName = await getCurrentBranch();
|
||||
console.log(branchName)
|
||||
}
|
||||
```
|
||||
<!--/@-->
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
8
packages/git-utils/example.js
Normal file
8
packages/git-utils/example.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
const { getCurrentBranch } = require('@pnpm-utils').default
|
||||
|
||||
main()
|
||||
async function main() {
|
||||
const branchName = await getCurrentBranch();
|
||||
console.log(branchName)
|
||||
}
|
||||
1
packages/git-utils/jest.config.js
Normal file
1
packages/git-utils/jest.config.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('../../jest.config.js')
|
||||
46
packages/git-utils/package.json
Normal file
46
packages/git-utils/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@pnpm/git-utils",
|
||||
"version": "0.0.0",
|
||||
"description": "Utilities for git",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.6"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src/**/*.ts test/**/*.ts",
|
||||
"_test": "jest",
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"fix": "tslint -c tslint.json src/**/*.ts test/**/*.ts --fix",
|
||||
"compile-only": "tsc --build",
|
||||
"compile": "tsc --build && pnpm run lint --fix"
|
||||
},
|
||||
"repository": "https://github.com/pnpm/pnpm/blob/main/packages/git-utils",
|
||||
"keywords": [
|
||||
"pnpm7",
|
||||
"pnpm",
|
||||
"git",
|
||||
"npm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/main/packages/git-utils#readme",
|
||||
"dependencies": {
|
||||
"execa": "npm:safe-execa@^0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/git-utils": "workspace:0.0.0",
|
||||
"tempy": "^1.0.0"
|
||||
},
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
}
|
||||
}
|
||||
39
packages/git-utils/test/index.test.ts
Normal file
39
packages/git-utils/test/index.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import tempy from 'tempy'
|
||||
import execa from 'execa'
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { getCurrentBranch, isGitRepo, isWorkingTreeClean } from '@pnpm/git-utils'
|
||||
|
||||
test('isGitRepo', async () => {
|
||||
const tempDir = tempy.directory()
|
||||
process.chdir(tempDir)
|
||||
|
||||
await expect(isGitRepo()).resolves.toBe(false)
|
||||
|
||||
await execa('git', ['init'])
|
||||
|
||||
await expect(isGitRepo()).resolves.toBe(true)
|
||||
})
|
||||
|
||||
test('getCurrentBranch', async () => {
|
||||
const tempDir = tempy.directory()
|
||||
process.chdir(tempDir)
|
||||
|
||||
await execa('git', ['init'])
|
||||
await execa('git', ['checkout', '-b', 'foo'])
|
||||
|
||||
await expect(getCurrentBranch()).resolves.toBe('foo')
|
||||
})
|
||||
|
||||
test('isWorkingTreeClean', async () => {
|
||||
const tempDir = tempy.directory()
|
||||
process.chdir(tempDir)
|
||||
|
||||
await execa('git', ['init'])
|
||||
|
||||
await expect(isWorkingTreeClean()).resolves.toBe(true)
|
||||
|
||||
await fs.writeFile(path.join(tempDir, 'foo'), 'foo')
|
||||
|
||||
await expect(isWorkingTreeClean()).resolves.toBe(false)
|
||||
})
|
||||
13
packages/git-utils/tsconfig.json
Normal file
13
packages/git-utils/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../typings/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
]
|
||||
}
|
||||
8
packages/git-utils/tsconfig.lint.json
Normal file
8
packages/git-utils/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../typings/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -137,6 +137,7 @@ export interface HeadlessOptions {
|
||||
skipped: Set<string>
|
||||
enableModulesDir?: boolean
|
||||
nodeLinker?: 'isolated' | 'hoisted' | 'pnp'
|
||||
useGitBranchLockfile?: boolean
|
||||
}
|
||||
|
||||
export default async (opts: HeadlessOptions) => {
|
||||
@@ -146,7 +147,12 @@ export default async (opts: HeadlessOptions) => {
|
||||
}
|
||||
|
||||
const lockfileDir = opts.lockfileDir
|
||||
const wantedLockfile = opts.wantedLockfile ?? await readWantedLockfile(lockfileDir, { ignoreIncompatible: false })
|
||||
const wantedLockfile = opts.wantedLockfile ?? await readWantedLockfile(lockfileDir, {
|
||||
ignoreIncompatible: false,
|
||||
useGitBranchLockfile: opts.useGitBranchLockfile,
|
||||
// mergeGitBranchLockfiles is intentionally not supported in headless
|
||||
mergeGitBranchLockfiles: false,
|
||||
})
|
||||
|
||||
if (wantedLockfile == null) {
|
||||
throw new Error(`Headless installation requires a ${WANTED_LOCKFILE} file`)
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"dependencies": {
|
||||
"@pnpm/constants": "workspace:6.1.0",
|
||||
"@pnpm/error": "workspace:3.0.1",
|
||||
"@pnpm/git-utils": "workspace:0.0.0",
|
||||
"@pnpm/lockfile-types": "workspace:4.0.2",
|
||||
"@pnpm/merge-lockfile-changes": "workspace:3.0.2",
|
||||
"@pnpm/types": "workspace:8.1.0",
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { getWantedLockfileName } from './lockfileName'
|
||||
|
||||
export default async (pkgPath: string) => new Promise((resolve, reject) => {
|
||||
fs.access(path.join(pkgPath, WANTED_LOCKFILE), (err) => {
|
||||
if (err == null) {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
if (err.code === 'ENOENT') {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
reject(err)
|
||||
interface ExistsWantedLockfileOptions {
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
}
|
||||
|
||||
export default async (pkgPath: string, opts: ExistsWantedLockfileOptions = {
|
||||
useGitBranchLockfile: false,
|
||||
mergeGitBranchLockfiles: false,
|
||||
}) => {
|
||||
const wantedLockfile: string = await getWantedLockfileName(opts)
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.access(path.join(pkgPath, wantedLockfile), (err) => {
|
||||
if (err == null) {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
if (err.code === 'ENOENT') {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
18
packages/lockfile-file/src/gitBranchLockfile.ts
Normal file
18
packages/lockfile-file/src/gitBranchLockfile.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export async function getGitBranchLockfileNames (lockfileDir: string) {
|
||||
const files = await fs.readdir(lockfileDir)
|
||||
const gitBranchLockfileNames: string[] = files.filter(file => file.match(/^pnpm-lock.(?:.*).yaml$/))
|
||||
return gitBranchLockfileNames
|
||||
}
|
||||
|
||||
export async function cleanGitBranchLockfiles (lockfileDir: string) {
|
||||
const gitBranchLockfiles: string[] = await getGitBranchLockfileNames(lockfileDir)
|
||||
await Promise.all(
|
||||
gitBranchLockfiles.map(async file => {
|
||||
const filepath: string = path.join(lockfileDir, file)
|
||||
await fs.unlink(filepath)
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -15,3 +15,5 @@ export {
|
||||
writeCurrentLockfile,
|
||||
writeWantedLockfile,
|
||||
}
|
||||
|
||||
export { cleanGitBranchLockfiles } from './gitBranchLockfile'
|
||||
25
packages/lockfile-file/src/lockfileName.ts
Normal file
25
packages/lockfile-file/src/lockfileName.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { getCurrentBranch } from '@pnpm/git-utils'
|
||||
|
||||
export interface GetWantedLockfileNameOptions {
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
}
|
||||
|
||||
export async function getWantedLockfileName (opts: GetWantedLockfileNameOptions = { useGitBranchLockfile: false, mergeGitBranchLockfiles: false }) {
|
||||
if (opts.useGitBranchLockfile && !opts.mergeGitBranchLockfiles) {
|
||||
const currentBranchName = await getCurrentBranch()
|
||||
if (currentBranchName) {
|
||||
return WANTED_LOCKFILE.replace('.yaml', `.${stringifyBranchName(currentBranchName)}.yaml`)
|
||||
}
|
||||
}
|
||||
return WANTED_LOCKFILE
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Git branch name may contains slashes, which is not allowed in filenames
|
||||
* 2. Filesystem may be case-insensitive, so we need to convert branch name to lowercase
|
||||
*/
|
||||
function stringifyBranchName (branchName: string = '') {
|
||||
return branchName.replace(/[^a-zA-Z0-9-_.]/g, '!').toLowerCase()
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
WANTED_LOCKFILE,
|
||||
} from '@pnpm/constants'
|
||||
import PnpmError from '@pnpm/error'
|
||||
import mergeLockfileChanges from '@pnpm/merge-lockfile-changes'
|
||||
import { Lockfile } from '@pnpm/lockfile-types'
|
||||
import { DEPENDENCIES_FIELDS } from '@pnpm/types'
|
||||
import comverToSemver from 'comver-to-semver'
|
||||
@@ -15,6 +16,8 @@ import { LockfileBreakingChangeError } from './errors'
|
||||
import { autofixMergeConflicts, isDiff } from './gitMergeFile'
|
||||
import logger from './logger'
|
||||
import { LockfileFile } from './write'
|
||||
import { getWantedLockfileName } from './lockfileName'
|
||||
import { getGitBranchLockfileNames } from './gitBranchLockfile'
|
||||
|
||||
export async function readCurrentLockfile (
|
||||
virtualStoreDir: string,
|
||||
@@ -32,13 +35,17 @@ export async function readWantedLockfileAndAutofixConflicts (
|
||||
opts: {
|
||||
wantedVersion?: number
|
||||
ignoreIncompatible: boolean
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
}
|
||||
): Promise<{
|
||||
lockfile: Lockfile | null
|
||||
hadConflicts: boolean
|
||||
}> {
|
||||
const lockfilePath = path.join(pkgPath, WANTED_LOCKFILE)
|
||||
return _read(lockfilePath, pkgPath, { ...opts, autofixMergeConflicts: true })
|
||||
return _readWantedLockfile(pkgPath, {
|
||||
...opts,
|
||||
autofixMergeConflicts: true,
|
||||
})
|
||||
}
|
||||
|
||||
export async function readWantedLockfile (
|
||||
@@ -46,10 +53,11 @@ export async function readWantedLockfile (
|
||||
opts: {
|
||||
wantedVersion?: number
|
||||
ignoreIncompatible: boolean
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
}
|
||||
): Promise<Lockfile | null> {
|
||||
const lockfilePath = path.join(pkgPath, WANTED_LOCKFILE)
|
||||
return (await _read(lockfilePath, pkgPath, opts)).lockfile
|
||||
return (await _readWantedLockfile(pkgPath, opts)).lockfile
|
||||
}
|
||||
|
||||
async function _read (
|
||||
@@ -136,6 +144,83 @@ export function createLockfileObject (
|
||||
}
|
||||
}
|
||||
|
||||
async function _readWantedLockfile (
|
||||
pkgPath: string,
|
||||
opts: {
|
||||
wantedVersion?: number
|
||||
ignoreIncompatible: boolean
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
autofixMergeConflicts?: boolean
|
||||
}
|
||||
): Promise<{
|
||||
lockfile: Lockfile | null
|
||||
hadConflicts: boolean
|
||||
}> {
|
||||
const lockfileNames: string[] = [WANTED_LOCKFILE]
|
||||
if (opts.useGitBranchLockfile) {
|
||||
const gitBranchLockfileName: string = await getWantedLockfileName(opts)
|
||||
if (gitBranchLockfileName !== WANTED_LOCKFILE) {
|
||||
lockfileNames.unshift(gitBranchLockfileName)
|
||||
}
|
||||
}
|
||||
let result: { lockfile: Lockfile | null, hadConflicts: boolean } = { lockfile: null, hadConflicts: false }
|
||||
for (const lockfileName of lockfileNames) {
|
||||
result = await _read(path.join(pkgPath, lockfileName), pkgPath, { ...opts, autofixMergeConflicts: true })
|
||||
if (result.lockfile) {
|
||||
if (opts.mergeGitBranchLockfiles) {
|
||||
result.lockfile = await _mergeGitBranchLockfiles(result.lockfile, pkgPath, pkgPath, opts)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function _mergeGitBranchLockfiles (
|
||||
lockfile: Lockfile | null,
|
||||
lockfileDir: string,
|
||||
prefix: string,
|
||||
opts: {
|
||||
autofixMergeConflicts?: boolean
|
||||
wantedVersion?: number
|
||||
ignoreIncompatible: boolean
|
||||
}
|
||||
): Promise<Lockfile | null> {
|
||||
if (!lockfile) {
|
||||
return lockfile
|
||||
}
|
||||
const gitBranchLockfiles: Array<(Lockfile | null)> = (await _readGitBranchLockfiles(lockfileDir, prefix, opts)).map(({ lockfile }) => lockfile)
|
||||
|
||||
let mergedLockfile: Lockfile = lockfile
|
||||
|
||||
for (const gitBranchLockfile of gitBranchLockfiles) {
|
||||
if (!gitBranchLockfile) {
|
||||
continue
|
||||
}
|
||||
mergedLockfile = mergeLockfileChanges(mergedLockfile, gitBranchLockfile)
|
||||
}
|
||||
|
||||
return mergedLockfile
|
||||
}
|
||||
|
||||
async function _readGitBranchLockfiles (
|
||||
lockfileDir: string,
|
||||
prefix: string,
|
||||
opts: {
|
||||
autofixMergeConflicts?: boolean
|
||||
wantedVersion?: number
|
||||
ignoreIncompatible: boolean
|
||||
}
|
||||
): Promise<Array<{
|
||||
lockfile: Lockfile | null
|
||||
hadConflicts: boolean
|
||||
}>> {
|
||||
const files = await getGitBranchLockfileNames(lockfileDir)
|
||||
|
||||
return Promise.all(files.map((file) => _read(path.join(lockfileDir, file), prefix, opts)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts changes from the "forceSharedFormat" write option if necessary.
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,7 @@ import isEmpty from 'ramda/src/isEmpty'
|
||||
import writeFileAtomicCB from 'write-file-atomic'
|
||||
import logger from './logger'
|
||||
import { sortLockfileKeys } from './sortLockfileKeys'
|
||||
import { getWantedLockfileName } from './lockfileName'
|
||||
|
||||
async function writeFileAtomic (filename: string, data: string) {
|
||||
return new Promise<void>((resolve, reject) => writeFileAtomicCB(filename, data, {}, (err?: Error) => (err != null) ? reject(err) : resolve()))
|
||||
@@ -28,9 +29,12 @@ export async function writeWantedLockfile (
|
||||
wantedLockfile: Lockfile,
|
||||
opts?: {
|
||||
forceSharedFormat?: boolean
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
}
|
||||
) {
|
||||
return writeLockfile(WANTED_LOCKFILE, pkgPath, wantedLockfile, opts)
|
||||
const wantedLockfileName: string = await getWantedLockfileName(opts)
|
||||
return writeLockfile(wantedLockfileName, pkgPath, wantedLockfile, opts)
|
||||
}
|
||||
|
||||
export async function writeCurrentLockfile (
|
||||
@@ -142,9 +146,12 @@ export default async function writeLockfiles (
|
||||
wantedLockfileDir: string
|
||||
currentLockfile: Lockfile
|
||||
currentLockfileDir: string
|
||||
useGitBranchLockfile?: boolean
|
||||
mergeGitBranchLockfiles?: boolean
|
||||
}
|
||||
) {
|
||||
const wantedLockfilePath = path.join(opts.wantedLockfileDir, WANTED_LOCKFILE)
|
||||
const wantedLockfileName: string = await getWantedLockfileName(opts)
|
||||
const wantedLockfilePath = path.join(opts.wantedLockfileDir, wantedLockfileName)
|
||||
const currentLockfilePath = path.join(opts.currentLockfileDir, 'lock.yaml')
|
||||
|
||||
// empty lockfile is not saved
|
||||
|
||||
0
packages/lockfile-file/test/fixtures/6/otherFile.md
vendored
Normal file
0
packages/lockfile-file/test/fixtures/6/otherFile.md
vendored
Normal file
9
packages/lockfile-file/test/fixtures/6/pnpm-lock.branch.yaml
vendored
Normal file
9
packages/lockfile-file/test/fixtures/6/pnpm-lock.branch.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
is-positive: '2.0.0'
|
||||
|
||||
packages:
|
||||
/is-positive/2.0.0:
|
||||
resolution: {integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g='}
|
||||
|
||||
8
packages/lockfile-file/test/fixtures/6/pnpm-lock.yaml
generated
vendored
Normal file
8
packages/lockfile-file/test/fixtures/6/pnpm-lock.yaml
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
is-positive: '1.0.0'
|
||||
|
||||
packages:
|
||||
/is-positive/1.0.0:
|
||||
resolution: {integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g='}
|
||||
10
packages/lockfile-file/test/gitBranchLockfile.test.ts
Normal file
10
packages/lockfile-file/test/gitBranchLockfile.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import path from 'path'
|
||||
import { getGitBranchLockfileNames } from '../lib/gitBranchLockfile'
|
||||
|
||||
process.chdir(__dirname)
|
||||
|
||||
test('getGitBranchLockfileNames()', async () => {
|
||||
const lockfileDir: string = path.join('fixtures', '6')
|
||||
const gitBranchLockfileNames = await getGitBranchLockfileNames(lockfileDir)
|
||||
expect(gitBranchLockfileNames).toEqual(['pnpm-lock.branch.yaml'])
|
||||
})
|
||||
30
packages/lockfile-file/test/lockfileName.test.ts
Normal file
30
packages/lockfile-file/test/lockfileName.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { getCurrentBranch } from '@pnpm/git-utils'
|
||||
import { getWantedLockfileName } from '../lib/lockfileName'
|
||||
|
||||
jest.mock('@pnpm/git-utils', () => ({ getCurrentBranch: jest.fn() }))
|
||||
|
||||
describe('lockfileName', () => {
|
||||
afterEach(() => {
|
||||
getCurrentBranch['mockReset']()
|
||||
})
|
||||
|
||||
test('returns default lockfile name if useGitBranchLockfile is off', async () => {
|
||||
await expect(getWantedLockfileName()).resolves.toBe(WANTED_LOCKFILE)
|
||||
})
|
||||
|
||||
test('returns git branch lockfile name', async () => {
|
||||
getCurrentBranch['mockReturnValue']('main')
|
||||
await expect(getWantedLockfileName({ useGitBranchLockfile: true })).resolves.toBe('pnpm-lock.main.yaml')
|
||||
})
|
||||
|
||||
test('returns git branch lockfile name when git branch contains clashes', async () => {
|
||||
getCurrentBranch['mockReturnValue']('a/b/c')
|
||||
await expect(getWantedLockfileName({ useGitBranchLockfile: true })).resolves.toBe('pnpm-lock.a!b!c.yaml')
|
||||
})
|
||||
|
||||
test('returns git branch lockfile name when git branch contains uppercase', async () => {
|
||||
getCurrentBranch['mockReturnValue']('aBc')
|
||||
await expect(getWantedLockfileName({ useGitBranchLockfile: true })).resolves.toBe('pnpm-lock.abc.yaml')
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from 'path'
|
||||
import { getCurrentBranch } from '@pnpm/git-utils'
|
||||
import {
|
||||
existsWantedLockfile,
|
||||
readCurrentLockfile,
|
||||
@@ -8,6 +9,8 @@ import {
|
||||
} from '@pnpm/lockfile-file'
|
||||
import tempy from 'tempy'
|
||||
|
||||
jest.mock('@pnpm/git-utils', () => ({ getCurrentBranch: jest.fn() }))
|
||||
|
||||
process.chdir(__dirname)
|
||||
|
||||
test('readWantedLockfile()', async () => {
|
||||
@@ -190,3 +193,71 @@ test('existsWantedLockfile()', async () => {
|
||||
})
|
||||
expect(await existsWantedLockfile(projectPath)).toBe(true)
|
||||
})
|
||||
|
||||
test('readWantedLockfile() when useGitBranchLockfile', async () => {
|
||||
getCurrentBranch['mockReturnValue']('branch')
|
||||
const lockfile = await readWantedLockfile(path.join('fixtures', '6'), {
|
||||
ignoreIncompatible: false,
|
||||
})
|
||||
expect(lockfile?.importers).toEqual({
|
||||
'.': {
|
||||
specifiers: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(lockfile?.packages).toStrictEqual({
|
||||
'/is-positive/1.0.0': {
|
||||
resolution: {
|
||||
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const gitBranchLockfile = await readWantedLockfile(path.join('fixtures', '6'), {
|
||||
ignoreIncompatible: false,
|
||||
useGitBranchLockfile: true,
|
||||
})
|
||||
expect(gitBranchLockfile?.importers).toEqual({
|
||||
'.': {
|
||||
specifiers: {
|
||||
'is-positive': '2.0.0',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(gitBranchLockfile?.packages).toStrictEqual({
|
||||
'/is-positive/2.0.0': {
|
||||
resolution: {
|
||||
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('readWantedLockfile() when useGitBranchLockfile and mergeGitBranchLockfiles', async () => {
|
||||
getCurrentBranch['mockReturnValue']('branch')
|
||||
const lockfile = await readWantedLockfile(path.join('fixtures', '6'), {
|
||||
ignoreIncompatible: false,
|
||||
useGitBranchLockfile: true,
|
||||
mergeGitBranchLockfiles: true,
|
||||
})
|
||||
expect(lockfile?.importers).toEqual({
|
||||
'.': {
|
||||
specifiers: {
|
||||
'is-positive': '2.0.0',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(lockfile?.packages).toStrictEqual({
|
||||
'/is-positive/1.0.0': {
|
||||
resolution: {
|
||||
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
|
||||
},
|
||||
},
|
||||
'/is-positive/2.0.0': {
|
||||
resolution: {
|
||||
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,6 +8,9 @@ import {
|
||||
} from '@pnpm/lockfile-file'
|
||||
import tempy from 'tempy'
|
||||
import yaml from 'yaml-tag'
|
||||
import { getCurrentBranch } from '@pnpm/git-utils'
|
||||
|
||||
jest.mock('@pnpm/git-utils', () => ({ getCurrentBranch: jest.fn() }))
|
||||
|
||||
test('writeLockfiles()', async () => {
|
||||
const projectPath = tempy.directory()
|
||||
@@ -192,3 +195,39 @@ test('writeLockfiles() does not fail if the lockfile has undefined properties',
|
||||
wantedLockfileDir: projectPath,
|
||||
})
|
||||
})
|
||||
|
||||
test('writeLockfiles() when useGitBranchLockfile', async () => {
|
||||
const branchName: string = 'branch'
|
||||
getCurrentBranch['mockReturnValue'](branchName)
|
||||
const projectPath = tempy.directory()
|
||||
const wantedLockfile = {
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
foo: '1.0.0',
|
||||
},
|
||||
specifiers: {
|
||||
foo: '^1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
lockfileVersion: LOCKFILE_VERSION,
|
||||
packages: {
|
||||
'/foo/1.0.0': {
|
||||
resolution: {
|
||||
integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g=',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await writeLockfiles({
|
||||
currentLockfile: wantedLockfile,
|
||||
currentLockfileDir: projectPath,
|
||||
wantedLockfile,
|
||||
wantedLockfileDir: projectPath,
|
||||
useGitBranchLockfile: true,
|
||||
})
|
||||
expect(fs.existsSync(path.join(projectPath, WANTED_LOCKFILE))).toBeFalsy()
|
||||
expect(fs.existsSync(path.join(projectPath, `pnpm-lock.${branchName}.yaml`))).toBeTruthy()
|
||||
})
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
{
|
||||
"path": "../error"
|
||||
},
|
||||
{
|
||||
"path": "../git-utils"
|
||||
},
|
||||
{
|
||||
"path": "../lockfile-types"
|
||||
},
|
||||
|
||||
@@ -33,6 +33,8 @@ export function rcOptionsTypes () {
|
||||
'lockfile-directory',
|
||||
'lockfile-only',
|
||||
'lockfile',
|
||||
'merge-git-branch-lockfiles',
|
||||
'merge-git-branch-lockfiles-branch-pattern',
|
||||
'modules-dir',
|
||||
'network-concurrency',
|
||||
'node-linker',
|
||||
@@ -139,6 +141,10 @@ For options that may be used with `-r`, see "pnpm help recursive"',
|
||||
description: 'Fix broken lockfile entries automatically',
|
||||
name: '--fix-lockfile',
|
||||
},
|
||||
{
|
||||
description: 'Merge lockfiles were generated on git branch',
|
||||
name: '--merge-git-branch-lockfiles',
|
||||
},
|
||||
{
|
||||
description: 'The directory in which dependencies will be installed (instead of node_modules)',
|
||||
name: '--modules-dir <dir>',
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"@pnpm/config": "workspace:15.2.1",
|
||||
"@pnpm/error": "workspace:3.0.1",
|
||||
"@pnpm/exportable-manifest": "workspace:3.0.3",
|
||||
"@pnpm/git-utils": "workspace:0.0.0",
|
||||
"@pnpm/lifecycle": "workspace:13.0.4",
|
||||
"@pnpm/package-bins": "workspace:6.0.2",
|
||||
"@pnpm/pick-registry-for-package": "workspace:3.0.2",
|
||||
|
||||
@@ -6,6 +6,7 @@ import PnpmError from '@pnpm/error'
|
||||
import runLifecycleHooks, { RunLifecycleHookOptions } from '@pnpm/lifecycle'
|
||||
import runNpm from '@pnpm/run-npm'
|
||||
import { ProjectManifest } from '@pnpm/types'
|
||||
import { getCurrentBranch, isGitRepo, isRemoteHistoryClean, isWorkingTreeClean } from '@pnpm/git-utils'
|
||||
import { prompt } from 'enquirer'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import pick from 'ramda/src/pick'
|
||||
@@ -14,7 +15,6 @@ import renderHelp from 'render-help'
|
||||
import tempy from 'tempy'
|
||||
import * as pack from './pack'
|
||||
import recursivePublish, { PublishRecursiveOpts } from './recursivePublish'
|
||||
import { getCurrentBranch, isGitRepo, isRemoteHistoryClean, isWorkingTreeClean } from './gitChecks'
|
||||
|
||||
export function rcOptionsTypes () {
|
||||
return pick([
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
{
|
||||
"path": "../filter-workspace-packages"
|
||||
},
|
||||
{
|
||||
"path": "../git-utils"
|
||||
},
|
||||
{
|
||||
"path": "../lifecycle"
|
||||
},
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"prepublishOnly": "pnpm compile && npm cache clear --force && publish-packed --prune --npm-client=pnpm --dest=dist",
|
||||
"postpublish": "publish-packed",
|
||||
"_compile": "tsc --build",
|
||||
"compile": "tsc --build && pnpm run lint --fix && rimraf dist bin/nodes && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts && shx cp -r node_modules/ps-list/vendor dist/vendor && shx cp pnpmrc dist/pnpmrc"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
37
pnpm-lock.yaml
generated
37
pnpm-lock.yaml
generated
@@ -345,6 +345,8 @@ importers:
|
||||
'@pnpm/config': workspace:15.2.1
|
||||
'@pnpm/constants': workspace:6.1.0
|
||||
'@pnpm/error': workspace:3.0.1
|
||||
'@pnpm/git-utils': workspace:0.0.0
|
||||
'@pnpm/matcher': workspace:3.0.0
|
||||
'@pnpm/npm-conf': 1.0.4
|
||||
'@pnpm/pnpmfile': workspace:2.0.3
|
||||
'@pnpm/prepare': workspace:*
|
||||
@@ -364,6 +366,8 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/constants': link:../constants
|
||||
'@pnpm/error': link:../error
|
||||
'@pnpm/git-utils': link:../git-utils
|
||||
'@pnpm/matcher': link:../matcher
|
||||
'@pnpm/npm-conf': 1.0.4
|
||||
'@pnpm/pnpmfile': link:../pnpmfile
|
||||
'@pnpm/read-project-manifest': link:../read-project-manifest
|
||||
@@ -403,6 +407,7 @@ importers:
|
||||
'@pnpm/error': workspace:3.0.1
|
||||
'@pnpm/filter-lockfile': workspace:6.0.5
|
||||
'@pnpm/get-context': workspace:6.1.3
|
||||
'@pnpm/git-utils': workspace:0.0.0
|
||||
'@pnpm/graph-sequencer': 1.0.0
|
||||
'@pnpm/headless': workspace:18.1.11
|
||||
'@pnpm/hoist': workspace:6.1.3
|
||||
@@ -530,6 +535,7 @@ importers:
|
||||
'@pnpm/cafs': link:../cafs
|
||||
'@pnpm/client': link:../client
|
||||
'@pnpm/core': 'link:'
|
||||
'@pnpm/git-utils': link:../git-utils
|
||||
'@pnpm/logger': 4.0.0
|
||||
'@pnpm/package-store': link:../package-store
|
||||
'@pnpm/prepare': link:../../privatePackages/prepare
|
||||
@@ -1080,6 +1086,17 @@ importers:
|
||||
'@types/semver': 7.3.9
|
||||
is-windows: 1.0.2
|
||||
|
||||
packages/git-utils:
|
||||
specifiers:
|
||||
'@pnpm/git-utils': workspace:0.0.0
|
||||
execa: npm:safe-execa@^0.1.1
|
||||
tempy: ^1.0.0
|
||||
dependencies:
|
||||
execa: /safe-execa/0.1.1
|
||||
devDependencies:
|
||||
'@pnpm/git-utils': 'link:'
|
||||
tempy: 1.0.1
|
||||
|
||||
packages/graceful-fs:
|
||||
specifiers:
|
||||
'@pnpm/graceful-fs': workspace:2.0.0
|
||||
@@ -1379,6 +1396,7 @@ importers:
|
||||
specifiers:
|
||||
'@pnpm/constants': workspace:6.1.0
|
||||
'@pnpm/error': workspace:3.0.1
|
||||
'@pnpm/git-utils': workspace:0.0.0
|
||||
'@pnpm/lockfile-file': workspace:5.0.4
|
||||
'@pnpm/lockfile-types': workspace:4.0.2
|
||||
'@pnpm/logger': ^4.0.0
|
||||
@@ -1404,6 +1422,7 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/constants': link:../constants
|
||||
'@pnpm/error': link:../error
|
||||
'@pnpm/git-utils': link:../git-utils
|
||||
'@pnpm/lockfile-types': link:../lockfile-types
|
||||
'@pnpm/merge-lockfile-changes': link:../merge-lockfile-changes
|
||||
'@pnpm/types': link:../types
|
||||
@@ -2408,6 +2427,7 @@ importers:
|
||||
'@pnpm/error': workspace:3.0.1
|
||||
'@pnpm/exportable-manifest': workspace:3.0.3
|
||||
'@pnpm/filter-workspace-packages': workspace:5.0.12
|
||||
'@pnpm/git-utils': workspace:0.0.0
|
||||
'@pnpm/lifecycle': workspace:13.0.4
|
||||
'@pnpm/logger': ^4.0.0
|
||||
'@pnpm/package-bins': workspace:6.0.2
|
||||
@@ -2453,6 +2473,7 @@ importers:
|
||||
'@pnpm/config': link:../config
|
||||
'@pnpm/error': link:../error
|
||||
'@pnpm/exportable-manifest': link:../exportable-manifest
|
||||
'@pnpm/git-utils': link:../git-utils
|
||||
'@pnpm/lifecycle': link:../lifecycle
|
||||
'@pnpm/package-bins': link:../package-bins
|
||||
'@pnpm/pick-registry-for-package': link:../pick-registry-for-package
|
||||
@@ -8967,7 +8988,7 @@ packages:
|
||||
pkg-dir: 4.2.0
|
||||
|
||||
/findup/0.1.5:
|
||||
resolution: {integrity: sha512-Udxo3C9A6alt2GZ2MNsgnIvX7De0V3VGxeP/x98NSVgSlizcDHdmJza61LI7zJy4OEtSiJyE72s0/+tBl5/ZxA==}
|
||||
resolution: {integrity: sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=}
|
||||
engines: {node: '>=0.6'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@@ -9310,7 +9331,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/github-from-package/0.0.0:
|
||||
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||
resolution: {integrity: sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=}
|
||||
dev: true
|
||||
|
||||
/glob-parent/5.1.2:
|
||||
@@ -11185,7 +11206,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/manage-path/2.0.0:
|
||||
resolution: {integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==}
|
||||
resolution: {integrity: sha1-9M+EV7km7u4qg7FzUBQUvHbrlZc=}
|
||||
dev: true
|
||||
|
||||
/map-age-cleaner/0.1.3:
|
||||
@@ -11581,7 +11602,7 @@ packages:
|
||||
dev: true
|
||||
|
||||
/mute-stream/0.0.7:
|
||||
resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==}
|
||||
resolution: {integrity: sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=}
|
||||
dev: true
|
||||
|
||||
/mv/2.1.1:
|
||||
@@ -11996,7 +12017,7 @@ packages:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
/opt-cli/1.5.1:
|
||||
resolution: {integrity: sha512-iRFQBiQjXZ+LX/8pis04prUhS6FOYcJiZRouofN3rUJEB282b/e0s3jp9vT7aHgXY6TUpgPwu12f0i+qF40Kjw==}
|
||||
resolution: {integrity: sha1-BNtEexPJa5kusxaFJm9O0NlzbcI=}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
commander: 2.9.0
|
||||
@@ -13611,11 +13632,11 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/spawn-command/0.0.2:
|
||||
resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
|
||||
resolution: {integrity: sha1-lUThpDygRfhTGqwaSMspva5iM44=}
|
||||
dev: true
|
||||
|
||||
/spawn-command/0.0.2-1:
|
||||
resolution: {integrity: sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==}
|
||||
resolution: {integrity: sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=}
|
||||
dev: true
|
||||
|
||||
/spawndamnit/2.0.0:
|
||||
@@ -14091,7 +14112,7 @@ packages:
|
||||
next-tick: 1.1.0
|
||||
|
||||
/tiny-each-async/2.0.3:
|
||||
resolution: {integrity: sha512-5ROII7nElnAirvFn8g7H7MtpfV1daMcyfTGQwsn/x2VtyV+VPiO5CjReCJtWLvoKTDEDmZocf3cNPraiMnBXLA==}
|
||||
resolution: {integrity: sha1-jru/1tYpXxNwAD+7NxYq/loKUdE=}
|
||||
dev: false
|
||||
|
||||
/tinylogic/1.0.3:
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface Project {
|
||||
*
|
||||
* https://github.com/microsoft/TypeScript/pull/32695 might help with this.
|
||||
*/
|
||||
readLockfile: () => Promise<Required<RawLockfile>>
|
||||
readLockfile: (lockfileName?: string) => Promise<Required<RawLockfile>>
|
||||
writePackageJson: (pkgJson: object) => Promise<void>
|
||||
}
|
||||
|
||||
@@ -143,9 +143,9 @@ export default (projectPath: string, encodedRegistryName?: string): Project => {
|
||||
}
|
||||
},
|
||||
readModulesManifest: async () => readModules(modules),
|
||||
async readLockfile () {
|
||||
async readLockfile (lockfileName: string = WANTED_LOCKFILE) {
|
||||
try {
|
||||
return await readYamlFile(path.join(projectPath, WANTED_LOCKFILE)) // eslint-disable-line
|
||||
return await readYamlFile(path.join(projectPath, lockfileName)) // eslint-disable-line
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
if (err.code === 'ENOENT') return null!
|
||||
throw err
|
||||
|
||||
Reference in New Issue
Block a user