mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat(cli): installing new configurational dependencies (#9377)
This commit is contained in:
5
.changeset/dark-parts-drum.md
Normal file
5
.changeset/dark-parts-drum.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/types": minor
|
||||
---
|
||||
|
||||
Export `ConfigDependencies` type.
|
||||
10
.changeset/purple-lemons-train.md
Normal file
10
.changeset/purple-lemons-train.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
"@pnpm/default-reporter": minor
|
||||
"@pnpm/config.deps-installer": minor
|
||||
"@pnpm/core-loggers": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Now you can use the `pnpm add` command with the `--config` flag to install new configurational dependencies [#9377](https://github.com/pnpm/pnpm/pull/9377).
|
||||
|
||||
5
.changeset/tangy-wings-share.md
Normal file
5
.changeset/tangy-wings-share.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/fetch": minor
|
||||
---
|
||||
|
||||
Export `CreateFetchFromRegistryOptions` type.
|
||||
@@ -137,6 +137,7 @@ export function toOutput$ (
|
||||
const statsPushStream = new Rx.Subject<logs.StatsLog>()
|
||||
const packageImportMethodPushStream = new Rx.Subject<logs.PackageImportMethodLog>()
|
||||
const installCheckPushStream = new Rx.Subject<logs.InstallCheckLog>()
|
||||
const installingConfigDepsStream = new Rx.Subject<logs.InstallingConfigDepsLog>()
|
||||
const ignoredScriptsPushStream = new Rx.Subject<logs.IgnoredScriptsLog>()
|
||||
const registryPushStream = new Rx.Subject<logs.RegistryLog>()
|
||||
const rootPushStream = new Rx.Subject<logs.RootLog>()
|
||||
@@ -188,6 +189,9 @@ export function toOutput$ (
|
||||
case 'pnpm:install-check':
|
||||
installCheckPushStream.next(log)
|
||||
break
|
||||
case 'pnpm:installing-config-deps':
|
||||
installingConfigDepsStream.next(log)
|
||||
break
|
||||
case 'pnpm:ignored-scripts':
|
||||
ignoredScriptsPushStream.next(log)
|
||||
break
|
||||
@@ -242,6 +246,7 @@ export function toOutput$ (
|
||||
executionTime: Rx.from(executionTimePushStream),
|
||||
hook: Rx.from(hookPushStream),
|
||||
installCheck: Rx.from(installCheckPushStream),
|
||||
installingConfigDeps: Rx.from(installingConfigDepsStream),
|
||||
ignoredScripts: Rx.from(ignoredScriptsPushStream),
|
||||
lifecycle: Rx.from(lifecyclePushStream),
|
||||
link: Rx.from(linkPushStream),
|
||||
|
||||
@@ -9,6 +9,7 @@ import { reportExecutionTime } from './reportExecutionTime'
|
||||
import { reportDeprecations } from './reportDeprecations'
|
||||
import { reportHooks } from './reportHooks'
|
||||
import { reportInstallChecks } from './reportInstallChecks'
|
||||
import { reportInstallingConfigDeps } from './reportInstallingConfigDeps'
|
||||
import { reportLifecycleScripts } from './reportLifecycleScripts'
|
||||
import { reportMisc, LOG_LEVEL_NUMBER } from './reportMisc'
|
||||
import { reportPeerDependencyIssues } from './reportPeerDependencyIssues'
|
||||
@@ -41,6 +42,7 @@ export function reporterForClient (
|
||||
lifecycle: Rx.Observable<logs.LifecycleLog>
|
||||
stats: Rx.Observable<logs.StatsLog>
|
||||
installCheck: Rx.Observable<logs.InstallCheckLog>
|
||||
installingConfigDeps: Rx.Observable<logs.InstallingConfigDepsLog>
|
||||
registry: Rx.Observable<logs.RegistryLog>
|
||||
root: Rx.Observable<logs.RootLog>
|
||||
packageManifest: Rx.Observable<logs.PackageManifestLog>
|
||||
@@ -126,6 +128,7 @@ export function reporterForClient (
|
||||
width,
|
||||
}),
|
||||
reportInstallChecks(log$.installCheck, { cwd }),
|
||||
reportInstallingConfigDeps(log$.installingConfigDeps),
|
||||
reportScope(log$.scope, { isRecursive: opts.isRecursive, cmd: opts.cmd }),
|
||||
reportSkippedOptionalDependencies(log$.skippedOptionalDependency, { cwd }),
|
||||
reportHooks(log$.hook, { cwd, isRecursive: opts.isRecursive }),
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { type InstallingConfigDepsLog } from '@pnpm/core-loggers'
|
||||
import * as Rx from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
export function reportInstallingConfigDeps (
|
||||
installingConfigDeps$: Rx.Observable<InstallingConfigDepsLog>
|
||||
): Rx.Observable<Rx.Observable<{ msg: string }>> {
|
||||
return Rx.of(installingConfigDeps$.pipe(
|
||||
map((log) => {
|
||||
switch (log.status) {
|
||||
case 'started': {
|
||||
return {
|
||||
msg: 'Installing config dependencies...',
|
||||
}
|
||||
}
|
||||
case 'done':
|
||||
return {
|
||||
msg: `Installed config dependencies: ${log.deps.map(({ name, version }) => `${name}@${version}`).join(', ')}`,
|
||||
}
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
@@ -33,8 +33,14 @@
|
||||
"compile": "tsc --build && pnpm run lint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/config.config-writer": "workspace:*",
|
||||
"@pnpm/core-loggers": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/fetch": "workspace:*",
|
||||
"@pnpm/network.auth-header": "workspace:*",
|
||||
"@pnpm/npm-resolver": "workspace:*",
|
||||
"@pnpm/package-store": "workspace:*",
|
||||
"@pnpm/parse-wanted-dependency": "workspace:*",
|
||||
"@pnpm/pick-registry-for-package": "workspace:*",
|
||||
"@pnpm/read-modules-dir": "workspace:*",
|
||||
"@pnpm/read-package-json": "workspace:*",
|
||||
@@ -42,12 +48,16 @@
|
||||
"@zkochan/rimraf": "catalog:",
|
||||
"get-npm-tarball-url": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pnpm/logger": ">=5.1.0 <1001.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/config.deps-installer": "workspace:*",
|
||||
"@pnpm/prepare": "workspace:*",
|
||||
"@pnpm/registry-mock": "catalog:",
|
||||
"@pnpm/testing.temp-store": "workspace:*",
|
||||
"load-json-file": "catalog:"
|
||||
"load-json-file": "catalog:",
|
||||
"read-yaml-file": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12"
|
||||
|
||||
@@ -1,66 +1,2 @@
|
||||
import path from 'path'
|
||||
import getNpmTarballUrl from 'get-npm-tarball-url'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { pickRegistryForPackage } from '@pnpm/pick-registry-for-package'
|
||||
import { readModulesDir } from '@pnpm/read-modules-dir'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import { safeReadPackageJsonFromDir } from '@pnpm/read-package-json'
|
||||
import { type StoreController } from '@pnpm/package-store'
|
||||
import { type Registries } from '@pnpm/types'
|
||||
|
||||
export async function installConfigDeps (configDeps: Record<string, string>, opts: {
|
||||
registries: Registries
|
||||
rootDir: string
|
||||
store: StoreController
|
||||
}): Promise<void> {
|
||||
const configModulesDir = path.join(opts.rootDir, 'node_modules/.pnpm-config')
|
||||
const existingConfigDeps: string[] = await readModulesDir(configModulesDir) ?? []
|
||||
await Promise.all(existingConfigDeps.map(async (existingConfigDep) => {
|
||||
if (!configDeps[existingConfigDep]) {
|
||||
await rimraf(path.join(configModulesDir, existingConfigDep))
|
||||
}
|
||||
}))
|
||||
await Promise.all(Object.entries(configDeps).map(async ([pkgName, pkgSpec]) => {
|
||||
const configDepPath = path.join(configModulesDir, pkgName)
|
||||
const sepIndex = pkgSpec.indexOf('+')
|
||||
if (sepIndex === -1) {
|
||||
throw new PnpmError('CONFIG_DEP_NO_INTEGRITY', `Your config dependency called "${pkgName}" at "pnpm.configDependencies" doesn't have an integrity checksum`, {
|
||||
hint: `All config dependencies should have their integrity checksum inlined in the version specifier. For example:
|
||||
|
||||
{
|
||||
"pnpm": {
|
||||
"configDependencies": {
|
||||
"my-config": "1.0.0+sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q=="
|
||||
},
|
||||
}
|
||||
}`,
|
||||
})
|
||||
}
|
||||
const version = pkgSpec.substring(0, sepIndex)
|
||||
const integrity = pkgSpec.substring(sepIndex + 1)
|
||||
if (existingConfigDeps.includes(pkgName)) {
|
||||
const configDepPkgJson = await safeReadPackageJsonFromDir(configDepPath)
|
||||
if (configDepPkgJson == null || configDepPkgJson.name !== pkgName || configDepPkgJson.version !== version) {
|
||||
await rimraf(configDepPath)
|
||||
}
|
||||
}
|
||||
const registry = pickRegistryForPackage(opts.registries, pkgName)
|
||||
const { fetching } = await opts.store.fetchPackage({
|
||||
force: true,
|
||||
lockfileDir: opts.rootDir,
|
||||
pkg: {
|
||||
id: `${pkgName}@${version}`,
|
||||
resolution: {
|
||||
tarball: getNpmTarballUrl(pkgName, version, { registry }),
|
||||
integrity,
|
||||
},
|
||||
},
|
||||
})
|
||||
const { files: filesResponse } = await fetching()
|
||||
await opts.store.importPackage(configDepPath, {
|
||||
force: true,
|
||||
requiresBuild: false,
|
||||
filesResponse,
|
||||
})
|
||||
}))
|
||||
}
|
||||
export { installConfigDeps, type InstallConfigDepsOpts } from './installConfigDeps'
|
||||
export { resolveConfigDeps, type ResolveConfigDepsOpts } from './resolveConfigDeps'
|
||||
|
||||
77
config/deps-installer/src/installConfigDeps.ts
Normal file
77
config/deps-installer/src/installConfigDeps.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import path from 'path'
|
||||
import getNpmTarballUrl from 'get-npm-tarball-url'
|
||||
import { installingConfigDepsLogger } from '@pnpm/core-loggers'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { pickRegistryForPackage } from '@pnpm/pick-registry-for-package'
|
||||
import { readModulesDir } from '@pnpm/read-modules-dir'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import { safeReadPackageJsonFromDir } from '@pnpm/read-package-json'
|
||||
import { type StoreController } from '@pnpm/package-store'
|
||||
import { type Registries } from '@pnpm/types'
|
||||
|
||||
export interface InstallConfigDepsOpts {
|
||||
registries: Registries
|
||||
rootDir: string
|
||||
store: StoreController
|
||||
}
|
||||
|
||||
export async function installConfigDeps (configDeps: Record<string, string>, opts: InstallConfigDepsOpts): Promise<void> {
|
||||
const configModulesDir = path.join(opts.rootDir, 'node_modules/.pnpm-config')
|
||||
const existingConfigDeps: string[] = await readModulesDir(configModulesDir) ?? []
|
||||
await Promise.all(existingConfigDeps.map(async (existingConfigDep) => {
|
||||
if (!configDeps[existingConfigDep]) {
|
||||
await rimraf(path.join(configModulesDir, existingConfigDep))
|
||||
}
|
||||
}))
|
||||
const installedConfigDeps: Array<{ name: string, version: string }> = []
|
||||
await Promise.all(Object.entries(configDeps).map(async ([pkgName, pkgSpec]) => {
|
||||
const configDepPath = path.join(configModulesDir, pkgName)
|
||||
const sepIndex = pkgSpec.indexOf('+')
|
||||
if (sepIndex === -1) {
|
||||
throw new PnpmError('CONFIG_DEP_NO_INTEGRITY', `Your config dependency called "${pkgName}" at "pnpm.configDependencies" doesn't have an integrity checksum`, {
|
||||
hint: `All config dependencies should have their integrity checksum inlined in the version specifier. For example:
|
||||
|
||||
pnpm-workspace.yaml:
|
||||
configDependencies:
|
||||
my-config: "1.0.0+sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q=="
|
||||
`,
|
||||
})
|
||||
}
|
||||
const version = pkgSpec.substring(0, sepIndex)
|
||||
const integrity = pkgSpec.substring(sepIndex + 1)
|
||||
if (existingConfigDeps.includes(pkgName)) {
|
||||
const configDepPkgJson = await safeReadPackageJsonFromDir(configDepPath)
|
||||
if (configDepPkgJson == null || configDepPkgJson.name !== pkgName || configDepPkgJson.version !== version) {
|
||||
await rimraf(configDepPath)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
installingConfigDepsLogger.debug({ status: 'started' })
|
||||
const registry = pickRegistryForPackage(opts.registries, pkgName)
|
||||
const { fetching } = await opts.store.fetchPackage({
|
||||
force: true,
|
||||
lockfileDir: opts.rootDir,
|
||||
pkg: {
|
||||
id: `${pkgName}@${version}`,
|
||||
resolution: {
|
||||
tarball: getNpmTarballUrl(pkgName, version, { registry }),
|
||||
integrity,
|
||||
},
|
||||
},
|
||||
})
|
||||
const { files: filesResponse } = await fetching()
|
||||
await opts.store.importPackage(configDepPath, {
|
||||
force: true,
|
||||
requiresBuild: false,
|
||||
filesResponse,
|
||||
})
|
||||
installedConfigDeps.push({
|
||||
name: pkgName,
|
||||
version,
|
||||
})
|
||||
}))
|
||||
if (installedConfigDeps.length) {
|
||||
installingConfigDepsLogger.debug({ status: 'done', deps: installedConfigDeps })
|
||||
}
|
||||
}
|
||||
45
config/deps-installer/src/resolveConfigDeps.ts
Normal file
45
config/deps-installer/src/resolveConfigDeps.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { writeSettings } from '@pnpm/config.config-writer'
|
||||
import { createFetchFromRegistry, type CreateFetchFromRegistryOptions } from '@pnpm/fetch'
|
||||
import { createNpmResolver, type ResolverFactoryOptions } from '@pnpm/npm-resolver'
|
||||
import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header'
|
||||
import { parseWantedDependency } from '@pnpm/parse-wanted-dependency'
|
||||
import { type ConfigDependencies } from '@pnpm/types'
|
||||
import { installConfigDeps, type InstallConfigDepsOpts } from './installConfigDeps'
|
||||
|
||||
export type ResolveConfigDepsOpts = CreateFetchFromRegistryOptions & ResolverFactoryOptions & InstallConfigDepsOpts & {
|
||||
configDependencies?: ConfigDependencies
|
||||
rootDir: string
|
||||
userConfig?: Record<string, string>
|
||||
}
|
||||
|
||||
export async function resolveConfigDeps (configDeps: string[], opts: ResolveConfigDepsOpts): Promise<void> {
|
||||
const fetch = createFetchFromRegistry(opts)
|
||||
const getAuthHeader = createGetAuthHeaderByURI({ allSettings: opts.userConfig!, userSettings: opts.userConfig })
|
||||
const { resolveFromNpm } = createNpmResolver(fetch, getAuthHeader, opts)
|
||||
const configDependencies = opts.configDependencies ?? {}
|
||||
await Promise.all(configDeps.map(async (configDep) => {
|
||||
const wantedDep = parseWantedDependency(configDep)
|
||||
if (!wantedDep.alias) {
|
||||
throw new PnpmError('BAD_CONFIG_DEP', `Cannot install ${configDep} as configuration dependency`)
|
||||
}
|
||||
const resolution = await resolveFromNpm(wantedDep, {
|
||||
lockfileDir: opts.rootDir,
|
||||
preferredVersions: {},
|
||||
projectDir: opts.rootDir,
|
||||
})
|
||||
if (resolution?.resolution == null || !('integrity' in resolution?.resolution)) {
|
||||
throw new PnpmError('BAD_CONFIG_DEP', `Cannot install ${configDep} as configuration dependency because it has no integrity`)
|
||||
}
|
||||
configDependencies[wantedDep.alias] = `${resolution?.manifest?.version}+${resolution.resolution.integrity}`
|
||||
}))
|
||||
await writeSettings({
|
||||
...opts,
|
||||
rootProjectManifestDir: opts.rootDir,
|
||||
workspaceDir: opts.rootDir,
|
||||
updatedSettings: {
|
||||
configDependencies,
|
||||
},
|
||||
})
|
||||
await installConfigDeps(configDependencies, opts)
|
||||
}
|
||||
28
config/deps-installer/test/resolveConfigDeps.test.ts
Normal file
28
config/deps-installer/test/resolveConfigDeps.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import path from 'path'
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import { getIntegrity, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
|
||||
import { resolveConfigDeps } from '@pnpm/config.deps-installer'
|
||||
import { createTempStore } from '@pnpm/testing.temp-store'
|
||||
import { sync as readYamlFile } from 'read-yaml-file'
|
||||
|
||||
const registry = `http://localhost:${REGISTRY_MOCK_PORT}/`
|
||||
|
||||
test('configuration dependency is resolved', async () => {
|
||||
prepareEmpty()
|
||||
const { storeController } = createTempStore()
|
||||
|
||||
await resolveConfigDeps(['@pnpm.e2e/foo@100.0.0'], {
|
||||
registries: {
|
||||
default: registry,
|
||||
},
|
||||
rootDir: process.cwd(),
|
||||
cacheDir: path.resolve('cache'),
|
||||
userConfig: {},
|
||||
store: storeController,
|
||||
})
|
||||
|
||||
const workspaceManifest = readYamlFile<{ configDependencies: Record<string, string> }>('pnpm-workspace.yaml')
|
||||
expect(workspaceManifest.configDependencies).toStrictEqual({
|
||||
'@pnpm.e2e/foo': `100.0.0+${getIntegrity('@pnpm.e2e/foo', '100.0.0')}`,
|
||||
})
|
||||
})
|
||||
@@ -15,21 +15,39 @@
|
||||
{
|
||||
"path": "../../fs/read-modules-dir"
|
||||
},
|
||||
{
|
||||
"path": "../../network/auth-header"
|
||||
},
|
||||
{
|
||||
"path": "../../network/fetch"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/core-loggers"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/parse-wanted-dependency"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/types"
|
||||
},
|
||||
{
|
||||
"path": "../../pkg-manifest/read-package-json"
|
||||
},
|
||||
{
|
||||
"path": "../../resolving/npm-resolver"
|
||||
},
|
||||
{
|
||||
"path": "../../store/package-store"
|
||||
},
|
||||
{
|
||||
"path": "../../testing/temp-store"
|
||||
},
|
||||
{
|
||||
"path": "../config-writer"
|
||||
},
|
||||
{
|
||||
"path": "../pick-registry-for-package"
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ export function fetchWithAgent (url: RequestInfo, opts: FetchWithAgentOptions):
|
||||
|
||||
export type { AgentOptions }
|
||||
|
||||
export function createFetchFromRegistry (
|
||||
defaultOpts: {
|
||||
fullMetadata?: boolean
|
||||
userAgent?: string
|
||||
sslConfigs?: Record<string, SslConfig>
|
||||
} & AgentOptions
|
||||
): FetchFromRegistry {
|
||||
export interface CreateFetchFromRegistryOptions extends AgentOptions {
|
||||
fullMetadata?: boolean
|
||||
userAgent?: string
|
||||
sslConfigs?: Record<string, SslConfig>
|
||||
}
|
||||
|
||||
export function createFetchFromRegistry (defaultOpts: CreateFetchFromRegistryOptions): FetchFromRegistry {
|
||||
return async (url, opts): Promise<Response> => {
|
||||
const headers = {
|
||||
'user-agent': USER_AGENT,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export type { FetchFromRegistry } from '@pnpm/fetching-types'
|
||||
export { fetch, type RetryTimeoutOptions } from './fetch'
|
||||
export { createFetchFromRegistry, fetchWithAgent, type AgentOptions } from './fetchFromRegistry'
|
||||
export { createFetchFromRegistry, fetchWithAgent, type AgentOptions, type CreateFetchFromRegistryOptions } from './fetchFromRegistry'
|
||||
|
||||
@@ -3,6 +3,7 @@ export * from './deprecationLogger'
|
||||
export * from './fetchingProgressLogger'
|
||||
export * from './hookLogger'
|
||||
export * from './installCheckLogger'
|
||||
export * from './installingConfigDeps'
|
||||
export * from './ignoredScriptsLogger'
|
||||
export * from './lifecycleLogger'
|
||||
export * from './linkLogger'
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
type ExecutionTimeLog,
|
||||
type HookLog,
|
||||
type InstallCheckLog,
|
||||
type InstallingConfigDepsLog,
|
||||
type IgnoredScriptsLog,
|
||||
type LifecycleLog,
|
||||
type LinkLog,
|
||||
@@ -32,6 +33,7 @@ export type Log =
|
||||
| ExecutionTimeLog
|
||||
| HookLog
|
||||
| InstallCheckLog
|
||||
| InstallingConfigDepsLog
|
||||
| IgnoredScriptsLog
|
||||
| LifecycleLog
|
||||
| LinkLog
|
||||
|
||||
23
packages/core-loggers/src/installingConfigDeps.ts
Normal file
23
packages/core-loggers/src/installingConfigDeps.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
type LogBase,
|
||||
logger,
|
||||
} from '@pnpm/logger'
|
||||
|
||||
export const installingConfigDepsLogger = logger<InstallingConfigDepsMessage>('installing-config-deps')
|
||||
|
||||
export interface InstallingConfigDepsMessageBase {
|
||||
status?: 'started' | 'done'
|
||||
}
|
||||
|
||||
export interface InstallingConfigDepsStartedMessage extends InstallingConfigDepsMessageBase {
|
||||
status: 'started'
|
||||
}
|
||||
|
||||
export interface InstallingConfigDepsDoneMessage extends InstallingConfigDepsMessageBase {
|
||||
deps: Array<{ name: string, version: string }>
|
||||
status: 'done'
|
||||
}
|
||||
|
||||
export type InstallingConfigDepsMessage = InstallingConfigDepsStartedMessage | InstallingConfigDepsDoneMessage
|
||||
|
||||
export type InstallingConfigDepsLog = { name: 'pnpm:installing-config-deps' } & LogBase & InstallingConfigDepsMessage
|
||||
@@ -133,8 +133,10 @@ export interface PeerDependencyRules {
|
||||
|
||||
export type AllowedDeprecatedVersions = Record<string, string>
|
||||
|
||||
export type ConfigDependencies = Record<string, string>
|
||||
|
||||
export interface PnpmSettings {
|
||||
configDependencies?: Record<string, string>
|
||||
configDependencies?: ConfigDependencies
|
||||
neverBuiltDependencies?: string[]
|
||||
onlyBuiltDependencies?: string[]
|
||||
onlyBuiltDependenciesFile?: string
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"@pnpm/common-cli-options-help": "workspace:*",
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/config.config-writer": "workspace:*",
|
||||
"@pnpm/config.deps-installer": "workspace:*",
|
||||
"@pnpm/constants": "workspace:*",
|
||||
"@pnpm/core": "workspace:*",
|
||||
"@pnpm/dedupe.check": "workspace:*",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { docsUrl } from '@pnpm/cli-utils'
|
||||
import { FILTERING, OPTIONS, UNIVERSAL_OPTIONS } from '@pnpm/common-cli-options-help'
|
||||
import { types as allTypes } from '@pnpm/config'
|
||||
import { resolveConfigDeps } from '@pnpm/config.deps-installer'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { prepareExecutionEnv } from '@pnpm/plugin-commands-env'
|
||||
import { createOrConnectStoreController } from '@pnpm/store-connection-manager'
|
||||
import pick from 'ramda/src/pick'
|
||||
import renderHelp from 'render-help'
|
||||
import { createProjectManifestWriter } from './createProjectManifestWriter'
|
||||
@@ -80,6 +82,7 @@ export function cliOptionsTypes (): Record<string, unknown> {
|
||||
recursive: Boolean,
|
||||
save: Boolean,
|
||||
workspace: Boolean,
|
||||
config: Boolean,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +140,10 @@ For options that may be used with `-r`, see "pnpm help recursive"',
|
||||
description: 'Only adds the new dependency if it is found in the workspace',
|
||||
name: '--workspace',
|
||||
},
|
||||
{
|
||||
description: 'Save the dependency to configurational dependencies',
|
||||
name: '--config',
|
||||
},
|
||||
OPTIONS.ignoreScripts,
|
||||
OPTIONS.offline,
|
||||
OPTIONS.preferOffline,
|
||||
@@ -175,6 +182,7 @@ export type AddCommandOptions = InstallCommandOptions & {
|
||||
update?: boolean
|
||||
useBetaCli?: boolean
|
||||
workspaceRoot?: boolean
|
||||
config?: boolean
|
||||
}
|
||||
|
||||
export async function handler (
|
||||
@@ -187,6 +195,15 @@ export async function handler (
|
||||
if (!params || (params.length === 0)) {
|
||||
throw new PnpmError('MISSING_PACKAGE_NAME', '`pnpm add` requires the package name')
|
||||
}
|
||||
if (opts.config) {
|
||||
const store = await createOrConnectStoreController(opts)
|
||||
await resolveConfigDeps(params, {
|
||||
...opts,
|
||||
store: store.ctrl,
|
||||
rootDir: opts.workspaceDir ?? opts.rootProjectManifestDir,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (
|
||||
!opts.recursive &&
|
||||
opts.workspaceDir === opts.dir &&
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
{
|
||||
"path": "../../config/config-writer"
|
||||
},
|
||||
{
|
||||
"path": "../../config/deps-installer"
|
||||
},
|
||||
{
|
||||
"path": "../../config/matcher"
|
||||
},
|
||||
|
||||
27
pnpm-lock.yaml
generated
27
pnpm-lock.yaml
generated
@@ -1591,12 +1591,33 @@ importers:
|
||||
|
||||
config/deps-installer:
|
||||
dependencies:
|
||||
'@pnpm/config.config-writer':
|
||||
specifier: workspace:*
|
||||
version: link:../config-writer
|
||||
'@pnpm/core-loggers':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core-loggers
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
'@pnpm/fetch':
|
||||
specifier: workspace:*
|
||||
version: link:../../network/fetch
|
||||
'@pnpm/logger':
|
||||
specifier: '>=5.1.0 <1001.0.0'
|
||||
version: 1000.0.0
|
||||
'@pnpm/network.auth-header':
|
||||
specifier: workspace:*
|
||||
version: link:../../network/auth-header
|
||||
'@pnpm/npm-resolver':
|
||||
specifier: workspace:*
|
||||
version: link:../../resolving/npm-resolver
|
||||
'@pnpm/package-store':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/package-store
|
||||
'@pnpm/parse-wanted-dependency':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/parse-wanted-dependency
|
||||
'@pnpm/pick-registry-for-package':
|
||||
specifier: workspace:*
|
||||
version: link:../pick-registry-for-package
|
||||
@@ -1631,6 +1652,9 @@ importers:
|
||||
load-json-file:
|
||||
specifier: 'catalog:'
|
||||
version: 6.2.0
|
||||
read-yaml-file:
|
||||
specifier: 'catalog:'
|
||||
version: 2.1.0
|
||||
|
||||
config/matcher:
|
||||
dependencies:
|
||||
@@ -5374,6 +5398,9 @@ importers:
|
||||
'@pnpm/config.config-writer':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/config-writer
|
||||
'@pnpm/config.deps-installer':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/deps-installer
|
||||
'@pnpm/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/constants
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 readYamlFile } from 'read-yaml-file'
|
||||
import { sync as writeYamlFile } from 'write-yaml-file'
|
||||
import { execPnpm } from './utils'
|
||||
|
||||
@@ -119,3 +120,14 @@ test('catalog applied by configurational dependency hook', async () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('installing a new configurational dependency', async () => {
|
||||
prepare()
|
||||
|
||||
await execPnpm(['add', '@pnpm.e2e/foo@100.0.0', '--config'])
|
||||
|
||||
const workspaceManifest = readYamlFile<{ configDependencies: Record<string, string> }>('pnpm-workspace.yaml')
|
||||
expect(workspaceManifest.configDependencies).toStrictEqual({
|
||||
'@pnpm.e2e/foo': `100.0.0+${getIntegrity('@pnpm.e2e/foo', '100.0.0')}`,
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user