Skip to content

Commit

Permalink
feat: preserve yaml anchor and alias when bump pnpm catalog
Browse files Browse the repository at this point in the history
Co-authored-by: Kevin Deng 三咲智子 <sxzz@sxzz.moe>
  • Loading branch information
younggglcy and sxzz authored Feb 4, 2025
1 parent b43a167 commit 173c2f8
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 51 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@
},
"dependencies": {
"@antfu/ni": "^23.3.1",
"js-yaml": "^4.1.0",
"ofetch": "^1.4.1",
"package-manager-detector": "^0.2.9",
"tinyexec": "^0.3.2",
"unconfig": "^0.6.1",
"yaml": "^2.7.0",
"yargs": "^17.7.2"
},
"devDependencies": {
Expand All @@ -48,7 +48,6 @@
"@npmcli/config": "^10.0.1",
"@types/cli-progress": "^3.11.6",
"@types/debug": "^4.1.12",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.13.1",
"@types/npm-package-arg": "^6.1.4",
"@types/npm-registry-fetch": "^8.0.7",
Expand Down
23 changes: 8 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 1 addition & 6 deletions src/commands/check/checkGlobal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Agent } from 'package-manager-detector'
import type { CheckOptions, PackageMeta, RawDep } from '../../types'
import type { CheckOptions, GlobalPackageMeta, RawDep } from '../../types'
/* eslint-disable no-console */
import { getCommand } from '@antfu/ni'
import c from 'picocolors'
Expand Down Expand Up @@ -29,10 +28,6 @@ interface PnpmOut {
}
}

interface GlobalPackageMeta extends PackageMeta {
agent: Agent
}

export async function checkGlobal(options: CheckOptions) {
let exitCode = 0
let resolvePkgs: GlobalPackageMeta[] = []
Expand Down
70 changes: 53 additions & 17 deletions src/io/pnpmWorkspaces.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import type { CommonOptions, PackageMeta, RawDep } from '../types'
import type { Scalar } from 'yaml'
import type { CommonOptions, PnpmWorkspaceMeta, RawDep } from '../types'
import fs from 'node:fs/promises'
import path from 'node:path'
import YAML from 'js-yaml'
import _debug from 'debug'
import { isAlias, parse, parseDocument, YAMLMap } from 'yaml'
import { findAnchor, writeYaml } from '../utils/yaml'
import { dumpDependencies, parseDependency } from './dependencies'

const debug = _debug('taze:io:pnpmWorkspace')

export async function loadPnpmWorkspace(
relative: string,
options: CommonOptions,
shouldUpdate: (name: string) => boolean,
): Promise<PackageMeta[]> {
): Promise<PnpmWorkspaceMeta[]> {
const filepath = path.resolve(options.cwd ?? '', relative)
const rawText = await fs.readFile(filepath, 'utf-8')
const raw = YAML.load(rawText) as any || {}
const raw = parse(rawText)
const document = parseDocument(rawText)

const catalogs: PackageMeta[] = []
const catalogs: PnpmWorkspaceMeta[] = []

function createCatalogFromKeyValue(catalogName: string, map: Record<string, string>): PackageMeta {
function createCatalogFromKeyValue(catalogName: string, map: Record<string, string>): PnpmWorkspaceMeta {
const deps: RawDep[] = Object.entries(map)
.map(([name, version]) => parseDependency(name, version, 'pnpm:catalog', shouldUpdate))

Expand All @@ -29,7 +35,8 @@ export async function loadPnpmWorkspace(
raw,
deps,
resolved: [],
}
document,
} satisfies PnpmWorkspaceMeta
}

if (raw.catalog) {
Expand All @@ -50,31 +57,60 @@ export async function loadPnpmWorkspace(
}

export async function writePnpmWorkspace(
pkg: PackageMeta,
pkg: PnpmWorkspaceMeta,
_options: CommonOptions,
) {
const catalogName = pkg.name.replace('catalog:', '')
const versions = dumpDependencies(pkg.resolved, 'pnpm:catalog')

if (!Object.keys(versions).length)
return

const catalogName = pkg.name.replace('catalog:', '')
const document = pkg.document.clone()
let changed = false

if (catalogName === 'default') {
if (JSON.stringify(pkg.raw.catalog) !== JSON.stringify(versions)) {
pkg.raw.catalog = versions
changed = true
if (!document.has('catalog')) {
document.set('catalog', new YAMLMap())
}
const catalog = document.get('catalog') as YAMLMap<Scalar.Parsed, Scalar.Parsed>
updateCatalog(catalog)
}
else {
pkg.raw.catalogs ??= {}
if (pkg.raw.catalogs[catalogName] !== versions) {
pkg.raw.catalogs[catalogName] = versions
changed = true
if (!document.has('catalogs')) {
document.set('catalogs', new YAMLMap())
}
const catalog = (document.get('catalogs') as YAMLMap).get(catalogName) as YAMLMap<Scalar.Parsed, Scalar.Parsed>
updateCatalog(catalog)
}

if (changed)
await fs.writeFile(pkg.filepath, YAML.dump(pkg.raw), 'utf-8')
await writeYaml(pkg, document)

// currently only support preserve yaml anchor and alias with single string value
function updateCatalog(catalog: YAMLMap<Scalar.Parsed, Scalar.Parsed>) {
for (const [key, targetVersion] of Object.entries(versions)) {
const pair = catalog.items.find(i => i.key.value === key)
if (!pair?.value || !pair.key) {
debug(`Exception encountered while parsing pnpm-workspace.yaml, key: ${key}`)
continue
}

if (isAlias(pair?.value)) {
const anchor = findAnchor(document, pair.value)
if (!anchor) {
debug(`can't find anchor for alias: ${pair.value} in pnpm-workspace.yaml`)
continue
}
else if (anchor.value !== targetVersion) {
anchor.value = targetVersion
changed = true
}
}
else if (pair.value.value !== targetVersion) {
pair.value.value = targetVersion
changed = true
}
}
}
}
40 changes: 31 additions & 9 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Agent } from 'package-manager-detector'
import type { Document } from 'yaml'
import type { MODE_CHOICES } from './constants'
import type { SortOption } from './utils/sort'

Expand Down Expand Up @@ -130,7 +132,7 @@ export interface CheckOptions extends CommonOptions {
timediff?: boolean
}

export interface PackageMeta {
interface BasePackageMeta {
/**
* Package name
*/
Expand All @@ -139,10 +141,6 @@ export interface PackageMeta {
* Is private package
*/
private: boolean
/**
* Package type
*/
type: 'package.json' | 'pnpm-workspace.yaml' | 'global'
/**
* Package version
*/
Expand All @@ -155,10 +153,6 @@ export interface PackageMeta {
* Relative filepath to the root project
*/
relative: string
/**
* Raw package.json Object
*/
raw: any
/**
* Dependencies
*/
Expand All @@ -169,6 +163,34 @@ export interface PackageMeta {
resolved: ResolvedDepChange[]
}

export interface PackageJsonMeta extends BasePackageMeta {
/**
* Package type
*/
type: 'package.json'
/**
* Raw package.json Object
*/
raw: any
}

export interface GlobalPackageMeta extends BasePackageMeta {
agent: Agent
type: 'global'
raw: null
}

export interface PnpmWorkspaceMeta extends BasePackageMeta {
type: 'pnpm-workspace.yaml'
raw: any
document: Document
}

export type PackageMeta =
| PackageJsonMeta
| GlobalPackageMeta
| PnpmWorkspaceMeta

export type DependencyFilter = (dep: RawDep) => boolean | Promise<boolean>
export type DependencyResolvedCallback = (packageName: string | null, depName: string, progress: number, total: number) => void

Expand Down
26 changes: 26 additions & 0 deletions src/utils/yaml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Alias, Document, Scalar } from 'yaml'
import type { PnpmWorkspaceMeta } from '../types'
import { writeFile } from 'node:fs/promises'
import { visit } from 'yaml'

export function writeYaml(pkg: PnpmWorkspaceMeta, document: Document) {
return writeFile(pkg.filepath, document.toString(), 'utf-8')
}

export function findAnchor(doc: Document, alias: Alias): Scalar<string> | null {
const { source } = alias
let anchor: Scalar<string> | null = null

visit(doc, {
Scalar: (_key, scalar, _path) => {
if (
scalar.anchor === source
&& typeof scalar.value === 'string'
) {
anchor = scalar as Scalar<string>
}
},
})

return anchor
}
Loading

0 comments on commit 173c2f8

Please sign in to comment.