Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: preserve yaml anchors and aliases w/ single string value when updating pnpm-workspace.yaml #153

Merged
merged 11 commits into from
Feb 4, 2025
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