mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat: config update hook (#9325)
This commit is contained in:
20
.changeset/silly-deer-fly.md
Normal file
20
.changeset/silly-deer-fly.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": major
|
||||
"@pnpm/pnpmfile": minor
|
||||
"@pnpm/cli-utils": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
**Experimental.** A new hook is supported for updating configuration settings. The hook can be provided via `.pnpmfile.cjs`. For example:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
hooks: {
|
||||
updateConfig: (config) => ({
|
||||
...config,
|
||||
nodeLinker: 'hoisted',
|
||||
}),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
5
.changeset/wild-kings-hang.md
Normal file
5
.changeset/wild-kings-hang.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/config.deps-installer": major
|
||||
---
|
||||
|
||||
Initial release.
|
||||
@@ -245,6 +245,7 @@ async function updateManifest (workspaceDir: string, manifest: ProjectManifest,
|
||||
scripts = { ...manifest.scripts }
|
||||
break
|
||||
case '@pnpm/exec.build-commands':
|
||||
case '@pnpm/config.deps-installer':
|
||||
case '@pnpm/headless':
|
||||
case '@pnpm/outdated':
|
||||
case '@pnpm/package-requester':
|
||||
|
||||
@@ -32,11 +32,14 @@
|
||||
"dependencies": {
|
||||
"@pnpm/cli-meta": "workspace:*",
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/config.deps-installer": "workspace:*",
|
||||
"@pnpm/default-reporter": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/manifest-utils": "workspace:*",
|
||||
"@pnpm/package-is-installable": "workspace:*",
|
||||
"@pnpm/pnpmfile": "workspace:*",
|
||||
"@pnpm/read-project-manifest": "workspace:*",
|
||||
"@pnpm/store-connection-manager": "workspace:*",
|
||||
"@pnpm/types": "workspace:*",
|
||||
"chalk": "catalog:",
|
||||
"load-json-file": "catalog:"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { packageManager } from '@pnpm/cli-meta'
|
||||
import { getConfig as _getConfig, type CliOptions, type Config } from '@pnpm/config'
|
||||
import { formatWarn } from '@pnpm/default-reporter'
|
||||
import { createOrConnectStoreController } from '@pnpm/store-connection-manager'
|
||||
import { installConfigDeps } from '@pnpm/config.deps-installer'
|
||||
import { requireHooks } from '@pnpm/pnpmfile'
|
||||
|
||||
export async function getConfig (
|
||||
cliOptions: CliOptions,
|
||||
@@ -13,7 +16,7 @@ export async function getConfig (
|
||||
ignoreNonAuthSettingsFromLocal?: boolean
|
||||
}
|
||||
): Promise<Config> {
|
||||
const { config, warnings } = await _getConfig({
|
||||
let { config, warnings } = await _getConfig({
|
||||
cliOptions,
|
||||
globalDirShouldAllowWrite: opts.globalDirShouldAllowWrite,
|
||||
packageManager,
|
||||
@@ -23,6 +26,21 @@ export async function getConfig (
|
||||
ignoreNonAuthSettingsFromLocal: opts.ignoreNonAuthSettingsFromLocal,
|
||||
})
|
||||
config.cliOptions = cliOptions
|
||||
if (config.configDependencies) {
|
||||
const store = await createOrConnectStoreController(config)
|
||||
await installConfigDeps(config.configDependencies, {
|
||||
registries: config.registries,
|
||||
rootDir: config.lockfileDir ?? config.rootProjectManifestDir,
|
||||
store: store.ctrl,
|
||||
})
|
||||
}
|
||||
if (!config.ignorePnpmfile) {
|
||||
config.hooks = requireHooks(config.lockfileDir ?? config.dir, config)
|
||||
if (config.hooks?.updateConfig) {
|
||||
const updateConfigResult = config.hooks.updateConfig(config)
|
||||
config = updateConfigResult instanceof Promise ? await updateConfigResult : updateConfigResult
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.excludeReporter) {
|
||||
delete config.reporter // This is a silly workaround because @pnpm/core expects a function as opts.reporter
|
||||
|
||||
@@ -15,9 +15,15 @@
|
||||
{
|
||||
"path": "../../config/config"
|
||||
},
|
||||
{
|
||||
"path": "../../config/deps-installer"
|
||||
},
|
||||
{
|
||||
"path": "../../config/package-is-installable"
|
||||
},
|
||||
{
|
||||
"path": "../../hooks/pnpmfile"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
@@ -33,6 +39,9 @@
|
||||
{
|
||||
"path": "../../pkg-manifest/read-project-manifest"
|
||||
},
|
||||
{
|
||||
"path": "../../store/store-connection-manager"
|
||||
},
|
||||
{
|
||||
"path": "../cli-meta"
|
||||
},
|
||||
|
||||
17
config/deps-installer/README.md
Normal file
17
config/deps-installer/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# @pnpm/config.deps-installer
|
||||
|
||||
> Installer for configurational dependencies
|
||||
|
||||
<!--@shields('npm')-->
|
||||
[](https://www.npmjs.com/package/@pnpm/config.deps-installer)
|
||||
<!--/@-->
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @pnpm/config.deps-installer
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
58
config/deps-installer/package.json
Normal file
58
config/deps-installer/package.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@pnpm/config.deps-installer",
|
||||
"version": "1000.0.0-0",
|
||||
"description": "Installer for configurational dependencies",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
"pnpm10",
|
||||
"config"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"repository": "https://github.com/pnpm/pnpm/blob/main/config/deps-installer",
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/main/config/deps-installer#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"test-with-preview": "ts-node test",
|
||||
"_test": "jest",
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"start": "tsc --watch",
|
||||
"compile": "tsc --build && pnpm run lint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/package-store": "workspace:*",
|
||||
"@pnpm/pick-registry-for-package": "workspace:*",
|
||||
"@pnpm/read-modules-dir": "workspace:*",
|
||||
"@pnpm/read-package-json": "workspace:*",
|
||||
"@pnpm/types": "workspace:*",
|
||||
"@zkochan/rimraf": "catalog:",
|
||||
"get-npm-tarball-url": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/config.deps-installer": "workspace:*",
|
||||
"@pnpm/prepare": "workspace:*",
|
||||
"@pnpm/registry-mock": "catalog:",
|
||||
"@pnpm/testing.temp-store": "workspace:*",
|
||||
"load-json-file": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@pnpm/jest-config/with-registry"
|
||||
}
|
||||
}
|
||||
104
config/deps-installer/test/installConfigDeps.ts
Normal file
104
config/deps-installer/test/installConfigDeps.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import fs from 'fs'
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import { getIntegrity, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
|
||||
import { createTempStore } from '@pnpm/testing.temp-store'
|
||||
import { installConfigDeps } from '@pnpm/config.deps-installer'
|
||||
import { sync as loadJsonFile } from 'load-json-file'
|
||||
|
||||
const registry = `http://localhost:${REGISTRY_MOCK_PORT}/`
|
||||
|
||||
test('configuration dependency is installed', async () => {
|
||||
prepareEmpty()
|
||||
const { storeController } = createTempStore()
|
||||
|
||||
let configDeps: Record<string, string> = {
|
||||
'@pnpm.e2e/foo': `100.0.0+${getIntegrity('@pnpm.e2e/foo', '100.0.0')}`,
|
||||
}
|
||||
await installConfigDeps(configDeps, {
|
||||
registries: {
|
||||
default: registry,
|
||||
},
|
||||
rootDir: process.cwd(),
|
||||
store: storeController,
|
||||
})
|
||||
|
||||
{
|
||||
const configDepManifest = loadJsonFile<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
||||
expect(configDepManifest.name).toBe('@pnpm.e2e/foo')
|
||||
expect(configDepManifest.version).toBe('100.0.0')
|
||||
}
|
||||
|
||||
// Dependency is updated
|
||||
configDeps!['@pnpm.e2e/foo'] = `100.1.0+${getIntegrity('@pnpm.e2e/foo', '100.1.0')}`
|
||||
|
||||
await installConfigDeps(configDeps, {
|
||||
registries: {
|
||||
default: registry,
|
||||
},
|
||||
rootDir: process.cwd(),
|
||||
store: storeController,
|
||||
})
|
||||
|
||||
{
|
||||
const configDepManifest = loadJsonFile<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
||||
expect(configDepManifest.name).toBe('@pnpm.e2e/foo')
|
||||
expect(configDepManifest.version).toBe('100.1.0')
|
||||
}
|
||||
|
||||
// Dependency is removed
|
||||
configDeps! = {}
|
||||
|
||||
await installConfigDeps(configDeps, {
|
||||
registries: {
|
||||
default: registry,
|
||||
},
|
||||
rootDir: process.cwd(),
|
||||
store: storeController,
|
||||
})
|
||||
|
||||
expect(fs.existsSync('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')).toBeFalsy()
|
||||
})
|
||||
|
||||
test('installation fails if the checksum of the config dependency is invalid', async () => {
|
||||
prepareEmpty()
|
||||
const { storeController } = createTempStore({
|
||||
clientOptions: {
|
||||
retry: {
|
||||
retries: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const configDeps: Record<string, string> = {
|
||||
'@pnpm.e2e/foo': '100.0.0+sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==',
|
||||
}
|
||||
await expect(installConfigDeps(configDeps, {
|
||||
registries: {
|
||||
default: registry,
|
||||
},
|
||||
rootDir: process.cwd(),
|
||||
store: storeController,
|
||||
})).rejects.toThrow('Got unexpected checksum for')
|
||||
})
|
||||
|
||||
test('installation fails if the config dependency does not have a checksum', async () => {
|
||||
prepareEmpty()
|
||||
const { storeController } = createTempStore({
|
||||
clientOptions: {
|
||||
retry: {
|
||||
retries: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const configDeps: Record<string, string> = {
|
||||
'@pnpm.e2e/foo': '100.0.0',
|
||||
}
|
||||
await expect(installConfigDeps(configDeps, {
|
||||
registries: {
|
||||
default: registry,
|
||||
},
|
||||
rootDir: process.cwd(),
|
||||
store: storeController,
|
||||
})).rejects.toThrow("doesn't have an integrity checksum")
|
||||
})
|
||||
17
config/deps-installer/test/tsconfig.json
Normal file
17
config/deps-installer/test/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"outDir": "../test.lib",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
}
|
||||
37
config/deps-installer/tsconfig.json
Normal file
37
config/deps-installer/tsconfig.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../__utils__/prepare"
|
||||
},
|
||||
{
|
||||
"path": "../../fs/read-modules-dir"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/types"
|
||||
},
|
||||
{
|
||||
"path": "../../pkg-manifest/read-package-json"
|
||||
},
|
||||
{
|
||||
"path": "../../store/package-store"
|
||||
},
|
||||
{
|
||||
"path": "../../testing/temp-store"
|
||||
},
|
||||
{
|
||||
"path": "../pick-registry-for-package"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
config/deps-installer/tsconfig.lint.json
Normal file
8
config/deps-installer/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -16,4 +16,6 @@ export interface Hooks {
|
||||
filterLog?: (log: Log) => boolean
|
||||
importPackage?: ImportIndexedPackageAsync
|
||||
fetchers?: CustomFetchers
|
||||
// eslint-disable-next-line
|
||||
updateConfig?: (config: any) => any
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PreResolutionHookContext, PreResolutionHookLogger } from '@pnpm/hooks.types'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { hookLogger } from '@pnpm/core-loggers'
|
||||
import { createHashFromFile } from '@pnpm/crypto.hash'
|
||||
import pathAbsolute from 'path-absolute'
|
||||
@@ -20,6 +21,7 @@ export interface CookedHooks {
|
||||
preResolution?: Cook<Required<Hooks>['preResolution']>
|
||||
afterAllResolved?: Array<Cook<Required<Hooks>['afterAllResolved']>>
|
||||
filterLog?: Array<Cook<Required<Hooks>['filterLog']>>
|
||||
updateConfig?: Hooks['updateConfig']
|
||||
importPackage?: ImportIndexedPackageAsync
|
||||
fetchers?: CustomFetchers
|
||||
calculatePnpmfileChecksum?: () => Promise<string | undefined>
|
||||
@@ -79,6 +81,16 @@ export function requireHooks (
|
||||
: undefined
|
||||
|
||||
cookedHooks.fetchers = globalHooks.fetchers
|
||||
if (hooks.updateConfig != null) {
|
||||
const updateConfig = hooks.updateConfig
|
||||
cookedHooks.updateConfig = (config) => {
|
||||
const updatedConfig = updateConfig(config)
|
||||
if (updatedConfig == null) {
|
||||
throw new PnpmError('CONFIG_IS_UNDEFINED', 'The updateConfig hook returned undefined')
|
||||
}
|
||||
return updatedConfig
|
||||
}
|
||||
}
|
||||
|
||||
return cookedHooks
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
hooks: {
|
||||
updateConfig: () => undefined,
|
||||
},
|
||||
}
|
||||
@@ -62,3 +62,9 @@ test('calculatePnpmfileChecksum is undefined if pnpmfile even when it exports un
|
||||
const hooks = requireHooks(__dirname, { pnpmfile })
|
||||
expect(hooks.calculatePnpmfileChecksum).toBeUndefined()
|
||||
})
|
||||
|
||||
test('updateConfig throws an error if it returns undefined', async () => {
|
||||
const pnpmfile = path.join(__dirname, '__fixtures__/updateConfigReturnsUndefined.js')
|
||||
const hooks = requireHooks(__dirname, { pnpmfile })
|
||||
expect(() => hooks.updateConfig!({})).toThrow('The updateConfig hook returned undefined')
|
||||
})
|
||||
|
||||
@@ -12,7 +12,6 @@ import { filterDependenciesByType } from '@pnpm/manifest-utils'
|
||||
import { findWorkspacePackages } from '@pnpm/workspace.find-packages'
|
||||
import { type LockfileObject } from '@pnpm/lockfile.types'
|
||||
import { rebuildProjects } from '@pnpm/plugin-commands-rebuild'
|
||||
import { requireHooks } from '@pnpm/pnpmfile'
|
||||
import { createOrConnectStoreController, type CreateStoreControllerOptions } from '@pnpm/store-connection-manager'
|
||||
import { type IncludedDependencies, type Project, type ProjectsGraph, type ProjectRootDir, type PrepareExecutionEnv } from '@pnpm/types'
|
||||
import {
|
||||
@@ -40,7 +39,6 @@ import {
|
||||
recursive,
|
||||
} from './recursive'
|
||||
import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies'
|
||||
import { installConfigDeps } from './installConfigDeps'
|
||||
|
||||
const OVERWRITE_UPDATE_OPTIONS = {
|
||||
allowNew: true,
|
||||
@@ -172,20 +170,7 @@ when running add/update with the --workspace option')
|
||||
// @ts-expect-error
|
||||
opts['preserveWorkspaceProtocol'] = !opts.linkWorkspacePackages
|
||||
}
|
||||
let store = await createOrConnectStoreController(opts)
|
||||
if (opts.configDependencies) {
|
||||
await installConfigDeps(opts.configDependencies, {
|
||||
registries: opts.registries,
|
||||
rootDir: opts.lockfileDir ?? opts.rootProjectManifestDir,
|
||||
store: store.ctrl,
|
||||
})
|
||||
}
|
||||
if (!opts.ignorePnpmfile && !opts.hooks) {
|
||||
opts.hooks = requireHooks(opts.lockfileDir ?? opts.dir, opts)
|
||||
if (opts.hooks.fetchers != null || opts.hooks.importPackage != null) {
|
||||
store = await createOrConnectStoreController(opts)
|
||||
}
|
||||
}
|
||||
const store = await createOrConnectStoreController(opts)
|
||||
const includeDirect = opts.includeDirect ?? {
|
||||
dependencies: true,
|
||||
devDependencies: true,
|
||||
|
||||
@@ -13,13 +13,11 @@ import { getAllDependenciesFromManifest } from '@pnpm/manifest-utils'
|
||||
import { createOrConnectStoreController, type CreateStoreControllerOptions } from '@pnpm/store-connection-manager'
|
||||
import { type DependenciesField, type ProjectRootDir } from '@pnpm/types'
|
||||
import { mutateModulesInSingleProject } from '@pnpm/core'
|
||||
import { requireHooks } from '@pnpm/pnpmfile'
|
||||
import pick from 'ramda/src/pick'
|
||||
import without from 'ramda/src/without'
|
||||
import renderHelp from 'render-help'
|
||||
import { getSaveType } from './getSaveType'
|
||||
import { recursive } from './recursive'
|
||||
import { installConfigDeps } from './installConfigDeps'
|
||||
|
||||
class RemoveMissingDepsError extends PnpmError {
|
||||
constructor (
|
||||
@@ -164,20 +162,7 @@ export async function handler (
|
||||
devDependencies: opts.dev !== false,
|
||||
optionalDependencies: opts.optional !== false,
|
||||
}
|
||||
let store = await createOrConnectStoreController(opts)
|
||||
if (opts.configDependencies) {
|
||||
await installConfigDeps(opts.configDependencies, {
|
||||
registries: opts.registries,
|
||||
rootDir: opts.lockfileDir ?? opts.rootProjectManifestDir,
|
||||
store: store.ctrl,
|
||||
})
|
||||
}
|
||||
if (!opts.ignorePnpmfile) {
|
||||
opts.hooks = requireHooks(opts.lockfileDir ?? opts.dir, opts)
|
||||
if (opts.hooks.fetchers != null || opts.hooks.importPackage != null) {
|
||||
store = await createOrConnectStoreController(opts)
|
||||
}
|
||||
}
|
||||
const store = await createOrConnectStoreController(opts)
|
||||
if (opts.recursive && (opts.allProjects != null) && (opts.selectedProjectsGraph != null) && opts.workspaceDir) {
|
||||
await recursive(opts.allProjects, params, {
|
||||
...opts,
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import { add, install } from '@pnpm/plugin-commands-installation'
|
||||
import { prepare } from '@pnpm/prepare'
|
||||
import { getIntegrity } from '@pnpm/registry-mock'
|
||||
import { type ProjectManifest } from '@pnpm/types'
|
||||
import { sync as rimraf } from '@zkochan/rimraf'
|
||||
import { sync as loadJsonFile } from 'load-json-file'
|
||||
import { DEFAULT_OPTS } from './utils'
|
||||
|
||||
test('configuration dependency is installed', async () => {
|
||||
const rootProjectManifest: ProjectManifest = {
|
||||
pnpm: {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/foo': `100.0.0+${getIntegrity('@pnpm.e2e/foo', '100.0.0')}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
prepare(rootProjectManifest)
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
})
|
||||
|
||||
{
|
||||
const configDepManifest = loadJsonFile<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
||||
expect(configDepManifest.name).toBe('@pnpm.e2e/foo')
|
||||
expect(configDepManifest.version).toBe('100.0.0')
|
||||
}
|
||||
|
||||
// Dependency is updated
|
||||
rootProjectManifest.pnpm!.configDependencies!['@pnpm.e2e/foo'] = `100.1.0+${getIntegrity('@pnpm.e2e/foo', '100.1.0')}`
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
})
|
||||
|
||||
{
|
||||
const configDepManifest = loadJsonFile<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
|
||||
expect(configDepManifest.name).toBe('@pnpm.e2e/foo')
|
||||
expect(configDepManifest.version).toBe('100.1.0')
|
||||
}
|
||||
|
||||
// Dependency is removed
|
||||
rootProjectManifest.pnpm!.configDependencies = {}
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
})
|
||||
|
||||
expect(fs.existsSync('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')).toBeFalsy()
|
||||
})
|
||||
|
||||
test('patch from configuration dependency is applied', async () => {
|
||||
const rootProjectManifest = {
|
||||
pnpm: {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/has-patch-for-foo': `1.0.0+${getIntegrity('@pnpm.e2e/has-patch-for-foo', '1.0.0')}`,
|
||||
},
|
||||
patchedDependencies: {
|
||||
'@pnpm.e2e/foo@100.0.0': 'node_modules/.pnpm-config/@pnpm.e2e/has-patch-for-foo/@pnpm.e2e__foo@100.0.0.patch',
|
||||
},
|
||||
},
|
||||
}
|
||||
prepare(rootProjectManifest)
|
||||
|
||||
await add.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
patchedDependencies: rootProjectManifest.pnpm?.patchedDependencies,
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
}, ['@pnpm.e2e/foo@100.0.0'])
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/foo/index.js')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('installation fails if the checksum of the config dependency is invalid', async () => {
|
||||
const rootProjectManifest: ProjectManifest = {
|
||||
pnpm: {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/foo': '100.0.0+sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==',
|
||||
},
|
||||
},
|
||||
}
|
||||
prepare(rootProjectManifest)
|
||||
|
||||
await expect(install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
})).rejects.toThrow('Got unexpected checksum for')
|
||||
})
|
||||
|
||||
test('installation fails if the config dependency does not have a checksum', async () => {
|
||||
const rootProjectManifest: ProjectManifest = {
|
||||
pnpm: {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/foo': '100.0.0',
|
||||
},
|
||||
},
|
||||
}
|
||||
prepare(rootProjectManifest)
|
||||
|
||||
await expect(install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
})).rejects.toThrow("doesn't have an integrity checksum")
|
||||
})
|
||||
|
||||
test('selectively allow scripts in some dependencies by onlyBuiltDependenciesFile', async () => {
|
||||
const rootProjectManifest = {
|
||||
pnpm: {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/build-allow-list': `1.0.0+${getIntegrity('@pnpm.e2e/build-allow-list', '1.0.0')}`,
|
||||
},
|
||||
onlyBuiltDependenciesFile: 'node_modules/.pnpm-config/@pnpm.e2e/build-allow-list/list.json',
|
||||
},
|
||||
}
|
||||
prepare(rootProjectManifest)
|
||||
|
||||
await add.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
|
||||
|
||||
rimraf('node_modules')
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: true,
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
})
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('selectively allow scripts in some dependencies by onlyBuiltDependenciesFile and onlyBuiltDependencies', async () => {
|
||||
const rootProjectManifest = {
|
||||
pnpm: {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/build-allow-list': `1.0.0+${getIntegrity('@pnpm.e2e/build-allow-list', '1.0.0')}`,
|
||||
},
|
||||
onlyBuiltDependenciesFile: 'node_modules/.pnpm-config/@pnpm.e2e/build-allow-list/list.json',
|
||||
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
|
||||
},
|
||||
}
|
||||
prepare(rootProjectManifest)
|
||||
|
||||
await add.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
}, ['@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
|
||||
|
||||
rimraf('node_modules')
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
configDependencies: rootProjectManifest.pnpm!.configDependencies,
|
||||
dir: process.cwd(),
|
||||
frozenLockfile: true,
|
||||
rootProjectManifest,
|
||||
rootProjectManifestDir: process.cwd(),
|
||||
})
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
|
||||
})
|
||||
@@ -756,47 +756,3 @@ test('installing in monorepo with shared lockfile should work on virtual drives'
|
||||
|
||||
projects['project-1'].has('is-positive')
|
||||
})
|
||||
|
||||
test('pass readPackage with shared lockfile', async () => {
|
||||
const projects = preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
},
|
||||
},
|
||||
])
|
||||
fs.writeFileSync('.pnpmfile.cjs', `
|
||||
module.exports = {
|
||||
hooks: {
|
||||
readPackage: (pkg) => ({
|
||||
...pkg,
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
`, 'utf8')
|
||||
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
...await filterPackagesFromDir(process.cwd(), []),
|
||||
dir: process.cwd(),
|
||||
recursive: true,
|
||||
workspaceDir: process.cwd(),
|
||||
})
|
||||
|
||||
projects['project-1'].has('is-positive')
|
||||
projects['project-1'].hasNot('is-negative')
|
||||
projects['project-2'].has('is-positive')
|
||||
projects['project-2'].hasNot('is-negative')
|
||||
})
|
||||
|
||||
52
pnpm-lock.yaml
generated
52
pnpm-lock.yaml
generated
@@ -1236,6 +1236,9 @@ importers:
|
||||
'@pnpm/config':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/config
|
||||
'@pnpm/config.deps-installer':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/deps-installer
|
||||
'@pnpm/default-reporter':
|
||||
specifier: workspace:*
|
||||
version: link:../default-reporter
|
||||
@@ -1248,9 +1251,15 @@ importers:
|
||||
'@pnpm/package-is-installable':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/package-is-installable
|
||||
'@pnpm/pnpmfile':
|
||||
specifier: workspace:*
|
||||
version: link:../../hooks/pnpmfile
|
||||
'@pnpm/read-project-manifest':
|
||||
specifier: workspace:*
|
||||
version: link:../../pkg-manifest/read-project-manifest
|
||||
'@pnpm/store-connection-manager':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/store-connection-manager
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
@@ -1580,6 +1589,49 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 0.29.12
|
||||
|
||||
config/deps-installer:
|
||||
dependencies:
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
'@pnpm/package-store':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/package-store
|
||||
'@pnpm/pick-registry-for-package':
|
||||
specifier: workspace:*
|
||||
version: link:../pick-registry-for-package
|
||||
'@pnpm/read-modules-dir':
|
||||
specifier: workspace:*
|
||||
version: link:../../fs/read-modules-dir
|
||||
'@pnpm/read-package-json':
|
||||
specifier: workspace:*
|
||||
version: link:../../pkg-manifest/read-package-json
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
'@zkochan/rimraf':
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.2
|
||||
get-npm-tarball-url:
|
||||
specifier: 'catalog:'
|
||||
version: 2.1.0
|
||||
devDependencies:
|
||||
'@pnpm/config.deps-installer':
|
||||
specifier: workspace:*
|
||||
version: 'link:'
|
||||
'@pnpm/prepare':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.2.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/testing.temp-store':
|
||||
specifier: workspace:*
|
||||
version: link:../../testing/temp-store
|
||||
load-json-file:
|
||||
specifier: 'catalog:'
|
||||
version: 6.2.0
|
||||
|
||||
config/matcher:
|
||||
dependencies:
|
||||
escape-string-regexp:
|
||||
|
||||
71
pnpm/test/configurationalDependencies.test.ts
Normal file
71
pnpm/test/configurationalDependencies.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import fs from 'fs'
|
||||
import { prepare } from '@pnpm/prepare'
|
||||
import { getIntegrity } from '@pnpm/registry-mock'
|
||||
import { sync as rimraf } from '@zkochan/rimraf'
|
||||
import { sync as writeYamlFile } from 'write-yaml-file'
|
||||
import { execPnpm } from './utils'
|
||||
|
||||
test('patch from configuration dependency is applied', async () => {
|
||||
prepare()
|
||||
writeYamlFile('pnpm-workspace.yaml', {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/has-patch-for-foo': `1.0.0+${getIntegrity('@pnpm.e2e/has-patch-for-foo', '1.0.0')}`,
|
||||
},
|
||||
patchedDependencies: {
|
||||
'@pnpm.e2e/foo@100.0.0': 'node_modules/.pnpm-config/@pnpm.e2e/has-patch-for-foo/@pnpm.e2e__foo@100.0.0.patch',
|
||||
},
|
||||
})
|
||||
|
||||
await execPnpm(['add', '@pnpm.e2e/foo@100.0.0'])
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/foo/index.js')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('selectively allow scripts in some dependencies by onlyBuiltDependenciesFile', async () => {
|
||||
prepare({})
|
||||
writeYamlFile('pnpm-workspace.yaml', {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/build-allow-list': `1.0.0+${getIntegrity('@pnpm.e2e/build-allow-list', '1.0.0')}`,
|
||||
},
|
||||
onlyBuiltDependenciesFile: 'node_modules/.pnpm-config/@pnpm.e2e/build-allow-list/list.json',
|
||||
})
|
||||
|
||||
await execPnpm(['add', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
|
||||
|
||||
rimraf('node_modules')
|
||||
|
||||
await execPnpm(['install'])
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeFalsy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeFalsy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('selectively allow scripts in some dependencies by onlyBuiltDependenciesFile and onlyBuiltDependencies', async () => {
|
||||
prepare()
|
||||
writeYamlFile('pnpm-workspace.yaml', {
|
||||
configDependencies: {
|
||||
'@pnpm.e2e/build-allow-list': `1.0.0+${getIntegrity('@pnpm.e2e/build-allow-list', '1.0.0')}`,
|
||||
},
|
||||
onlyBuiltDependenciesFile: 'node_modules/.pnpm-config/@pnpm.e2e/build-allow-list/list.json',
|
||||
onlyBuiltDependencies: ['@pnpm.e2e/pre-and-postinstall-scripts-example'],
|
||||
})
|
||||
|
||||
await execPnpm(['add', '@pnpm.e2e/pre-and-postinstall-scripts-example@1.0.0', '@pnpm.e2e/install-script-example'])
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
|
||||
|
||||
rimraf('node_modules')
|
||||
|
||||
await execPnpm(['install'])
|
||||
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
|
||||
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
|
||||
})
|
||||
@@ -313,3 +313,24 @@ test('loading a pnpmfile from a config dependency', async () => {
|
||||
|
||||
expect(fs.readdirSync('node_modules/.pnpm')).toContain('@pnpm+y@1.0.0')
|
||||
})
|
||||
|
||||
test('updateConfig hook', async () => {
|
||||
prepare()
|
||||
const pnpmfile = `
|
||||
module.exports = {
|
||||
hooks: {
|
||||
updateConfig: (config) => ({
|
||||
...config,
|
||||
nodeLinker: 'hoisted',
|
||||
}),
|
||||
},
|
||||
}`
|
||||
|
||||
fs.writeFileSync('.pnpmfile.cjs', pnpmfile, 'utf8')
|
||||
|
||||
await execPnpm(['add', 'is-odd@1.0.0'])
|
||||
|
||||
const nodeModulesFiles = fs.readdirSync('node_modules')
|
||||
expect(nodeModulesFiles).toContain('kind-of')
|
||||
expect(nodeModulesFiles).toContain('is-number')
|
||||
})
|
||||
|
||||
@@ -304,7 +304,7 @@ test('fails when .pnpmfile.cjs requires a non-existed module', async () => {
|
||||
|
||||
const proc = execPnpmSync(['add', '@pnpm.e2e/pkg-with-1-dep'])
|
||||
|
||||
expect(proc.stdout.toString()).toContain('Error during pnpmfile execution')
|
||||
expect(proc.stderr.toString()).toContain('Error during pnpmfile execution')
|
||||
expect(proc.status).toBe(1)
|
||||
})
|
||||
|
||||
@@ -651,3 +651,42 @@ test('preResolution hook', async () => {
|
||||
'@foo': 'https://foo.com/',
|
||||
})
|
||||
})
|
||||
|
||||
test('pass readPackage with shared lockfile', async () => {
|
||||
const projects = preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
},
|
||||
},
|
||||
])
|
||||
writeYamlFile('pnpm-workspace.yaml', { packages: ['*'] })
|
||||
fs.writeFileSync('.pnpmfile.cjs', `
|
||||
module.exports = {
|
||||
hooks: {
|
||||
readPackage: (pkg) => ({
|
||||
...pkg,
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
`, 'utf8')
|
||||
|
||||
await execPnpm(['install'])
|
||||
|
||||
projects['project-1'].has('is-positive')
|
||||
projects['project-1'].hasNot('is-negative')
|
||||
projects['project-2'].has('is-positive')
|
||||
projects['project-2'].hasNot('is-negative')
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface CreateTempStoreResult {
|
||||
export function createTempStore (opts?: {
|
||||
fastUnpack?: boolean
|
||||
storeDir?: string
|
||||
clientOptions?: ClientOptions
|
||||
clientOptions?: Partial<ClientOptions>
|
||||
storeOptions?: CreatePackageStoreOptions
|
||||
}): CreateTempStoreResult {
|
||||
const authConfig = { registry }
|
||||
|
||||
Reference in New Issue
Block a user