diff --git a/.changeset/silent-swans-teach.md b/.changeset/silent-swans-teach.md new file mode 100644 index 00000000000..d3709a6925c --- /dev/null +++ b/.changeset/silent-swans-teach.md @@ -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. diff --git a/fixtures/with-non-package-dep/pnpm-lock.yaml b/fixtures/with-non-package-dep/pnpm-lock.yaml index ecd037e6608..57771e8883b 100644 --- a/fixtures/with-non-package-dep/pnpm-lock.yaml +++ b/fixtures/with-non-package-dep/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: 5.3 +lockfileVersion: 5.4 specifiers: camelcase: denolib/camelcase#aeb6b15f9c9957c8fa56f9731e914c4d8a6d2f2b diff --git a/packages/config/package.json b/packages/config/package.json index 3152524d4c6..5b244304d23 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -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", diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index a3d844e2f83..87e0b1c6e36 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -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 diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index d5f13160df4..1c256e4a154 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -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']) { diff --git a/packages/config/test/index.ts b/packages/config/test/index.ts index 36c09691326..fb6f88052b0 100644 --- a/packages/config/test/index.ts +++ b/packages/config/test/index.ts @@ -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) + } +}) diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json index 9660d8adfdb..7a6e0ef56be 100644 --- a/packages/config/tsconfig.json +++ b/packages/config/tsconfig.json @@ -18,6 +18,12 @@ { "path": "../error" }, + { + "path": "../git-utils" + }, + { + "path": "../matcher" + }, { "path": "../pnpmfile" }, diff --git a/packages/core/package.json b/packages/core/package.json index 790e06e9667..56fd8bc6d24 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -71,6 +71,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:*", diff --git a/packages/core/src/getPeerDependencyIssues.ts b/packages/core/src/getPeerDependencyIssues.ts index 9b6bde7fd5d..dc6ceaf299d 100644 --- a/packages/core/src/getPeerDependencyIssues.ts +++ b/packages/core/src/getPeerDependencyIssues.ts @@ -16,6 +16,7 @@ export type ListMissingPeersOptions = Partial | 'preferWorkspacePackages' | 'saveWorkspaceProtocol' | 'storeController' +| 'useGitBranchLockfile' | 'workspacePackages' > & Pick diff --git a/packages/core/src/install/extendInstallOptions.ts b/packages/core/src/install/extendInstallOptions.ts index 18c316df14d..9463f0df485 100644 --- a/packages/core/src/install/extendInstallOptions.ts +++ b/packages/core/src/install/extendInstallOptions.ts @@ -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: {}, diff --git a/packages/core/src/install/index.ts b/packages/core/src/install/index.ts index dad5eac8595..1e548b18cf4 100644 --- a/packages/core/src/install/index.ts +++ b/packages/core/src/install/index.ts @@ -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 { @@ -781,7 +786,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, diff --git a/packages/core/src/link/index.ts b/packages/core/src/link/index.ts index dfef26c1b72..6a5ae22b6b8 100644 --- a/packages/core/src/link/index.ts +++ b/packages/core/src/link/index.ts @@ -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, diff --git a/packages/core/src/link/options.ts b/packages/core/src/link/options.ts index 624f827fd39..201aed0939c 100644 --- a/packages/core/src/link/options.ts +++ b/packages/core/src/link/options.ts @@ -29,6 +29,9 @@ interface StrictLinkOptions { publicHoistPattern: string[] | undefined forcePublicHoistPattern: boolean + + useGitBranchLockfile: boolean + mergeGitBranchLockfiles: boolean } export type LinkOptions = diff --git a/packages/core/test/install/gitBranchLockfile.test.ts b/packages/core/test/install/gitBranchLockfile.test.ts new file mode 100644 index 00000000000..ba546f29f86 --- /dev/null +++ b/packages/core/test/install/gitBranchLockfile.test.ts @@ -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) +}) diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 0ad13abbdaa..25256f27af9 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -51,6 +51,9 @@ { "path": "../get-context" }, + { + "path": "../git-utils" + }, { "path": "../headless" }, diff --git a/packages/get-context/src/index.ts b/packages/get-context/src/index.ts index a277974bda0..fbe8dec3906 100644 --- a/packages/get-context/src/index.ts +++ b/packages/get-context/src/index.ts @@ -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 ( 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, }), } diff --git a/packages/get-context/src/readLockfiles.ts b/packages/get-context/src/readLockfiles.ts index 6ce12cfba03..f108e138eca 100644 --- a/packages/get-context/src/readLockfiles.ts +++ b/packages/get-context/src/readLockfiles.ts @@ -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> 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, diff --git a/packages/git-utils/README.md b/packages/git-utils/README.md new file mode 100644 index 00000000000..7ccb1016902 --- /dev/null +++ b/packages/git-utils/README.md @@ -0,0 +1,32 @@ +# @pnpm/git-utils + +> Utilities for git + + +[![npm version](https://img.shields.io/npm/v/@pnpm/git-utils.svg)](https://www.npmjs.com/package/@pnpm/git-utils) + + +## Installation + +``` +pnpm add @pnpm/git-utils +``` + +## Usage + + +```js +'use strict' +const { getCurrentBranchName } = require('@pnpm-utils').default + +main() +async function main() { + const branchName = await getCurrentBranch(); + console.log(branchName) +} +``` + + +# License + +MIT \ No newline at end of file diff --git a/packages/git-utils/example.js b/packages/git-utils/example.js new file mode 100644 index 00000000000..9b5ad529457 --- /dev/null +++ b/packages/git-utils/example.js @@ -0,0 +1,8 @@ +'use strict' +const { getCurrentBranch } = require('@pnpm-utils').default + +main() +async function main() { + const branchName = await getCurrentBranch(); + console.log(branchName) +} diff --git a/packages/git-utils/jest.config.js b/packages/git-utils/jest.config.js new file mode 100644 index 00000000000..f697d831691 --- /dev/null +++ b/packages/git-utils/jest.config.js @@ -0,0 +1 @@ +module.exports = require('../../jest.config.js') diff --git a/packages/git-utils/package.json b/packages/git-utils/package.json new file mode 100644 index 00000000000..e75932675ef --- /dev/null +++ b/packages/git-utils/package.json @@ -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" + } +} diff --git a/packages/plugin-commands-publishing/src/gitChecks.ts b/packages/git-utils/src/index.ts similarity index 100% rename from packages/plugin-commands-publishing/src/gitChecks.ts rename to packages/git-utils/src/index.ts diff --git a/packages/git-utils/test/index.test.ts b/packages/git-utils/test/index.test.ts new file mode 100644 index 00000000000..dba8c3b52ef --- /dev/null +++ b/packages/git-utils/test/index.test.ts @@ -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) +}) \ No newline at end of file diff --git a/packages/git-utils/tsconfig.json b/packages/git-utils/tsconfig.json new file mode 100644 index 00000000000..f798b65d3a5 --- /dev/null +++ b/packages/git-utils/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@pnpm/tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts", + "../../typings/**/*.d.ts" + ], + "references": [ + ] +} diff --git a/packages/git-utils/tsconfig.lint.json b/packages/git-utils/tsconfig.lint.json new file mode 100644 index 00000000000..0dc5add6b7b --- /dev/null +++ b/packages/git-utils/tsconfig.lint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "../../typings/**/*.d.ts" + ] +} diff --git a/packages/headless/src/index.ts b/packages/headless/src/index.ts index d11e87fdf79..0b6728c7f5c 100644 --- a/packages/headless/src/index.ts +++ b/packages/headless/src/index.ts @@ -137,6 +137,7 @@ export interface HeadlessOptions { skipped: Set 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`) diff --git a/packages/lockfile-file/package.json b/packages/lockfile-file/package.json index ec8163547ff..2a839242149 100644 --- a/packages/lockfile-file/package.json +++ b/packages/lockfile-file/package.json @@ -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", diff --git a/packages/lockfile-file/src/existsWantedLockfile.ts b/packages/lockfile-file/src/existsWantedLockfile.ts index c7625fc7d68..8757b6cfe0f 100644 --- a/packages/lockfile-file/src/existsWantedLockfile.ts +++ b/packages/lockfile-file/src/existsWantedLockfile.ts @@ -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) + }) }) -}) +} diff --git a/packages/lockfile-file/src/gitBranchLockfile.ts b/packages/lockfile-file/src/gitBranchLockfile.ts new file mode 100644 index 00000000000..e30d7a3724e --- /dev/null +++ b/packages/lockfile-file/src/gitBranchLockfile.ts @@ -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) + }) + ) +} diff --git a/packages/lockfile-file/src/index.ts b/packages/lockfile-file/src/index.ts index 0587041af04..9387bfd04f3 100644 --- a/packages/lockfile-file/src/index.ts +++ b/packages/lockfile-file/src/index.ts @@ -15,3 +15,5 @@ export { writeCurrentLockfile, writeWantedLockfile, } + +export { cleanGitBranchLockfiles } from './gitBranchLockfile' \ No newline at end of file diff --git a/packages/lockfile-file/src/lockfileName.ts b/packages/lockfile-file/src/lockfileName.ts new file mode 100644 index 00000000000..0d8f7691a6c --- /dev/null +++ b/packages/lockfile-file/src/lockfileName.ts @@ -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() +} \ No newline at end of file diff --git a/packages/lockfile-file/src/read.ts b/packages/lockfile-file/src/read.ts index a9c552f14ea..fca6a0f479b 100644 --- a/packages/lockfile-file/src/read.ts +++ b/packages/lockfile-file/src/read.ts @@ -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 { - 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 { + 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> { + 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. */ diff --git a/packages/lockfile-file/src/write.ts b/packages/lockfile-file/src/write.ts index ec7b662c60a..36ce35289fe 100644 --- a/packages/lockfile-file/src/write.ts +++ b/packages/lockfile-file/src/write.ts @@ -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((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 diff --git a/packages/lockfile-file/test/fixtures/6/otherFile.md b/packages/lockfile-file/test/fixtures/6/otherFile.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/lockfile-file/test/fixtures/6/pnpm-lock.branch.yaml b/packages/lockfile-file/test/fixtures/6/pnpm-lock.branch.yaml new file mode 100644 index 00000000000..8f8c945c11d --- /dev/null +++ b/packages/lockfile-file/test/fixtures/6/pnpm-lock.branch.yaml @@ -0,0 +1,9 @@ +lockfileVersion: 5.3 + +specifiers: + is-positive: '2.0.0' + +packages: + /is-positive/2.0.0: + resolution: {integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g='} + diff --git a/packages/lockfile-file/test/fixtures/6/pnpm-lock.yaml b/packages/lockfile-file/test/fixtures/6/pnpm-lock.yaml new file mode 100644 index 00000000000..35be8a9f8ad --- /dev/null +++ b/packages/lockfile-file/test/fixtures/6/pnpm-lock.yaml @@ -0,0 +1,8 @@ +lockfileVersion: 5.3 + +specifiers: + is-positive: '1.0.0' + +packages: + /is-positive/1.0.0: + resolution: {integrity: 'sha1-ChbBDewTLAqLCzb793Fo5VDvg/g='} \ No newline at end of file diff --git a/packages/lockfile-file/test/gitBranchLockfile.test.ts b/packages/lockfile-file/test/gitBranchLockfile.test.ts new file mode 100644 index 00000000000..9b026f760bf --- /dev/null +++ b/packages/lockfile-file/test/gitBranchLockfile.test.ts @@ -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']) +}) diff --git a/packages/lockfile-file/test/lockfileName.test.ts b/packages/lockfile-file/test/lockfileName.test.ts new file mode 100644 index 00000000000..1f4f8602d98 --- /dev/null +++ b/packages/lockfile-file/test/lockfileName.test.ts @@ -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') + }) +}) diff --git a/packages/lockfile-file/test/read.test.ts b/packages/lockfile-file/test/read.test.ts index 228e762e1a3..1becc08c588 100644 --- a/packages/lockfile-file/test/read.test.ts +++ b/packages/lockfile-file/test/read.test.ts @@ -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=', + }, + }, + }) +}) diff --git a/packages/lockfile-file/test/write.test.ts b/packages/lockfile-file/test/write.test.ts index b25fcda23aa..69a74d8d216 100644 --- a/packages/lockfile-file/test/write.test.ts +++ b/packages/lockfile-file/test/write.test.ts @@ -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() +}) diff --git a/packages/lockfile-file/tsconfig.json b/packages/lockfile-file/tsconfig.json index 54d74c76715..2099aac3f79 100644 --- a/packages/lockfile-file/tsconfig.json +++ b/packages/lockfile-file/tsconfig.json @@ -15,6 +15,9 @@ { "path": "../error" }, + { + "path": "../git-utils" + }, { "path": "../lockfile-types" }, diff --git a/packages/plugin-commands-installation/src/install.ts b/packages/plugin-commands-installation/src/install.ts index 7f9609022e1..d02ea1a3939 100644 --- a/packages/plugin-commands-installation/src/install.ts +++ b/packages/plugin-commands-installation/src/install.ts @@ -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 ', diff --git a/packages/plugin-commands-publishing/package.json b/packages/plugin-commands-publishing/package.json index 04b499255b6..c37cb0d4fc4 100644 --- a/packages/plugin-commands-publishing/package.json +++ b/packages/plugin-commands-publishing/package.json @@ -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", diff --git a/packages/plugin-commands-publishing/src/publish.ts b/packages/plugin-commands-publishing/src/publish.ts index 94422f60748..1dc4cd58aa9 100644 --- a/packages/plugin-commands-publishing/src/publish.ts +++ b/packages/plugin-commands-publishing/src/publish.ts @@ -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([ diff --git a/packages/plugin-commands-publishing/tsconfig.json b/packages/plugin-commands-publishing/tsconfig.json index cba3c3334e2..c061f19b6cd 100644 --- a/packages/plugin-commands-publishing/tsconfig.json +++ b/packages/plugin-commands-publishing/tsconfig.json @@ -30,6 +30,9 @@ { "path": "../filter-workspace-packages" }, + { + "path": "../git-utils" + }, { "path": "../lifecycle" }, diff --git a/packages/pnpm/package.json b/packages/pnpm/package.json index 43cb8adef6f..3dc1c4bd8ce 100644 --- a/packages/pnpm/package.json +++ b/packages/pnpm/package.json @@ -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": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28f931e1332..e02da78da83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 @@ -528,6 +533,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 @@ -1078,6 +1084,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 @@ -1377,6 +1394,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 @@ -1402,6 +1420,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 @@ -2406,6 +2425,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 @@ -2451,6 +2471,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 @@ -8965,7 +8986,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: @@ -9308,7 +9329,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: @@ -11183,7 +11204,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: @@ -11579,7 +11600,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: @@ -11994,7 +12015,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 @@ -13609,11 +13630,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: @@ -14089,7 +14110,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: diff --git a/privatePackages/assert-project/src/index.ts b/privatePackages/assert-project/src/index.ts index 92e0b8ada0f..1958b4442ca 100644 --- a/privatePackages/assert-project/src/index.ts +++ b/privatePackages/assert-project/src/index.ts @@ -39,7 +39,7 @@ export interface Project { * * https://github.com/microsoft/TypeScript/pull/32695 might help with this. */ - readLockfile: () => Promise> + readLockfile: (lockfileName?: string) => Promise> writePackageJson: (pkgJson: object) => Promise } @@ -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