Skip to content

Commit

Permalink
feat: allow anchor being defined in separated field
Browse files Browse the repository at this point in the history
  • Loading branch information
younggglcy authored and sxzz committed Feb 4, 2025
1 parent ee30ac7 commit d102f66
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 67 deletions.
52 changes: 24 additions & 28 deletions src/io/pnpmWorkspaces.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Scalar } from 'yaml'
import type { CommonOptions, PnpmWorkspaceMeta, RawDep } from '../types'
import fs from 'node:fs/promises'
import path from 'node:path'
import _debug from 'debug'
import { Alias, isAlias, parse, parseDocument, Scalar, YAMLMap } from 'yaml'
import { writeYaml } from '../utils/writeYaml'
import { isAlias, parse, parseDocument, YAMLMap } from 'yaml'
import { findAnchor, writeYaml } from '../utils/yaml'
import { dumpDependencies, parseDependency } from './dependencies'

const debug = _debug('taze:io:pnpmWorkspace')
Expand Down Expand Up @@ -65,33 +66,29 @@ export async function writePnpmWorkspace(
return

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

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

if (changed)
await writeYaml(pkg, contents)
await writeYaml(pkg, document)

// currently only support preserve yaml anchor and alias with single string value
function updateCatalog(catalog: YAMLMap<Scalar.Parsed, Scalar.Parsed>, contents: Record<string, any>) {
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) {
Expand All @@ -100,19 +97,18 @@ export async function writePnpmWorkspace(
}

if (isAlias(pair?.value)) {
contents[key] = new Alias(pair.value.source)
continue
}

if (pair.value.value !== targetVersion) {
if (pair.value.anchor) {
const node = new Scalar(targetVersion)
node.anchor = pair.value.anchor
contents[key] = node
const anchor = findAnchor(document, pair.value)
if (!anchor) {
debug(`can't find anchor for alias: ${pair.value} in pnpm-workspace.yaml`)
continue
}
else {
contents[key] = targetVersion
else if (anchor.value !== targetVersion) {
anchor.value = targetVersion
changed = true
}
}
else if (pair.value.value !== targetVersion) {
pair.value.value = targetVersion
changed = true
}
}
Expand Down
7 changes: 0 additions & 7 deletions src/utils/writeYaml.ts

This file was deleted.

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
}
107 changes: 75 additions & 32 deletions test/pnpmCatalog.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Document } from 'yaml'
import type { CheckOptions, PnpmWorkspaceMeta } from '../src'
import process from 'node:process'
import { expect, it, vi } from 'vitest'
import { parse, parseDocument, stringify } from 'yaml'
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { parse, parseDocument } from 'yaml'
import { CheckPackages } from '../src'
import { writePnpmWorkspace } from '../src/io/pnpmWorkspaces'
import * as writeYamlUtil from '../src/utils/writeYaml'
import * as YamlUtil from '../src/utils/yaml'

it('pnpm catalog', async () => {
const options: CheckOptions = {
Expand Down Expand Up @@ -104,42 +105,84 @@ it('pnpm catalog', async () => {
`)
})

it('pnpm catalog updates should preserve yaml anchors and aliases with single string value, when anchor is defined inline', async () => {
describe('pnpm catalog update w/ yaml anchors and aliases', () => {
// stringified yaml output that should be
// written to the pnpm-workspace.yaml file
let output: string | undefined

const writeYaml = vi.spyOn(writeYamlUtil, 'writeYaml').mockImplementation((_pkg: PnpmWorkspaceMeta, contents: any) => {
const writeYaml = vi.spyOn(YamlUtil, 'writeYaml').mockImplementation((_pkg: PnpmWorkspaceMeta, document: Document) => {
return Promise.resolve().then(() => {
output = stringify(contents)
output = document.toString()
})
})

const workspaceYamlContents = `
catalog:
react: &foo ^18.2.0
react-dom: *foo
`
const document = parseDocument(workspaceYamlContents)
const pkg: PnpmWorkspaceMeta = {
name: 'catalog:default',
resolved: [
// @ts-expect-error testing purpose
{ name: 'react', targetVersion: '^18.3.1', source: 'pnpm:catalog', update: true, currentVersion: '^18.2.0', diff: 'minor' },
// @ts-expect-error testing purpose
{ name: 'react-dom', targetVersion: '^18.3.1', source: 'pnpm:catalog', update: true, currentVersion: '^18.2.0', diff: 'minor' },
],
raw: parse(workspaceYamlContents),
document,
filepath: '/tmp/pnpm-workspace.yaml',
type: 'pnpm-workspace.yaml',
}
await writePnpmWorkspace(pkg, {})
expect(output).toMatchInlineSnapshot(`
"catalog:
react: &foo ^18.3.1
react-dom: *foo
"`)
beforeEach(() => {
output = undefined
})

writeYaml.mockRestore()
afterAll(() => {
writeYaml.mockRestore()
})

it('should preserve yaml anchors and aliases with single string value, when anchor is defined inline', async () => {
const workspaceYamlContents = `
catalog:
react: &foo ^18.2.0
react-dom: *foo
`
const document = parseDocument(workspaceYamlContents)
const pkg: PnpmWorkspaceMeta = {
name: 'catalog:default',
resolved: [
// @ts-expect-error testing purpose
{ name: 'react', targetVersion: '^18.3.1', source: 'pnpm:catalog', update: true, currentVersion: '^18.2.0', diff: 'minor' },
// @ts-expect-error testing purpose
{ name: 'react-dom', targetVersion: '^18.3.1', source: 'pnpm:catalog', update: true, currentVersion: '^18.2.0', diff: 'minor' },
],
raw: parse(workspaceYamlContents),
document,
filepath: '/tmp/pnpm-workspace.yaml',
type: 'pnpm-workspace.yaml',
}
await writePnpmWorkspace(pkg, {})
expect(output).toMatchInlineSnapshot(`
"catalog:
react: &foo ^18.3.1
react-dom: *foo
"`)
})

it('should preserve yaml anchors and aliases with single string value, when anchor is defined in a separate field', async () => {
const workspaceYamlContents = `
defines:
- &react ^18.2.0
catalog:
react: *react
react-dom: *react
`
const document = parseDocument(workspaceYamlContents)
const pkg: PnpmWorkspaceMeta = {
name: 'catalog:default',
resolved: [
// @ts-expect-error testing purpose
{ name: 'react', targetVersion: '^18.3.1', source: 'pnpm:catalog', update: true, currentVersion: '^18.2.0', diff: 'minor' },
// @ts-expect-error testing purpose
{ name: 'react-dom', targetVersion: '^18.3.1', source: 'pnpm:catalog', update: true, currentVersion: '^18.2.0', diff: 'minor' },
],
raw: parse(workspaceYamlContents),
document,
filepath: '/tmp/pnpm-workspace.yaml',
type: 'pnpm-workspace.yaml',
}
await writePnpmWorkspace(pkg, {})
expect(output).toMatchInlineSnapshot(`
"defines:
- &react ^18.3.1
catalog:
react: *react
react-dom: *react
"`)
})
})

0 comments on commit d102f66

Please sign in to comment.