mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-14 03:26:13 -04:00
feat: pnpm add option to add new entries to catalogs (#9484)
close #9425 --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
11
.changeset/tangy-nails-hide.md
Normal file
11
.changeset/tangy-nails-hide.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
"@pnpm/plugin-commands-installation": minor
|
||||||
|
"@pnpm/resolve-dependencies": minor
|
||||||
|
"@pnpm/workspace.read-manifest": minor
|
||||||
|
"@pnpm/workspace.manifest-writer": minor
|
||||||
|
"@pnpm/core": minor
|
||||||
|
"@pnpm/config": minor
|
||||||
|
"pnpm": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Added two new CLI options (`--save-catalog` and `--save-catalog-name=<name>`) to `pnpm add` to save new dependencies as catalog entries. `catalog:` or `catalog:<name>` will be added to `package.json` and the package specifier will be added to the `catalogs` or `catalog[<name>]` object in `pnpm-workspace.yaml` [#9425](https://github.com/pnpm/pnpm/issues/9425).
|
||||||
@@ -52,6 +52,7 @@ export interface Config extends OptionsFromRootManifest {
|
|||||||
saveDev?: boolean
|
saveDev?: boolean
|
||||||
saveOptional?: boolean
|
saveOptional?: boolean
|
||||||
savePeer?: boolean
|
savePeer?: boolean
|
||||||
|
saveCatalogName?: string
|
||||||
saveWorkspaceProtocol?: boolean | 'rolling'
|
saveWorkspaceProtocol?: boolean | 'rolling'
|
||||||
lockfileIncludeTarballUrl?: boolean
|
lockfileIncludeTarballUrl?: boolean
|
||||||
scriptShell?: string
|
scriptShell?: string
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ export async function getConfig (opts: {
|
|||||||
'resolution-mode': 'highest',
|
'resolution-mode': 'highest',
|
||||||
'resolve-peers-from-workspace-root': true,
|
'resolve-peers-from-workspace-root': true,
|
||||||
'save-peer': false,
|
'save-peer': false,
|
||||||
|
'save-catalog-name': undefined,
|
||||||
'save-workspace-protocol': 'rolling',
|
'save-workspace-protocol': 'rolling',
|
||||||
'scripts-prepend-node-path': false,
|
'scripts-prepend-node-path': false,
|
||||||
'strict-dep-builds': false,
|
'strict-dep-builds': false,
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export const types = Object.assign({
|
|||||||
'aggregate-output': Boolean,
|
'aggregate-output': Boolean,
|
||||||
'reporter-hide-prefix': Boolean,
|
'reporter-hide-prefix': Boolean,
|
||||||
'save-peer': Boolean,
|
'save-peer': Boolean,
|
||||||
|
'save-catalog-name': String,
|
||||||
'save-workspace-protocol': Boolean,
|
'save-workspace-protocol': Boolean,
|
||||||
'script-shell': String,
|
'script-shell': String,
|
||||||
'shamefully-flatten': Boolean,
|
'shamefully-flatten': Boolean,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface StrictInstallOptions {
|
|||||||
lockfileIncludeTarballUrl: boolean
|
lockfileIncludeTarballUrl: boolean
|
||||||
preferWorkspacePackages: boolean
|
preferWorkspacePackages: boolean
|
||||||
preserveWorkspaceProtocol: boolean
|
preserveWorkspaceProtocol: boolean
|
||||||
|
saveCatalogName?: string
|
||||||
scriptsPrependNodePath: boolean | 'warn-only'
|
scriptsPrependNodePath: boolean | 'warn-only'
|
||||||
scriptShell?: string
|
scriptShell?: string
|
||||||
shellEmulator: boolean
|
shellEmulator: boolean
|
||||||
|
|||||||
@@ -141,12 +141,18 @@ type Opts = Omit<InstallOptions, 'allProjects'> & {
|
|||||||
binsDir?: string
|
binsDir?: string
|
||||||
} & InstallMutationOptions
|
} & InstallMutationOptions
|
||||||
|
|
||||||
|
export interface InstallResult {
|
||||||
|
newCatalogs: CatalogSnapshots | undefined
|
||||||
|
updatedManifest: ProjectManifest
|
||||||
|
ignoredBuilds: string[] | undefined
|
||||||
|
}
|
||||||
|
|
||||||
export async function install (
|
export async function install (
|
||||||
manifest: ProjectManifest,
|
manifest: ProjectManifest,
|
||||||
opts: Opts
|
opts: Opts
|
||||||
): Promise<{ updatedManifest: ProjectManifest, ignoredBuilds: string[] | undefined }> {
|
): Promise<InstallResult> {
|
||||||
const rootDir = (opts.dir ?? process.cwd()) as ProjectRootDir
|
const rootDir = (opts.dir ?? process.cwd()) as ProjectRootDir
|
||||||
const { updatedProjects: projects, ignoredBuilds } = await mutateModules(
|
const { newCatalogs, updatedProjects: projects, ignoredBuilds } = await mutateModules(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
mutation: 'install',
|
mutation: 'install',
|
||||||
@@ -168,7 +174,7 @@ export async function install (
|
|||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return { updatedManifest: projects[0].manifest, ignoredBuilds }
|
return { newCatalogs, updatedManifest: projects[0].manifest, ignoredBuilds }
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProjectToBeInstalled {
|
interface ProjectToBeInstalled {
|
||||||
@@ -188,6 +194,12 @@ export type MutateModulesOptions = InstallOptions & {
|
|||||||
} | InstallOptions['hooks']
|
} | InstallOptions['hooks']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MutateModulesInSingleProjectResult {
|
||||||
|
newCatalogs: CatalogSnapshots | undefined
|
||||||
|
updatedProject: UpdatedProject
|
||||||
|
ignoredBuilds: string[] | undefined
|
||||||
|
}
|
||||||
|
|
||||||
export async function mutateModulesInSingleProject (
|
export async function mutateModulesInSingleProject (
|
||||||
project: MutatedProject & {
|
project: MutatedProject & {
|
||||||
binsDir?: string
|
binsDir?: string
|
||||||
@@ -196,7 +208,7 @@ export async function mutateModulesInSingleProject (
|
|||||||
modulesDir?: string
|
modulesDir?: string
|
||||||
},
|
},
|
||||||
maybeOpts: Omit<MutateModulesOptions, 'allProjects'> & InstallMutationOptions
|
maybeOpts: Omit<MutateModulesOptions, 'allProjects'> & InstallMutationOptions
|
||||||
): Promise<{ updatedProject: UpdatedProject, ignoredBuilds: string[] | undefined }> {
|
): Promise<MutateModulesInSingleProjectResult> {
|
||||||
const result = await mutateModules(
|
const result = await mutateModules(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -215,10 +227,15 @@ export async function mutateModulesInSingleProject (
|
|||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return { updatedProject: result.updatedProjects[0], ignoredBuilds: result.ignoredBuilds }
|
return {
|
||||||
|
newCatalogs: result.newCatalogs,
|
||||||
|
updatedProject: result.updatedProjects[0],
|
||||||
|
ignoredBuilds: result.ignoredBuilds,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MutateModulesResult {
|
export interface MutateModulesResult {
|
||||||
|
newCatalogs?: CatalogSnapshots
|
||||||
updatedProjects: UpdatedProject[]
|
updatedProjects: UpdatedProject[]
|
||||||
stats: InstallationResultStats
|
stats: InstallationResultStats
|
||||||
depsRequiringBuild?: DepPath[]
|
depsRequiringBuild?: DepPath[]
|
||||||
@@ -315,6 +332,7 @@ export async function mutateModules (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
newCatalogs: result.newCatalogs,
|
||||||
updatedProjects: result.updatedProjects,
|
updatedProjects: result.updatedProjects,
|
||||||
stats: result.stats ?? { added: 0, removed: 0, linkedToRoot: 0 },
|
stats: result.stats ?? { added: 0, removed: 0, linkedToRoot: 0 },
|
||||||
depsRequiringBuild: result.depsRequiringBuild,
|
depsRequiringBuild: result.depsRequiringBuild,
|
||||||
@@ -322,6 +340,7 @@ export async function mutateModules (
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface InnerInstallResult {
|
interface InnerInstallResult {
|
||||||
|
readonly newCatalogs?: CatalogSnapshots
|
||||||
readonly updatedProjects: UpdatedProject[]
|
readonly updatedProjects: UpdatedProject[]
|
||||||
readonly stats?: InstallationResultStats
|
readonly stats?: InstallationResultStats
|
||||||
readonly depsRequiringBuild?: DepPath[]
|
readonly depsRequiringBuild?: DepPath[]
|
||||||
@@ -520,6 +539,7 @@ export async function mutateModules (
|
|||||||
optionalDependencies,
|
optionalDependencies,
|
||||||
updateWorkspaceDependencies: project.update,
|
updateWorkspaceDependencies: project.update,
|
||||||
preferredSpecs,
|
preferredSpecs,
|
||||||
|
saveCatalogName: opts.saveCatalogName,
|
||||||
overrides: opts.overrides,
|
overrides: opts.overrides,
|
||||||
defaultCatalog: opts.catalogs?.default,
|
defaultCatalog: opts.catalogs?.default,
|
||||||
})
|
})
|
||||||
@@ -548,6 +568,7 @@ export async function mutateModules (
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
newCatalogs: result.newCatalogs,
|
||||||
updatedProjects: result.projects,
|
updatedProjects: result.projects,
|
||||||
stats: result.stats,
|
stats: result.stats,
|
||||||
depsRequiringBuild: result.depsRequiringBuild,
|
depsRequiringBuild: result.depsRequiringBuild,
|
||||||
@@ -854,9 +875,9 @@ export async function addDependenciesToPackage (
|
|||||||
pinnedVersion?: 'major' | 'minor' | 'patch'
|
pinnedVersion?: 'major' | 'minor' | 'patch'
|
||||||
targetDependenciesField?: DependenciesField
|
targetDependenciesField?: DependenciesField
|
||||||
} & InstallMutationOptions
|
} & InstallMutationOptions
|
||||||
): Promise<{ updatedManifest: ProjectManifest, ignoredBuilds: string[] | undefined }> {
|
): Promise<InstallResult> {
|
||||||
const rootDir = (opts.dir ?? process.cwd()) as ProjectRootDir
|
const rootDir = (opts.dir ?? process.cwd()) as ProjectRootDir
|
||||||
const { updatedProjects: projects, ignoredBuilds } = await mutateModules(
|
const { newCatalogs, updatedProjects: projects, ignoredBuilds } = await mutateModules(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
allowNew: opts.allowNew,
|
allowNew: opts.allowNew,
|
||||||
@@ -884,7 +905,7 @@ export async function addDependenciesToPackage (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
return { updatedManifest: projects[0].manifest, ignoredBuilds }
|
return { newCatalogs, updatedManifest: projects[0].manifest, ignoredBuilds }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImporterToUpdate = {
|
export type ImporterToUpdate = {
|
||||||
@@ -909,6 +930,7 @@ export interface UpdatedProject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface InstallFunctionResult {
|
interface InstallFunctionResult {
|
||||||
|
newCatalogs?: CatalogSnapshots
|
||||||
newLockfile: LockfileObject
|
newLockfile: LockfileObject
|
||||||
projects: UpdatedProject[]
|
projects: UpdatedProject[]
|
||||||
stats?: InstallationResultStats
|
stats?: InstallationResultStats
|
||||||
@@ -1023,6 +1045,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
|||||||
dependenciesGraph,
|
dependenciesGraph,
|
||||||
dependenciesByProjectId,
|
dependenciesByProjectId,
|
||||||
linkedDependenciesByProjectId,
|
linkedDependenciesByProjectId,
|
||||||
|
newCatalogs,
|
||||||
newLockfile,
|
newLockfile,
|
||||||
outdatedDependencies,
|
outdatedDependencies,
|
||||||
peerDependencyIssuesByProjects,
|
peerDependencyIssuesByProjects,
|
||||||
@@ -1417,6 +1440,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
newCatalogs,
|
||||||
newLockfile,
|
newLockfile,
|
||||||
projects: projects.map(({ id, manifest, rootDir }) => ({
|
projects: projects.map(({ id, manifest, rootDir }) => ({
|
||||||
manifest,
|
manifest,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export function parseWantedDependencies (
|
|||||||
overrides?: Record<string, string>
|
overrides?: Record<string, string>
|
||||||
updateWorkspaceDependencies?: boolean
|
updateWorkspaceDependencies?: boolean
|
||||||
preferredSpecs?: Record<string, string>
|
preferredSpecs?: Record<string, string>
|
||||||
|
saveCatalogName?: string
|
||||||
defaultCatalog?: Catalog
|
defaultCatalog?: Catalog
|
||||||
}
|
}
|
||||||
): WantedDependency[] {
|
): WantedDependency[] {
|
||||||
@@ -43,7 +44,8 @@ export function parseWantedDependencies (
|
|||||||
dev: Boolean(opts.dev || alias && !!opts.devDependencies[alias]),
|
dev: Boolean(opts.dev || alias && !!opts.devDependencies[alias]),
|
||||||
optional: Boolean(opts.optional || alias && !!opts.optionalDependencies[alias]),
|
optional: Boolean(opts.optional || alias && !!opts.optionalDependencies[alias]),
|
||||||
prevSpecifier: alias && opts.currentBareSpecifiers[alias],
|
prevSpecifier: alias && opts.currentBareSpecifiers[alias],
|
||||||
}
|
saveCatalogName: opts.saveCatalogName,
|
||||||
|
} satisfies Partial<WantedDependency>
|
||||||
if (bareSpecifier) {
|
if (bareSpecifier) {
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"@pnpm/store-connection-manager": "workspace:*",
|
"@pnpm/store-connection-manager": "workspace:*",
|
||||||
"@pnpm/types": "workspace:*",
|
"@pnpm/types": "workspace:*",
|
||||||
"@pnpm/workspace.find-packages": "workspace:*",
|
"@pnpm/workspace.find-packages": "workspace:*",
|
||||||
|
"@pnpm/workspace.manifest-writer": "workspace:*",
|
||||||
"@pnpm/workspace.pkgs-graph": "workspace:*",
|
"@pnpm/workspace.pkgs-graph": "workspace:*",
|
||||||
"@pnpm/workspace.state": "workspace:*",
|
"@pnpm/workspace.state": "workspace:*",
|
||||||
"@pnpm/write-project-manifest": "workspace:*",
|
"@pnpm/write-project-manifest": "workspace:*",
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import { type InstallCommandOptions } from './install'
|
|||||||
import { installDeps } from './installDeps'
|
import { installDeps } from './installDeps'
|
||||||
import { writeSettings } from '@pnpm/config.config-writer'
|
import { writeSettings } from '@pnpm/config.config-writer'
|
||||||
|
|
||||||
|
export const shorthands: Record<string, string> = {
|
||||||
|
'save-catalog': '--save-catalog-name=default',
|
||||||
|
}
|
||||||
|
|
||||||
export function rcOptionsTypes (): Record<string, unknown> {
|
export function rcOptionsTypes (): Record<string, unknown> {
|
||||||
return pick([
|
return pick([
|
||||||
'cache-dir',
|
'cache-dir',
|
||||||
@@ -51,6 +55,7 @@ export function rcOptionsTypes (): Record<string, unknown> {
|
|||||||
'public-hoist-pattern',
|
'public-hoist-pattern',
|
||||||
'registry',
|
'registry',
|
||||||
'reporter',
|
'reporter',
|
||||||
|
'save-catalog-name',
|
||||||
'save-dev',
|
'save-dev',
|
||||||
'save-exact',
|
'save-exact',
|
||||||
'save-optional',
|
'save-optional',
|
||||||
@@ -116,6 +121,14 @@ export function help (): string {
|
|||||||
description: 'Save package to your `peerDependencies` and `devDependencies`',
|
description: 'Save package to your `peerDependencies` and `devDependencies`',
|
||||||
name: '--save-peer',
|
name: '--save-peer',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: 'Save package to the default catalog',
|
||||||
|
name: '--save-catalog',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Save package to the specified catalog',
|
||||||
|
name: '--save-catalog-name=<name>',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: 'Install exact version',
|
description: 'Install exact version',
|
||||||
name: '--[no-]save-exact',
|
name: '--[no-]save-exact',
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ export type InstallCommandOptions = Pick<Config,
|
|||||||
| 'savePeer'
|
| 'savePeer'
|
||||||
| 'savePrefix'
|
| 'savePrefix'
|
||||||
| 'saveProd'
|
| 'saveProd'
|
||||||
|
| 'saveCatalogName'
|
||||||
| 'saveWorkspaceProtocol'
|
| 'saveWorkspaceProtocol'
|
||||||
| 'lockfileIncludeTarballUrl'
|
| 'lockfileIncludeTarballUrl'
|
||||||
| 'allProjectsGraph'
|
| 'allProjectsGraph'
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
} from '@pnpm/core'
|
} from '@pnpm/core'
|
||||||
import { globalInfo, logger } from '@pnpm/logger'
|
import { globalInfo, logger } from '@pnpm/logger'
|
||||||
import { sequenceGraph } from '@pnpm/sort-packages'
|
import { sequenceGraph } from '@pnpm/sort-packages'
|
||||||
|
import { addCatalogs } from '@pnpm/workspace.manifest-writer'
|
||||||
import { createPkgGraph } from '@pnpm/workspace.pkgs-graph'
|
import { createPkgGraph } from '@pnpm/workspace.pkgs-graph'
|
||||||
import { updateWorkspaceState, type WorkspaceStateSettings } from '@pnpm/workspace.state'
|
import { updateWorkspaceState, type WorkspaceStateSettings } from '@pnpm/workspace.state'
|
||||||
import isSubdir from 'is-subdir'
|
import isSubdir from 'is-subdir'
|
||||||
@@ -313,9 +314,12 @@ when running add/update with the --workspace option')
|
|||||||
rootDir: opts.dir as ProjectRootDir,
|
rootDir: opts.dir as ProjectRootDir,
|
||||||
targetDependenciesField: getSaveType(opts),
|
targetDependenciesField: getSaveType(opts),
|
||||||
}
|
}
|
||||||
const { updatedProject, ignoredBuilds } = await mutateModulesInSingleProject(mutatedProject, installOpts)
|
const { newCatalogs, updatedProject, ignoredBuilds } = await mutateModulesInSingleProject(mutatedProject, installOpts)
|
||||||
if (opts.save !== false) {
|
if (opts.save !== false) {
|
||||||
await writeProjectManifest(updatedProject.manifest)
|
await Promise.all([
|
||||||
|
writeProjectManifest(updatedProject.manifest),
|
||||||
|
newCatalogs && addCatalogs(opts.workspaceDir ?? opts.dir, newCatalogs),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
if (!opts.lockfileOnly) {
|
if (!opts.lockfileOnly) {
|
||||||
await updateWorkspaceState({
|
await updateWorkspaceState({
|
||||||
@@ -333,9 +337,12 @@ when running add/update with the --workspace option')
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { updatedManifest, ignoredBuilds } = await install(manifest, installOpts)
|
const { newCatalogs, updatedManifest, ignoredBuilds } = await install(manifest, installOpts)
|
||||||
if (opts.update === true && opts.save !== false) {
|
if (opts.update === true && opts.save !== false) {
|
||||||
await writeProjectManifest(updatedManifest)
|
await Promise.all([
|
||||||
|
writeProjectManifest(updatedManifest),
|
||||||
|
newCatalogs && addCatalogs(opts.workspaceDir ?? opts.dir, newCatalogs),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
||||||
throw new IgnoredBuildsError(ignoredBuilds)
|
throw new IgnoredBuildsError(ignoredBuilds)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '@pnpm/config'
|
} from '@pnpm/config'
|
||||||
import { PnpmError } from '@pnpm/error'
|
import { PnpmError } from '@pnpm/error'
|
||||||
import { arrayOfWorkspacePackagesToMap } from '@pnpm/get-context'
|
import { arrayOfWorkspacePackagesToMap } from '@pnpm/get-context'
|
||||||
|
import { type CatalogSnapshots } from '@pnpm/lockfile.types'
|
||||||
import { logger } from '@pnpm/logger'
|
import { logger } from '@pnpm/logger'
|
||||||
import { filterDependenciesByType } from '@pnpm/manifest-utils'
|
import { filterDependenciesByType } from '@pnpm/manifest-utils'
|
||||||
import { createMatcherWithIndex } from '@pnpm/matcher'
|
import { createMatcherWithIndex } from '@pnpm/matcher'
|
||||||
@@ -30,6 +31,7 @@ import {
|
|||||||
type ProjectRootDir,
|
type ProjectRootDir,
|
||||||
type ProjectRootDirRealPath,
|
type ProjectRootDirRealPath,
|
||||||
} from '@pnpm/types'
|
} from '@pnpm/types'
|
||||||
|
import { addCatalogs } from '@pnpm/workspace.manifest-writer'
|
||||||
import {
|
import {
|
||||||
addDependenciesToPackage,
|
addDependenciesToPackage,
|
||||||
install,
|
install,
|
||||||
@@ -70,6 +72,7 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick<Config,
|
|||||||
| 'rootProjectManifest'
|
| 'rootProjectManifest'
|
||||||
| 'rootProjectManifestDir'
|
| 'rootProjectManifestDir'
|
||||||
| 'save'
|
| 'save'
|
||||||
|
| 'saveCatalogName'
|
||||||
| 'saveDev'
|
| 'saveDev'
|
||||||
| 'saveExact'
|
| 'saveExact'
|
||||||
| 'saveOptional'
|
| 'saveOptional'
|
||||||
@@ -149,6 +152,7 @@ export async function recursive (
|
|||||||
pruneLockfileImporters: opts.pruneLockfileImporters ??
|
pruneLockfileImporters: opts.pruneLockfileImporters ??
|
||||||
(((opts.ignoredPackages == null) || opts.ignoredPackages.size === 0) &&
|
(((opts.ignoredPackages == null) || opts.ignoredPackages.size === 0) &&
|
||||||
pkgs.length === allProjects.length),
|
pkgs.length === allProjects.length),
|
||||||
|
saveCatalogName: opts.saveCatalogName,
|
||||||
storeController: store.ctrl,
|
storeController: store.ctrl,
|
||||||
storeDir: store.dir,
|
storeDir: store.dir,
|
||||||
targetDependenciesField,
|
targetDependenciesField,
|
||||||
@@ -275,17 +279,22 @@ export async function recursive (
|
|||||||
throw new PnpmError('NO_PACKAGE_IN_DEPENDENCIES',
|
throw new PnpmError('NO_PACKAGE_IN_DEPENDENCIES',
|
||||||
'None of the specified packages were found in the dependencies of any of the projects.')
|
'None of the specified packages were found in the dependencies of any of the projects.')
|
||||||
}
|
}
|
||||||
const { updatedProjects: mutatedPkgs, ignoredBuilds } = await mutateModules(mutatedImporters, {
|
const {
|
||||||
|
newCatalogs,
|
||||||
|
updatedProjects: mutatedPkgs,
|
||||||
|
ignoredBuilds,
|
||||||
|
} = await mutateModules(mutatedImporters, {
|
||||||
...installOpts,
|
...installOpts,
|
||||||
storeController: store.ctrl,
|
storeController: store.ctrl,
|
||||||
})
|
})
|
||||||
if (opts.save !== false) {
|
if (opts.save !== false) {
|
||||||
await Promise.all(
|
const promises: Array<Promise<void>> = mutatedPkgs.map(async ({ originalManifest, manifest, rootDir }) => {
|
||||||
mutatedPkgs
|
return manifestsByPath[rootDir].writeProjectManifest(originalManifest ?? manifest)
|
||||||
.map(async ({ originalManifest, manifest, rootDir }) => {
|
})
|
||||||
return manifestsByPath[rootDir].writeProjectManifest(originalManifest ?? manifest)
|
if (newCatalogs) {
|
||||||
})
|
promises.push(addCatalogs(opts.workspaceDir, newCatalogs))
|
||||||
)
|
}
|
||||||
|
await Promise.all(promises)
|
||||||
}
|
}
|
||||||
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
||||||
throw new IgnoredBuildsError(ignoredBuilds)
|
throw new IgnoredBuildsError(ignoredBuilds)
|
||||||
@@ -295,6 +304,8 @@ export async function recursive (
|
|||||||
|
|
||||||
const pkgPaths = (Object.keys(opts.selectedProjectsGraph) as ProjectRootDir[]).sort()
|
const pkgPaths = (Object.keys(opts.selectedProjectsGraph) as ProjectRootDir[]).sort()
|
||||||
|
|
||||||
|
let newCatalogs: CatalogSnapshots | undefined
|
||||||
|
|
||||||
const limitInstallation = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency))
|
const limitInstallation = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency))
|
||||||
await Promise.all(pkgPaths.map(async (rootDir) =>
|
await Promise.all(pkgPaths.map(async (rootDir) =>
|
||||||
limitInstallation(async () => {
|
limitInstallation(async () => {
|
||||||
@@ -339,6 +350,7 @@ export async function recursive (
|
|||||||
& { pinnedVersion: 'major' | 'minor' | 'patch' }
|
& { pinnedVersion: 'major' | 'minor' | 'patch' }
|
||||||
|
|
||||||
interface ActionResult {
|
interface ActionResult {
|
||||||
|
newCatalogs?: CatalogSnapshots
|
||||||
updatedManifest: ProjectManifest
|
updatedManifest: ProjectManifest
|
||||||
ignoredBuilds: string[] | undefined
|
ignoredBuilds: string[] | undefined
|
||||||
}
|
}
|
||||||
@@ -356,7 +368,11 @@ export async function recursive (
|
|||||||
rootDir,
|
rootDir,
|
||||||
},
|
},
|
||||||
], opts)
|
], opts)
|
||||||
return { updatedManifest: mutationResult.updatedProjects[0].manifest, ignoredBuilds: mutationResult.ignoredBuilds }
|
return {
|
||||||
|
newCatalogs: undefined, // there's no reason to add new catalogs on `pnpm remove`
|
||||||
|
updatedManifest: mutationResult.updatedProjects[0].manifest,
|
||||||
|
ignoredBuilds: mutationResult.ignoredBuilds,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@@ -367,7 +383,11 @@ export async function recursive (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const localConfig = await memReadLocalConfig(rootDir)
|
const localConfig = await memReadLocalConfig(rootDir)
|
||||||
const { updatedManifest: newManifest, ignoredBuilds } = await action(
|
const {
|
||||||
|
newCatalogs: newCatalogsAddition,
|
||||||
|
updatedManifest: newManifest,
|
||||||
|
ignoredBuilds,
|
||||||
|
} = await action(
|
||||||
manifest,
|
manifest,
|
||||||
{
|
{
|
||||||
...installOpts,
|
...installOpts,
|
||||||
@@ -391,6 +411,10 @@ export async function recursive (
|
|||||||
)
|
)
|
||||||
if (opts.save !== false) {
|
if (opts.save !== false) {
|
||||||
await writeProjectManifest(newManifest)
|
await writeProjectManifest(newManifest)
|
||||||
|
if (newCatalogsAddition) {
|
||||||
|
newCatalogs ??= {}
|
||||||
|
Object.assign(newCatalogs, newCatalogsAddition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
if (opts.strictDepBuilds && ignoredBuilds?.length) {
|
||||||
throw new IgnoredBuildsError(ignoredBuilds)
|
throw new IgnoredBuildsError(ignoredBuilds)
|
||||||
@@ -415,6 +439,10 @@ export async function recursive (
|
|||||||
})
|
})
|
||||||
))
|
))
|
||||||
|
|
||||||
|
if (newCatalogs) {
|
||||||
|
await addCatalogs(opts.workspaceDir, newCatalogs)
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!opts.lockfileOnly && !opts.ignoreScripts && (
|
!opts.lockfileOnly && !opts.ignoreScripts && (
|
||||||
cmdFullName === 'add' ||
|
cmdFullName === 'add' ||
|
||||||
|
|||||||
210
pkg-manager/plugin-commands-installation/test/saveCatalog.ts
Normal file
210
pkg-manager/plugin-commands-installation/test/saveCatalog.ts
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { add } from '@pnpm/plugin-commands-installation'
|
||||||
|
import { prepare, preparePackages } from '@pnpm/prepare'
|
||||||
|
import { addDistTag } from '@pnpm/registry-mock'
|
||||||
|
import { type LockfileFile } from '@pnpm/lockfile.types'
|
||||||
|
import { sync as loadJsonFile } from 'load-json-file'
|
||||||
|
import { sync as readYamlFile } from 'read-yaml-file'
|
||||||
|
import { DEFAULT_OPTS } from './utils'
|
||||||
|
|
||||||
|
// This must be a function because some of its values depend on CWD
|
||||||
|
const createOptions = (saveCatalogName = 'default'): add.AddCommandOptions => ({
|
||||||
|
...DEFAULT_OPTS,
|
||||||
|
saveCatalogName,
|
||||||
|
dir: process.cwd(),
|
||||||
|
cacheDir: path.resolve('cache'),
|
||||||
|
storeDir: path.resolve('store'),
|
||||||
|
})
|
||||||
|
|
||||||
|
test('saveCatalogName creates new workspace manifest with the new catalogs', async () => {
|
||||||
|
const project = prepare({
|
||||||
|
name: 'test-save-catalog',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
|
||||||
|
|
||||||
|
await add.handler(createOptions(), ['@pnpm.e2e/foo'])
|
||||||
|
|
||||||
|
expect(loadJsonFile('package.json')).toHaveProperty(['dependencies'], {
|
||||||
|
'@pnpm.e2e/foo': 'catalog:',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toHaveProperty(['catalog'], {
|
||||||
|
'@pnpm.e2e/foo': '^100.1.0',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(project.readLockfile()).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
'@pnpm.e2e/foo@100.1.0': {
|
||||||
|
resolution: expect.anything(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('saveCatalogName works with different protocols', async () => {
|
||||||
|
const project = prepare({
|
||||||
|
name: 'test-save-catalog',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const options = createOptions()
|
||||||
|
options.registries['@jsr'] = options.rawConfig['@jsr:registry'] = 'https://npm.jsr.io/'
|
||||||
|
await add.handler(options, [
|
||||||
|
'@pnpm.e2e/foo@100.1.0',
|
||||||
|
'jsr:@rus/greet@0.0.3',
|
||||||
|
'github:kevva/is-positive#97edff6',
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(loadJsonFile('package.json')).toHaveProperty(['dependencies'], {
|
||||||
|
'@pnpm.e2e/foo': 'catalog:',
|
||||||
|
'@rus/greet': 'catalog:',
|
||||||
|
'is-positive': 'catalog:',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toHaveProperty(['catalog'], {
|
||||||
|
'@pnpm.e2e/foo': '100.1.0',
|
||||||
|
'@rus/greet': 'jsr:0.0.3',
|
||||||
|
'is-positive': 'github:kevva/is-positive#97edff6',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(project.readLockfile()).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
'@rus/greet': {
|
||||||
|
specifier: 'jsr:0.0.3',
|
||||||
|
version: '0.0.3',
|
||||||
|
},
|
||||||
|
'is-positive': {
|
||||||
|
specifier: 'github:kevva/is-positive#97edff6',
|
||||||
|
version: '3.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
'@rus/greet': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '@jsr/rus__greet@0.0.3',
|
||||||
|
},
|
||||||
|
'is-positive': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: 'https://codeload.github.com/kevva/is-positive/tar.gz/97edff6',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('saveCatalogName does not work with local dependencies', async () => {
|
||||||
|
preparePackages([
|
||||||
|
{
|
||||||
|
name: 'local-dep',
|
||||||
|
version: '0.1.2-local',
|
||||||
|
private: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'main',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
process.chdir('main')
|
||||||
|
|
||||||
|
await add.handler(createOptions(), ['../local-dep'])
|
||||||
|
|
||||||
|
expect(loadJsonFile('package.json')).toStrictEqual({
|
||||||
|
name: 'main',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
dependencies: {
|
||||||
|
'local-dep': process.platform === 'win32'
|
||||||
|
? 'link:..\\local-dep'
|
||||||
|
: 'link:../local-dep',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(fs.existsSync('pnpm-workspace.yaml')).toBe(false)
|
||||||
|
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).not.toHaveProperty(['catalog'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).not.toHaveProperty(['catalogs'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('saveCatalogName with non-default name', async () => {
|
||||||
|
const project = prepare({
|
||||||
|
name: 'test-save-catalog',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
|
||||||
|
|
||||||
|
await add.handler(createOptions('my-catalog'), ['@pnpm.e2e/foo'])
|
||||||
|
|
||||||
|
expect(loadJsonFile('package.json')).toHaveProperty(['dependencies'], {
|
||||||
|
'@pnpm.e2e/foo': 'catalog:my-catalog',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toHaveProperty(['catalogs', 'my-catalog'], {
|
||||||
|
'@pnpm.e2e/foo': '^100.1.0',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(project.readLockfile()).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
'my-catalog': {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:my-catalog',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
'@pnpm.e2e/foo@100.1.0': {
|
||||||
|
resolution: expect.anything(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
})
|
||||||
@@ -120,6 +120,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../workspace/find-workspace-dir"
|
"path": "../../workspace/find-workspace-dir"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../workspace/manifest-writer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../workspace/pkgs-graph"
|
"path": "../../workspace/pkgs-graph"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface WantedDependency {
|
|||||||
dev: boolean
|
dev: boolean
|
||||||
optional: boolean
|
optional: boolean
|
||||||
injected?: boolean
|
injected?: boolean
|
||||||
|
saveCatalogName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetNonDevWantedDependenciesManifest = Pick<DependencyManifest, 'bundleDependencies' | 'bundledDependencies' | 'optionalDependencies' | 'dependencies' | 'dependenciesMeta'>
|
type GetNonDevWantedDependenciesManifest = Pick<DependencyManifest, 'bundleDependencies' | 'bundledDependencies' | 'optionalDependencies' | 'dependencies' | 'dependenciesMeta'>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface WantedDependency {
|
|||||||
dev: boolean
|
dev: boolean
|
||||||
optional: boolean
|
optional: boolean
|
||||||
nodeExecPath?: string
|
nodeExecPath?: string
|
||||||
|
saveCatalogName?: string
|
||||||
updateSpec?: boolean
|
updateSpec?: boolean
|
||||||
prevSpecifier?: string
|
prevSpecifier?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
packageManifestLogger,
|
packageManifestLogger,
|
||||||
} from '@pnpm/core-loggers'
|
} from '@pnpm/core-loggers'
|
||||||
import {
|
import {
|
||||||
|
type CatalogSnapshots,
|
||||||
type LockfileObject,
|
type LockfileObject,
|
||||||
type ProjectSnapshot,
|
type ProjectSnapshot,
|
||||||
} from '@pnpm/lockfile.types'
|
} from '@pnpm/lockfile.types'
|
||||||
@@ -93,6 +94,7 @@ export interface ImporterToResolve extends Importer<{
|
|||||||
export interface ResolveDependenciesResult {
|
export interface ResolveDependenciesResult {
|
||||||
dependenciesByProjectId: DependenciesByProjectId
|
dependenciesByProjectId: DependenciesByProjectId
|
||||||
dependenciesGraph: GenericDependenciesGraphWithResolvedChildren<ResolvedPackage>
|
dependenciesGraph: GenericDependenciesGraphWithResolvedChildren<ResolvedPackage>
|
||||||
|
newCatalogs?: CatalogSnapshots | undefined
|
||||||
outdatedDependencies: {
|
outdatedDependencies: {
|
||||||
[pkgId: string]: string
|
[pkgId: string]: string
|
||||||
}
|
}
|
||||||
@@ -135,6 +137,7 @@ export async function resolveDependencies (
|
|||||||
appliedPatches,
|
appliedPatches,
|
||||||
time,
|
time,
|
||||||
allPeerDepNames,
|
allPeerDepNames,
|
||||||
|
newCatalogs,
|
||||||
} = await resolveDependencyTree(projectsToResolve, opts)
|
} = await resolveDependencyTree(projectsToResolve, opts)
|
||||||
|
|
||||||
opts.storeController.clearResolutionCache()
|
opts.storeController.clearResolutionCache()
|
||||||
@@ -303,7 +306,17 @@ export async function resolveDependencies (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Q: Why would `newLockfile.catalogs` be constructed twice?
|
||||||
|
// A: `getCatalogSnapshots` handles new dependencies that were resolved as `catalog:*` (e.g. new entries in `package.json` whose values were `catalog:*`),
|
||||||
|
// and `newCatalogs` handles dependencies that were added as CLI parameters from `pnpm add --save-catalog`.
|
||||||
newLockfile.catalogs = getCatalogSnapshots(Object.values(resolvedImporters).flatMap(({ directDependencies }) => directDependencies))
|
newLockfile.catalogs = getCatalogSnapshots(Object.values(resolvedImporters).flatMap(({ directDependencies }) => directDependencies))
|
||||||
|
for (const catalogName in newCatalogs) {
|
||||||
|
for (const dependencyName in newCatalogs[catalogName]) {
|
||||||
|
newLockfile.catalogs ??= {}
|
||||||
|
newLockfile.catalogs[catalogName] ??= {}
|
||||||
|
newLockfile.catalogs[catalogName][dependencyName] = newCatalogs[catalogName][dependencyName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// waiting till package requests are finished
|
// waiting till package requests are finished
|
||||||
async function waitTillAllFetchingsFinish (): Promise<void> {
|
async function waitTillAllFetchingsFinish (): Promise<void> {
|
||||||
@@ -319,6 +332,7 @@ export async function resolveDependencies (
|
|||||||
dependenciesGraph,
|
dependenciesGraph,
|
||||||
outdatedDependencies,
|
outdatedDependencies,
|
||||||
linkedDependenciesByProjectId,
|
linkedDependenciesByProjectId,
|
||||||
|
newCatalogs,
|
||||||
newLockfile,
|
newLockfile,
|
||||||
peerDependencyIssuesByProjects,
|
peerDependencyIssuesByProjects,
|
||||||
waitTillAllFetchingsFinish,
|
waitTillAllFetchingsFinish,
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ export type PkgAddress = {
|
|||||||
catalogLookup?: CatalogLookupMetadata
|
catalogLookup?: CatalogLookupMetadata
|
||||||
optional: boolean
|
optional: boolean
|
||||||
normalizedBareSpecifier?: string
|
normalizedBareSpecifier?: string
|
||||||
|
saveCatalogName?: string
|
||||||
} & ({
|
} & ({
|
||||||
isLinkedDependency: true
|
isLinkedDependency: true
|
||||||
version: string
|
version: string
|
||||||
@@ -1565,6 +1566,9 @@ async function resolveDependency (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolvedPkg = ctx.resolvedPkgsById[pkgResponse.body.id]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
alias: wantedDependency.alias ?? pkgResponse.body.alias ?? pkg.name,
|
alias: wantedDependency.alias ?? pkgResponse.body.alias ?? pkg.name,
|
||||||
depIsLinked,
|
depIsLinked,
|
||||||
@@ -1576,7 +1580,9 @@ async function resolveDependency (
|
|||||||
pkgId: pkgResponse.body.id,
|
pkgId: pkgResponse.body.id,
|
||||||
rootDir,
|
rootDir,
|
||||||
missingPeers: getMissingPeers(pkg),
|
missingPeers: getMissingPeers(pkg),
|
||||||
optional: ctx.resolvedPkgsById[pkgResponse.body.id].optional,
|
optional: resolvedPkg.optional,
|
||||||
|
version: resolvedPkg.version,
|
||||||
|
saveCatalogName: wantedDependency.saveCatalogName,
|
||||||
|
|
||||||
// Next fields are actually only needed when isNew = true
|
// Next fields are actually only needed when isNew = true
|
||||||
installable,
|
installable,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { resolveFromCatalog } from '@pnpm/catalogs.resolver'
|
import { resolveFromCatalog } from '@pnpm/catalogs.resolver'
|
||||||
import { type Catalogs } from '@pnpm/catalogs.types'
|
import { type Catalogs } from '@pnpm/catalogs.types'
|
||||||
import { type LockfileObject } from '@pnpm/lockfile.types'
|
import { type CatalogSnapshots, type LockfileObject } from '@pnpm/lockfile.types'
|
||||||
|
import { globalWarn } from '@pnpm/logger'
|
||||||
import { type PatchGroupRecord } from '@pnpm/patching.config'
|
import { type PatchGroupRecord } from '@pnpm/patching.config'
|
||||||
import { type PreferredVersions, type Resolution, type WorkspacePackages } from '@pnpm/resolver-base'
|
import { type PreferredVersions, type Resolution, type WorkspacePackages } from '@pnpm/resolver-base'
|
||||||
import { type StoreController } from '@pnpm/store-controller-types'
|
import { type StoreController } from '@pnpm/store-controller-types'
|
||||||
@@ -136,6 +137,7 @@ export interface ResolveDependenciesOptions {
|
|||||||
export interface ResolveDependencyTreeResult {
|
export interface ResolveDependencyTreeResult {
|
||||||
allPeerDepNames: Set<string>
|
allPeerDepNames: Set<string>
|
||||||
dependenciesTree: DependenciesTree<ResolvedPackage>
|
dependenciesTree: DependenciesTree<ResolvedPackage>
|
||||||
|
newCatalogs?: CatalogSnapshots
|
||||||
outdatedDependencies: {
|
outdatedDependencies: {
|
||||||
[pkgId: string]: string
|
[pkgId: string]: string
|
||||||
}
|
}
|
||||||
@@ -234,6 +236,29 @@ export async function resolveDependencyTree<T> (
|
|||||||
const { pkgAddressesByImporters, time } = await resolveRootDependencies(ctx, resolveArgs)
|
const { pkgAddressesByImporters, time } = await resolveRootDependencies(ctx, resolveArgs)
|
||||||
const directDepsByImporterId = zipObj(importers.map(({ id }) => id), pkgAddressesByImporters)
|
const directDepsByImporterId = zipObj(importers.map(({ id }) => id), pkgAddressesByImporters)
|
||||||
|
|
||||||
|
let newCatalogs: CatalogSnapshots | undefined
|
||||||
|
for (const directDependencies of pkgAddressesByImporters) {
|
||||||
|
for (const directDep of directDependencies as PkgAddress[]) {
|
||||||
|
const { alias, normalizedBareSpecifier, version, saveCatalogName } = directDep
|
||||||
|
const existingCatalog = opts.catalogs?.default?.[alias]
|
||||||
|
if (existingCatalog != null) {
|
||||||
|
if (existingCatalog !== normalizedBareSpecifier) {
|
||||||
|
globalWarn(
|
||||||
|
`Skip adding ${alias} to catalogs.${saveCatalogName} because it already exists as ${existingCatalog}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (saveCatalogName != null && normalizedBareSpecifier != null && version != null) {
|
||||||
|
newCatalogs ??= {}
|
||||||
|
newCatalogs[saveCatalogName] ??= {}
|
||||||
|
newCatalogs[saveCatalogName][alias] = {
|
||||||
|
specifier: normalizedBareSpecifier,
|
||||||
|
version,
|
||||||
|
}
|
||||||
|
directDep.normalizedBareSpecifier = `catalog:${saveCatalogName === 'default' ? '' : saveCatalogName}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const pendingNode of ctx.pendingNodes) {
|
for (const pendingNode of ctx.pendingNodes) {
|
||||||
ctx.dependenciesTree.set(pendingNode.nodeId, {
|
ctx.dependenciesTree.set(pendingNode.nodeId, {
|
||||||
children: () => buildTree(ctx, pendingNode.resolvedPackage.id,
|
children: () => buildTree(ctx, pendingNode.resolvedPackage.id,
|
||||||
@@ -276,6 +301,7 @@ export async function resolveDependencyTree<T> (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dependenciesTree: ctx.dependenciesTree,
|
dependenciesTree: ctx.dependenciesTree,
|
||||||
|
newCatalogs,
|
||||||
outdatedDependencies: ctx.outdatedDependencies,
|
outdatedDependencies: ctx.outdatedDependencies,
|
||||||
resolvedImporters,
|
resolvedImporters,
|
||||||
resolvedPkgsById: ctx.resolvedPkgsById,
|
resolvedPkgsById: ctx.resolvedPkgsById,
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -5472,6 +5472,9 @@ importers:
|
|||||||
'@pnpm/workspace.find-packages':
|
'@pnpm/workspace.find-packages':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../workspace/find-packages
|
version: link:../../workspace/find-packages
|
||||||
|
'@pnpm/workspace.manifest-writer':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../workspace/manifest-writer
|
||||||
'@pnpm/workspace.pkgs-graph':
|
'@pnpm/workspace.pkgs-graph':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../workspace/pkgs-graph
|
version: link:../../workspace/pkgs-graph
|
||||||
@@ -8273,6 +8276,9 @@ importers:
|
|||||||
'@pnpm/constants':
|
'@pnpm/constants':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/constants
|
version: link:../../packages/constants
|
||||||
|
'@pnpm/lockfile.types':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../lockfile/types
|
||||||
'@pnpm/object.key-sorting':
|
'@pnpm/object.key-sorting':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../object/key-sorting
|
version: link:../../object/key-sorting
|
||||||
|
|||||||
768
pnpm/test/saveCatalog.ts
Normal file
768
pnpm/test/saveCatalog.ts
Normal file
@@ -0,0 +1,768 @@
|
|||||||
|
import { type LockfileFile } from '@pnpm/lockfile.types'
|
||||||
|
import { prepare, preparePackages } from '@pnpm/prepare'
|
||||||
|
import { addDistTag } from '@pnpm/registry-mock'
|
||||||
|
import { type ProjectManifest } from '@pnpm/types'
|
||||||
|
import { sync as loadJsonFile } from 'load-json-file'
|
||||||
|
import { sync as readYamlFile } from 'read-yaml-file'
|
||||||
|
import { sync as writeYamlFile } from 'write-yaml-file'
|
||||||
|
import { execPnpm } from './utils'
|
||||||
|
|
||||||
|
test('--save-catalog adds catalogs to the manifest of a single package workspace', async () => {
|
||||||
|
const manifest: ProjectManifest = {
|
||||||
|
name: 'test-save-catalog',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': 'catalog:',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare(manifest)
|
||||||
|
|
||||||
|
writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '^100.1.0',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' })
|
||||||
|
|
||||||
|
await execPnpm(['install'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
'@pnpm.e2e/bar@100.1.0': expect.anything(),
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
|
||||||
|
await execPnpm(['add', '--save-catalog', '@pnpm.e2e/foo'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
'@pnpm.e2e/bar@100.1.0': expect.anything(),
|
||||||
|
'@pnpm.e2e/foo@100.1.0': expect.anything(),
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toStrictEqual({
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '^100.1.0',
|
||||||
|
'@pnpm.e2e/foo': '^100.1.0',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(loadJsonFile('package.json')).toStrictEqual({
|
||||||
|
...manifest,
|
||||||
|
dependencies: {
|
||||||
|
...manifest.dependencies,
|
||||||
|
'@pnpm.e2e/foo': 'catalog:',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('--save-catalog adds catalogs to the manifest of a shared lockfile workspace', async () => {
|
||||||
|
const manifests: ProjectManifest[] = [
|
||||||
|
{
|
||||||
|
name: 'project-0',
|
||||||
|
version: '0.0.0',
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': 'catalog:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'project-1',
|
||||||
|
version: '0.0.0',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
preparePackages(manifests)
|
||||||
|
|
||||||
|
writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
sharedWorkspaceLockfile: true,
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '^100.1.0',
|
||||||
|
},
|
||||||
|
packages: ['project-0', 'project-1'],
|
||||||
|
})
|
||||||
|
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' })
|
||||||
|
|
||||||
|
await execPnpm(['install'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'project-0': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'project-1': {},
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
'@pnpm.e2e/bar@100.1.0': expect.anything(),
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
|
||||||
|
await execPnpm(['--filter=project-1', 'add', '--save-catalog', '@pnpm.e2e/foo'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'project-0': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'project-1': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
'@pnpm.e2e/bar@100.1.0': expect.anything(),
|
||||||
|
'@pnpm.e2e/foo@100.1.0': expect.anything(),
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toStrictEqual({
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '^100.1.0',
|
||||||
|
'@pnpm.e2e/foo': '^100.1.0',
|
||||||
|
},
|
||||||
|
packages: ['project-0', 'project-1'],
|
||||||
|
sharedWorkspaceLockfile: true,
|
||||||
|
})
|
||||||
|
expect(loadJsonFile('project-1/package.json')).toStrictEqual({
|
||||||
|
...manifests[1],
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/foo': 'catalog:',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('--save-catalog adds catalogs to the manifest of a multi-lockfile workspace', async () => {
|
||||||
|
const manifests: ProjectManifest[] = [
|
||||||
|
{
|
||||||
|
name: 'project-0',
|
||||||
|
version: '0.0.0',
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': 'catalog:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'project-1',
|
||||||
|
version: '0.0.0',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
preparePackages(manifests)
|
||||||
|
|
||||||
|
writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
sharedWorkspaceLockfile: false,
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '^100.1.0',
|
||||||
|
},
|
||||||
|
packages: ['project-0', 'project-1'],
|
||||||
|
})
|
||||||
|
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' })
|
||||||
|
|
||||||
|
{
|
||||||
|
await execPnpm(['install'])
|
||||||
|
|
||||||
|
const lockfile0: LockfileFile = readYamlFile('project-0/pnpm-lock.yaml')
|
||||||
|
expect(lockfile0.catalogs).toStrictEqual({
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as LockfileFile['catalogs'])
|
||||||
|
expect(lockfile0.importers).toStrictEqual({
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as LockfileFile['importers'])
|
||||||
|
|
||||||
|
const lockfile1: LockfileFile = readYamlFile('project-1/pnpm-lock.yaml')
|
||||||
|
expect(lockfile1.catalogs).toBeUndefined()
|
||||||
|
expect(lockfile1.importers).toStrictEqual({
|
||||||
|
'.': {},
|
||||||
|
} as LockfileFile['importers'])
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
await execPnpm(['--filter=project-1', 'add', '--save-catalog', '@pnpm.e2e/foo'])
|
||||||
|
|
||||||
|
const lockfile0: LockfileFile = readYamlFile('project-0/pnpm-lock.yaml')
|
||||||
|
expect(lockfile0.catalogs).toStrictEqual({
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as LockfileFile['catalogs'])
|
||||||
|
expect(lockfile0.importers).toStrictEqual({
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as LockfileFile['importers'])
|
||||||
|
|
||||||
|
const lockfile1: LockfileFile = readYamlFile('project-1/pnpm-lock.yaml')
|
||||||
|
expect(lockfile1.catalogs).toStrictEqual({
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as LockfileFile['catalogs'])
|
||||||
|
expect(lockfile1.importers).toStrictEqual({
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as LockfileFile['importers'])
|
||||||
|
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toStrictEqual({
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '^100.1.0',
|
||||||
|
'@pnpm.e2e/foo': '^100.1.0',
|
||||||
|
},
|
||||||
|
packages: ['project-0', 'project-1'],
|
||||||
|
sharedWorkspaceLockfile: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(loadJsonFile('project-1/package.json')).toStrictEqual({
|
||||||
|
...manifests[1],
|
||||||
|
dependencies: {
|
||||||
|
...manifests[1].dependencies,
|
||||||
|
'@pnpm.e2e/foo': 'catalog:',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('--save-catalog does not add local workspace dependency as a catalog', async () => {
|
||||||
|
const manifests: ProjectManifest[] = [
|
||||||
|
{
|
||||||
|
name: 'project-0',
|
||||||
|
version: '0.0.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'project-1',
|
||||||
|
version: '0.0.0',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
preparePackages(manifests)
|
||||||
|
|
||||||
|
writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
packages: ['project-0', 'project-1'],
|
||||||
|
})
|
||||||
|
|
||||||
|
{
|
||||||
|
await execPnpm(['install'])
|
||||||
|
|
||||||
|
const lockfile: LockfileFile = readYamlFile('pnpm-lock.yaml')
|
||||||
|
expect(lockfile.catalogs).toBeUndefined()
|
||||||
|
expect(lockfile.importers).toStrictEqual({
|
||||||
|
'project-0': {},
|
||||||
|
'project-1': {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
await execPnpm(['--filter=project-1', 'add', '--save-catalog', 'project-0@workspace:*'])
|
||||||
|
|
||||||
|
const lockfile: LockfileFile = readYamlFile('pnpm-lock.yaml')
|
||||||
|
expect(lockfile.catalogs).toBeUndefined()
|
||||||
|
expect(lockfile.importers).toStrictEqual({
|
||||||
|
'project-0': {},
|
||||||
|
'project-1': {
|
||||||
|
dependencies: {
|
||||||
|
'project-0': {
|
||||||
|
specifier: 'workspace:*',
|
||||||
|
version: 'link:../project-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toStrictEqual({
|
||||||
|
packages: ['project-0', 'project-1'],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(loadJsonFile('project-1/package.json')).toStrictEqual({
|
||||||
|
...manifests[1],
|
||||||
|
dependencies: {
|
||||||
|
'project-0': 'workspace:*',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('--save-catalog does not affect new dependencies from package.json', async () => {
|
||||||
|
const manifest: ProjectManifest = {
|
||||||
|
name: 'test-save-catalog',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/pkg-a': 'catalog:',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = prepare(manifest)
|
||||||
|
|
||||||
|
writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/pkg-a': '1.0.0',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// initialize the lockfile
|
||||||
|
await execPnpm(['install'])
|
||||||
|
expect(project.readLockfile()).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/pkg-a': {
|
||||||
|
specifier: '1.0.0',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/pkg-a': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
|
||||||
|
// add a new dependency to package.json by editing it
|
||||||
|
project.writePackageJson({
|
||||||
|
...manifest,
|
||||||
|
dependencies: {
|
||||||
|
...manifest.dependencies,
|
||||||
|
'@pnpm.e2e/pkg-b': '*',
|
||||||
|
},
|
||||||
|
} as ProjectManifest)
|
||||||
|
|
||||||
|
// add a new dependency by running `pnpm add --save-catalog`
|
||||||
|
await execPnpm(['add', '--save-catalog', '@pnpm.e2e/pkg-c'])
|
||||||
|
|
||||||
|
const lockfile = project.readLockfile()
|
||||||
|
expect(lockfile.catalogs).toStrictEqual({
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/pkg-a': {
|
||||||
|
specifier: '1.0.0',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/pkg-c': {
|
||||||
|
specifier: '^1.0.0',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as LockfileFile['catalogs'])
|
||||||
|
expect(lockfile.catalogs.default).not.toHaveProperty(['@pnpm.e2e/pkg-b'])
|
||||||
|
expect(lockfile.importers).toStrictEqual({
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/pkg-a': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/pkg-b': {
|
||||||
|
specifier: '*', // unaffected by `pnpm add --save-catalog`
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/pkg-c': {
|
||||||
|
specifier: 'catalog:', // created by `pnpm add --save-catalog`
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as LockfileFile['importers'])
|
||||||
|
|
||||||
|
expect(loadJsonFile('package.json')).toStrictEqual({
|
||||||
|
...manifest,
|
||||||
|
dependencies: {
|
||||||
|
...manifest.dependencies,
|
||||||
|
'@pnpm.e2e/pkg-b': '*', // unaffected by `pnpm add --save-catalog`
|
||||||
|
'@pnpm.e2e/pkg-c': 'catalog:', // created by `pnpm add --save-catalog`
|
||||||
|
},
|
||||||
|
} as ProjectManifest)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('--save-catalog does not overwrite existing catalogs', async () => {
|
||||||
|
const manifests: ProjectManifest[] = [
|
||||||
|
{
|
||||||
|
name: 'project-0',
|
||||||
|
version: '0.0.0',
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': 'catalog:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'project-1',
|
||||||
|
version: '0.0.0',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
preparePackages(manifests)
|
||||||
|
|
||||||
|
writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '=100.0.0', // intentionally outdated
|
||||||
|
},
|
||||||
|
packages: ['project-0', 'project-1'],
|
||||||
|
})
|
||||||
|
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' })
|
||||||
|
|
||||||
|
await execPnpm(['install'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '=100.0.0',
|
||||||
|
version: '100.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'project-0': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'project-1': {},
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
|
||||||
|
await execPnpm(['add', '--filter=project-1', '--save-catalog', '@pnpm.e2e/foo@100.1.0', '@pnpm.e2e/bar@100.1.0'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '=100.0.0', // unchanged
|
||||||
|
version: '100.0.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '100.1.0', // created by `pnpm add --save-catalog`
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'project-0': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:', // unchanged
|
||||||
|
version: '100.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'project-1': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '100.1.0', // created by `pnpm add --save-catalog`
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:', // created by `pnpm add --save-catalog`
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toStrictEqual({
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '=100.0.0', // unchanged
|
||||||
|
'@pnpm.e2e/foo': '100.1.0', // created by `pnpm add --save-catalog`
|
||||||
|
},
|
||||||
|
packages: ['project-0', 'project-1'],
|
||||||
|
})
|
||||||
|
expect(loadJsonFile('project-0/package.json')).toStrictEqual(manifests[0])
|
||||||
|
expect(loadJsonFile('project-1/package.json')).toStrictEqual({
|
||||||
|
...manifests[1],
|
||||||
|
dependencies: {
|
||||||
|
...manifests[1].dependencies,
|
||||||
|
'@pnpm.e2e/bar': '100.1.0',
|
||||||
|
'@pnpm.e2e/foo': 'catalog:',
|
||||||
|
},
|
||||||
|
} as ProjectManifest)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('--save-catalog creates new workspace manifest with the new catalog (recursive add)', async () => {
|
||||||
|
const manifests: ProjectManifest[] = [
|
||||||
|
{
|
||||||
|
name: 'project-0',
|
||||||
|
version: '0.0.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'project-1',
|
||||||
|
version: '0.0.0',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
preparePackages(manifests)
|
||||||
|
|
||||||
|
await execPnpm(['add', '--recursive', '--save-catalog', '@pnpm.e2e/foo@100.1.0'])
|
||||||
|
|
||||||
|
expect(readYamlFile('project-0/pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
expect(readYamlFile('project-1/pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toStrictEqual({
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/foo': '100.1.0',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(loadJsonFile('project-0/package.json')).toStrictEqual({
|
||||||
|
...manifests[0],
|
||||||
|
dependencies: {
|
||||||
|
...manifests[0].dependencies,
|
||||||
|
'@pnpm.e2e/foo': 'catalog:',
|
||||||
|
},
|
||||||
|
} as ProjectManifest)
|
||||||
|
expect(loadJsonFile('project-1/package.json')).toStrictEqual({
|
||||||
|
...manifests[1],
|
||||||
|
dependencies: {
|
||||||
|
...manifests[1].dependencies,
|
||||||
|
'@pnpm.e2e/foo': 'catalog:',
|
||||||
|
},
|
||||||
|
} as ProjectManifest)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('--save-catalog with a non-default catalog name', async () => {
|
||||||
|
const manifest: ProjectManifest = {
|
||||||
|
name: 'test-save-catalog',
|
||||||
|
version: '0.0.0',
|
||||||
|
private: true,
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': 'catalog:',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare(manifest)
|
||||||
|
|
||||||
|
writeYamlFile('pnpm-workspace.yaml', {
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '^100.1.0',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
|
||||||
|
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' })
|
||||||
|
|
||||||
|
await execPnpm(['install'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
'@pnpm.e2e/bar@100.1.0': expect.anything(),
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
|
||||||
|
await execPnpm(['add', '--save-catalog-name=my-catalog', '@pnpm.e2e/foo'])
|
||||||
|
expect(readYamlFile('pnpm-lock.yaml')).toStrictEqual(expect.objectContaining({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'my-catalog': {
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: '^100.1.0',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importers: {
|
||||||
|
'.': {
|
||||||
|
dependencies: {
|
||||||
|
'@pnpm.e2e/bar': {
|
||||||
|
specifier: 'catalog:',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
'@pnpm.e2e/foo': {
|
||||||
|
specifier: 'catalog:my-catalog',
|
||||||
|
version: '100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
'@pnpm.e2e/bar@100.1.0': expect.anything(),
|
||||||
|
'@pnpm.e2e/foo@100.1.0': expect.anything(),
|
||||||
|
},
|
||||||
|
} as Partial<LockfileFile>))
|
||||||
|
expect(readYamlFile('pnpm-workspace.yaml')).toStrictEqual({
|
||||||
|
catalog: {
|
||||||
|
'@pnpm.e2e/bar': '^100.1.0',
|
||||||
|
},
|
||||||
|
catalogs: {
|
||||||
|
'my-catalog': {
|
||||||
|
'@pnpm.e2e/foo': '^100.1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(loadJsonFile('package.json')).toStrictEqual({
|
||||||
|
...manifest,
|
||||||
|
dependencies: {
|
||||||
|
...manifest.dependencies,
|
||||||
|
'@pnpm.e2e/foo': 'catalog:my-catalog',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pnpm/constants": "workspace:*",
|
"@pnpm/constants": "workspace:*",
|
||||||
|
"@pnpm/lockfile.types": "workspace:*",
|
||||||
"@pnpm/object.key-sorting": "workspace:*",
|
"@pnpm/object.key-sorting": "workspace:*",
|
||||||
"@pnpm/workspace.read-manifest": "workspace:*",
|
"@pnpm/workspace.read-manifest": "workspace:*",
|
||||||
"ramda": "catalog:",
|
"ramda": "catalog:",
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { type ResolvedCatalogEntry } from '@pnpm/lockfile.types'
|
||||||
import { readWorkspaceManifest, type WorkspaceManifest } from '@pnpm/workspace.read-manifest'
|
import { readWorkspaceManifest, type WorkspaceManifest } from '@pnpm/workspace.read-manifest'
|
||||||
import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants'
|
import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants'
|
||||||
import writeYamlFile from 'write-yaml-file'
|
import writeYamlFile from 'write-yaml-file'
|
||||||
import equals from 'ramda/src/equals'
|
import equals from 'ramda/src/equals'
|
||||||
import { sortKeysByPriority } from '@pnpm/object.key-sorting'
|
import { sortKeysByPriority } from '@pnpm/object.key-sorting'
|
||||||
|
|
||||||
|
async function writeManifestFile (dir: string, manifest: Partial<WorkspaceManifest>): Promise<void> {
|
||||||
|
manifest = sortKeysByPriority({
|
||||||
|
priority: { packages: 0 },
|
||||||
|
deep: false,
|
||||||
|
}, manifest)
|
||||||
|
return writeYamlFile(path.join(dir, WORKSPACE_MANIFEST_FILENAME), manifest, {
|
||||||
|
lineWidth: -1, // This is setting line width to never wrap
|
||||||
|
blankLines: true,
|
||||||
|
noCompatMode: true,
|
||||||
|
noRefs: true,
|
||||||
|
sortKeys: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateWorkspaceManifest (dir: string, updatedFields: Partial<WorkspaceManifest>): Promise<void> {
|
export async function updateWorkspaceManifest (dir: string, updatedFields: Partial<WorkspaceManifest>): Promise<void> {
|
||||||
let manifest = await readWorkspaceManifest(dir) ?? {} as WorkspaceManifest
|
const manifest = await readWorkspaceManifest(dir) ?? {} as WorkspaceManifest
|
||||||
let shouldBeUpdated = false
|
let shouldBeUpdated = false
|
||||||
for (const [key, value] of Object.entries(updatedFields)) {
|
for (const [key, value] of Object.entries(updatedFields)) {
|
||||||
if (!equals(manifest[key as keyof WorkspaceManifest], value)) {
|
if (!equals(manifest[key as keyof WorkspaceManifest], value)) {
|
||||||
@@ -27,14 +42,45 @@ export async function updateWorkspaceManifest (dir: string, updatedFields: Parti
|
|||||||
await fs.promises.rm(path.join(dir, WORKSPACE_MANIFEST_FILENAME))
|
await fs.promises.rm(path.join(dir, WORKSPACE_MANIFEST_FILENAME))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
manifest = sortKeysByPriority({
|
await writeManifestFile(dir, manifest)
|
||||||
priority: { packages: 0 },
|
}
|
||||||
deep: false,
|
|
||||||
}, manifest)
|
export interface NewCatalogs {
|
||||||
await writeYamlFile(path.join(dir, WORKSPACE_MANIFEST_FILENAME), manifest, {
|
[catalogName: string]: {
|
||||||
lineWidth: -1, // This is setting line width to never wrap
|
[dependencyName: string]: Pick<ResolvedCatalogEntry, 'specifier'>
|
||||||
noCompatMode: true,
|
}
|
||||||
noRefs: true,
|
}
|
||||||
sortKeys: false,
|
|
||||||
})
|
export async function addCatalogs (workspaceDir: string, newCatalogs: NewCatalogs): Promise<void> {
|
||||||
|
const manifest: Partial<WorkspaceManifest> = await readWorkspaceManifest(workspaceDir) ?? {}
|
||||||
|
let shouldBeUpdated = false
|
||||||
|
|
||||||
|
for (const catalogName in newCatalogs) {
|
||||||
|
let targetCatalog: Record<string, string> | undefined = catalogName === 'default'
|
||||||
|
? manifest.catalog ?? manifest.catalogs?.default
|
||||||
|
: manifest.catalogs?.[catalogName]
|
||||||
|
const targetCatalogWasNil = targetCatalog == null
|
||||||
|
|
||||||
|
for (const dependencyName in newCatalogs[catalogName]) {
|
||||||
|
targetCatalog ??= {}
|
||||||
|
targetCatalog[dependencyName] = newCatalogs[catalogName][dependencyName].specifier
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetCatalog == null) continue
|
||||||
|
|
||||||
|
shouldBeUpdated = true
|
||||||
|
|
||||||
|
if (targetCatalogWasNil) {
|
||||||
|
if (catalogName === 'default') {
|
||||||
|
manifest.catalog = targetCatalog
|
||||||
|
} else {
|
||||||
|
manifest.catalogs ??= {}
|
||||||
|
manifest.catalogs[catalogName] = targetCatalog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldBeUpdated) {
|
||||||
|
await writeManifestFile(workspaceDir, manifest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
191
workspace/manifest-writer/test/addCatalogs.test.ts
Normal file
191
workspace/manifest-writer/test/addCatalogs.test.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants'
|
||||||
|
import { tempDir } from '@pnpm/prepare-temp-dir'
|
||||||
|
import { addCatalogs } from '@pnpm/workspace.manifest-writer'
|
||||||
|
import { sync as readYamlFile } from 'read-yaml-file'
|
||||||
|
import { sync as writeYamlFile } from 'write-yaml-file'
|
||||||
|
|
||||||
|
test('addCatalogs does not write new workspace manifest for empty catalogs', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
await addCatalogs(dir, {})
|
||||||
|
expect(fs.existsSync(filePath)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs does not write new workspace manifest for empty default catalogs', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
default: {},
|
||||||
|
})
|
||||||
|
expect(fs.existsSync(filePath)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs does not write new workspace manifest for empty any-named catalogs', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
foo: {},
|
||||||
|
bar: {},
|
||||||
|
})
|
||||||
|
expect(fs.existsSync(filePath)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs does not add empty catalogs', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
writeYamlFile(filePath, {})
|
||||||
|
await addCatalogs(dir, {})
|
||||||
|
expect(readYamlFile(filePath)).toStrictEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs does not add empty default catalogs', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
writeYamlFile(filePath, {})
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
default: {},
|
||||||
|
})
|
||||||
|
expect(readYamlFile(filePath)).toStrictEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs does not add empty any-named catalogs', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
writeYamlFile(filePath, {})
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
foo: {},
|
||||||
|
bar: {},
|
||||||
|
})
|
||||||
|
expect(readYamlFile(filePath)).toStrictEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs adds `default` catalogs to the `catalog` object by default', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
default: {
|
||||||
|
foo: {
|
||||||
|
specifier: '^0.1.2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(readYamlFile(filePath)).toStrictEqual({
|
||||||
|
catalog: {
|
||||||
|
foo: '^0.1.2',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs adds `default` catalogs to the `catalog` object if it exists', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
writeYamlFile(filePath, {
|
||||||
|
catalog: {
|
||||||
|
bar: '3.2.1',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
default: {
|
||||||
|
foo: {
|
||||||
|
specifier: '^0.1.2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(readYamlFile(filePath)).toStrictEqual({
|
||||||
|
catalog: {
|
||||||
|
bar: '3.2.1',
|
||||||
|
foo: '^0.1.2',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs adds `default` catalogs to the `catalogs.default` object if it exists', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
writeYamlFile(filePath, {
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
bar: '3.2.1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
default: {
|
||||||
|
foo: {
|
||||||
|
specifier: '^0.1.2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(readYamlFile(filePath)).toStrictEqual({
|
||||||
|
catalogs: {
|
||||||
|
default: {
|
||||||
|
bar: '3.2.1',
|
||||||
|
foo: '^0.1.2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs creates a `catalogs` object for any-named catalogs', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
foo: {
|
||||||
|
abc: {
|
||||||
|
specifier: '0.1.2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
def: {
|
||||||
|
specifier: '3.2.1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(readYamlFile(filePath)).toStrictEqual({
|
||||||
|
catalogs: {
|
||||||
|
foo: {
|
||||||
|
abc: '0.1.2',
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
def: '3.2.1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('addCatalogs add any-named catalogs to the `catalogs` object if it already exists', async () => {
|
||||||
|
const dir = tempDir(false)
|
||||||
|
const filePath = path.join(dir, WORKSPACE_MANIFEST_FILENAME)
|
||||||
|
writeYamlFile(filePath, {
|
||||||
|
catalogs: {
|
||||||
|
foo: {
|
||||||
|
ghi: '7.8.9',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await addCatalogs(dir, {
|
||||||
|
foo: {
|
||||||
|
abc: {
|
||||||
|
specifier: '0.1.2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
def: {
|
||||||
|
specifier: '3.2.1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(readYamlFile(filePath)).toStrictEqual({
|
||||||
|
catalogs: {
|
||||||
|
foo: {
|
||||||
|
abc: '0.1.2',
|
||||||
|
ghi: '7.8.9',
|
||||||
|
},
|
||||||
|
bar: {
|
||||||
|
def: '3.2.1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -12,6 +12,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../__utils__/prepare-temp-dir"
|
"path": "../../__utils__/prepare-temp-dir"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../lockfile/types"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../../object/key-sorting"
|
"path": "../../object/key-sorting"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { InvalidWorkspaceManifestError } from './errors/InvalidWorkspaceManifestError'
|
import { InvalidWorkspaceManifestError } from './errors/InvalidWorkspaceManifestError'
|
||||||
|
|
||||||
export interface WorkspaceNamedCatalogs {
|
export interface WorkspaceNamedCatalogs {
|
||||||
readonly [catalogName: string]: WorkspaceCatalog
|
[catalogName: string]: WorkspaceCatalog
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceCatalog {
|
export interface WorkspaceCatalog {
|
||||||
readonly [dependencyName: string]: string
|
[dependencyName: string]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertValidWorkspaceManifestCatalog (manifest: { packages?: readonly string[], catalog?: unknown }): asserts manifest is { catalog?: WorkspaceCatalog } {
|
export function assertValidWorkspaceManifestCatalog (manifest: { packages?: readonly string[], catalog?: unknown }): asserts manifest is { catalog?: WorkspaceCatalog } {
|
||||||
|
|||||||
Reference in New Issue
Block a user