semrel-extra/zx-bulk-release

View on GitHub
src/main/js/processor/deps.js

Summary

Maintainability
A
50 mins
Test Coverage
A
100%
import {semver} from 'zx-extra'
import {topo as _topo, traverseDeps} from '@semrel-extra/topo'

export {traverseQueue, traverseDeps} from '@semrel-extra/topo'

export const updateDeps = async (pkg) => {
  const changes = []
  const {context: {packages}} = pkg

  await traverseDeps({pkg, packages, cb: async ({name, version, deps, scope, pkg: dep}) => {
    const prev = pkg.latest.meta?.[scope]?.[name]
    const actual = dep?.version
    const next = resolveNextVersion(version, actual, prev)
    const _version = dep?.preversion || next || subsWorkspace(version, actual) // NOTE preversion should always be pinned

    pkg[scope] = {...pkg[scope], [name]: _version}  // Update pkg context
    deps[name] = _version                           // Update manifest

    if (!next) return
    changes.push({
      group: 'Dependencies',
      releaseType: 'patch',
      change: 'perf',
      subj: `perf: ${scope} ${name} updated to ${next}`,
    })
  }})

  return changes
}

export const resolveNextVersion = (decl, actual, prev) => {
  if (!decl) return null

  // https://yarnpkg.com/features/workspaces
  decl = subsWorkspace(decl, actual)

  if (!semver.satisfies(actual, decl)) return actual === prev ? null : actual

  return decl === prev ? null : decl
}

export const subsWorkspace = (decl, actual) => {
  if (decl.startsWith('workspace:')) {
    const [, range, caret] = /^workspace:(([\^~*])?.*)$/.exec(decl)

    return caret === range
      ? caret === '*' ? actual : caret + actual
      : range
  }

  return decl
}

export const topo = async ({flags = {}, cwd} = {}) => {
  const ignore = typeof flags.ignore === 'string'
    ? flags.ignore.split(',').map(c => c.trim())
    : Array.isArray(flags.ignore)
      ? flags.ignore
      : []

  const depFilter = flags.onlyWorkspaceDeps ? ({version}) => version.startsWith('workspace:') : undefined
  const pkgFilter = ({manifest: {private: _private, name}}) =>
    !ignore.includes(name) && (flags.includePrivate || !_private)

  return _topo({cwd, pkgFilter, depFilter})
}