@hyperfrontend/versioning/workspaceworkspace/
Workspace-level utilities for monorepo package discovery, dependency tracking, cascade versioning, and batch operations.
Overview
This module provides tools for working with multi-package workspaces (monorepos). It handles package discovery, builds dependency graphs, calculates cascade bumps when releasing, and performs batch updates across packages.
Configuration
Cascade Bumps
CascadeOptions:
| Option | Default | Description |
|---|---|---|
cascadeBumpType |
'patch' |
Bump type for cascaded dependents |
includeDevDependencies |
false |
Cascade through dev dependencies |
includePeerDependencies |
true |
Cascade through peer dependencies |
prereleaseId |
'alpha' |
Prerelease identifier for prerelease bumps |
Batch Updates
BatchOptions:
| Option | Default | Description |
|---|---|---|
dryRun |
false |
Preview changes without writing |
updateChangelogs |
true |
Update changelog files |
updateDependencyVersions |
true |
Update dependency version ranges |
createGitCommit |
false |
Create git commit after updates |
createGitTag |
false |
Create git tags for each updated package |
Validation
ValidationOptions:
| Option | Description |
|---|---|
customRules |
Additional validation rules |
ignoreProjects |
Projects to skip validation |
disabledRules |
Rules to disable |
Built-in Validation Rules:
| Rule | Severity | Description |
|---|---|---|
valid-version |
error | Version must be valid semver |
valid-name |
error | Package name required |
valid-name-format |
error | Package name must follow npm conventions |
no-self-dependency |
error | Package cannot depend on itself |
no-circular-deps |
error | No circular dependencies in workspace |
version-compatibility |
warning | Internal deps should be compatible |
has-changelog |
warning | Publishable packages should have changelog |
no-prerelease-deps |
warning | Avoid prerelease external dependencies |
no-wildcard-deps |
warning | Avoid wildcard version ranges |
no-git-deps |
warning | Avoid git URL dependencies |
Usage Example
import {
discoverPackages,
buildDependencyGraph,
createWorkspace,
calculateCascadeBumps,
applyBumps,
validateWorkspace,
} from '@hyperfrontend/versioning'
// 1. Discover workspace
const projects = await discoverPackages('/path/to/monorepo')
const depGraph = buildDependencyGraph(projects)
const workspace = createWorkspace({
root: '/path/to/monorepo',
type: 'nx',
projects,
dependencyGraph: depGraph,
reverseDependencyGraph: buildReverseDependencyGraph(depGraph),
})
// 2. Validate workspace
const report = validateWorkspace(workspace)
if (!report.isValid) {
console.error(formatValidationReport(report))
process.exit(1)
}
// 3. Calculate cascade bumps
const result = calculateCascadeBumps(workspace, [{ name: 'core', bumpType: 'minor' }])
console.log(summarizeCascadeBumps(result))
// Output: "3 package(s) affected (1 direct, 2 cascade)"
// 4. Apply bumps
const updateResult = applyBumps(workspace, result.bumps, {
dryRun: true,
updateChangelogs: true,
})
console.log(formatBatchResult(updateResult))
Dependencies
Uses @hyperfrontend/project-scope for file system operations and workspace detection. Uses @hyperfrontend/immutable-api-utils for immutable data structures.
See Also
- git/ — Git operations for version coordination
- changelog/ — Changelog discovery and manipulation
- flow/ — Orchestrates workspace-wide versioning
- semver/ — Version parsing for cascade calculations
- @hyperfrontend/project-scope — Virtual file system
- Main README — Package overview and quick start
- ARCHITECTURE.md — Design principles and data flow
API Reference
ƒ Functions
Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscoveryOptions | Discovery configuration options (default: {}) |
Returns
WorkspaceExample
Create a complete workspace object from disk
import { createWorkspaceFromDisk } from '@hyperfrontend/versioning'
const workspace = createWorkspaceFromDisk()
// Access projects
for (const project of workspace.projectList) {
console.log(`${project.name}@${project.version}`)
}
// Get dependents of a package
const dependents = workspace.dependencyGraph.get('lib-utils')Parameters
Returns
Map<string, string>Example
Find changelogs using VFS tree
import { findChangelogsInTree, discoverPackages } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function myGenerator(tree: Tree) {
const { packages } = discoverPackages()
const changelogs = findChangelogsInTree(tree, packages)
for (const [projectPath, changelogPath] of changelogs) {
console.log(`Found changelog at ${changelogPath}`)
}
}Returns
stringExample
Find project changelog using VFS tree
import { findProjectChangelogInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function myGenerator(tree: Tree) {
const changelogPath = findProjectChangelogInTree(tree, 'libs/my-lib')
if (changelogPath) {
const content = tree.read(changelogPath, 'utf-8')
// Process changelog content
}
}updateDependencyReferencesInTree(tree: Tree, packageJsonPath: string, versionUpdates: Map<string, string>): void
Uses the
changeFile() pattern for cleaner transformation. Silently skips if the file doesn't exist.Parameters
Example
Update dependency version references using VFS Tree
import { updateDependencyReferencesInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function syncDeps(tree: Tree) {
const updates = new Map([
['@myorg/utils', '2.0.0'],
['@myorg/core', '3.1.0'],
])
updateDependencyReferencesInTree(tree, 'apps/my-app/package.json', updates)
}Parameters
Returns
ProjectExample
Add an internal dependent to a project
import { discoverProject, addDependent } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
const updated = addDependent(project, '@myorg/new-app')
console.log('Dependents now:', updated.internalDependents)
}applyBumps(tree: Tree, workspace: Workspace, bumps: unknown, options: BatchUpdateOptions): BatchUpdateResult
Changes are buffered in the tree until
commitChanges() is called, enabling atomic commits and rollback on failure.Parameters
Returns
BatchUpdateResultExample
Apply planned bumps to workspace using VFS Tree
import { createTree, commitChanges } from '@hyperfrontend/project-scope'
import { applyBumps, calculateCascadeBumps } from '@hyperfrontend/versioning'
const tree = createTree(workspaceRoot)
const cascadeResult = calculateCascadeBumps(workspace, directBumps)
const updateResult = applyBumps(tree, workspace, cascadeResult.bumps)
if (updateResult.success) {
commitChanges(tree) // Atomic commit of all changes
console.log(`Updated ${updateResult.updated.length} packages`)
} else {
console.error('Some updates failed:', updateResult.failed)
// No commitChanges() call - changes are discarded
}Parameters
| Name | Type | Description |
|---|---|---|
§projects | unknown | List of projects to analyze |
Returns
DependencyGraphAnalysisExample
Build a complete dependency graph
import { buildDependencyGraph, discoverPackages } from '@hyperfrontend/versioning'
const { projects } = discoverPackages()
const analysis = buildDependencyGraph(projects)
// Get packages that depend on 'lib-utils'
const dependents = analysis.dependencyGraph.get('lib-utils') ?? []
// Get packages in topological order for building
const buildOrder = getTopologicalOrder(analysis)calculateCascadeBumps(workspace: Workspace, directBumps: unknown, options: CascadeBumpOptions): CascadeBumpResult
When packages are directly bumped (e.g., due to commits), their dependents may also need version bumps. This function calculates all affected packages.
Parameters
Returns
CascadeBumpResultExample
Calculate cascade bumps for a workspace
import { calculateCascadeBumps } from '@hyperfrontend/versioning'
// If lib-utils is getting a minor bump
const result = calculateCascadeBumps(workspace, [
{ name: 'lib-utils', bumpType: 'minor' }
])
// result.bumps includes lib-utils and all packages that depend on it
for (const bump of result.bumps) {
console.log(`${bump.name}: ${bump.currentVersion} -> ${bump.nextVersion}`)
}calculateCascadeBumpsFromPackage(workspace: Workspace, packageName: string, bumpType: BumpType, options: CascadeBumpOptions): CascadeBumpResult
Parameters
Returns
CascadeBumpResultExample
Calculate cascade bumps from a single package
import { discoverWorkspace, calculateCascadeBumpsFromPackage } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const result = calculateCascadeBumpsFromPackage(workspace, '@myorg/utils', 'minor')
console.log(`${result.totalAffected} packages will be bumped`)
for (const bump of result.bumps) {
console.log(`${bump.name}: ${bump.currentVersion} -> ${bump.nextVersion}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateProjectOptions | Project properties |
Returns
ProjectExample
Create a new Project object
import { createProject, readPackageJson } from '@hyperfrontend/versioning'
const packageJson = readPackageJson('./libs/my-lib/package.json')
const project = createProject({
name: '@myorg/my-lib',
version: '1.0.0',
path: './libs/my-lib',
packageJsonPath: './libs/my-lib/package.json',
packageJson,
changelogPath: './libs/my-lib/CHANGELOG.md',
})Parameters
| Name | Type | Description |
|---|---|---|
§options | CreateWorkspaceOptions | Workspace properties |
Returns
WorkspaceExample
Create a new workspace object
import { createWorkspace, createWorkspaceConfig, createProject } from '@hyperfrontend/versioning'
const projects = new Map([['@myorg/utils', createProject({ ... })]])
const workspace = createWorkspace({
root: '/path/to/workspace',
type: 'nx',
projects,
config: createWorkspaceConfig(),
dependencyGraph: new Map(),
reverseDependencyGraph: new Map(),
})Parameters
| Name | Type | Description |
|---|---|---|
§options? | Partial<WorkspaceConfig> | Partial configuration options |
Returns
WorkspaceConfigExample
Create a workspace configuration with defaults
import { createWorkspaceConfig } from '@hyperfrontend/versioning'
const config = createWorkspaceConfig({
patterns: ['packages/*', 'libs/*'],
exclude: ['node_modules', 'dist'],
})Parameters
Returns
booleanExample
Check if one project depends on another
import { discoverWorkspace, dependsOn } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (dependsOn(workspace, '@myorg/app', '@myorg/utils')) {
console.log('@myorg/app directly depends on @myorg/utils')
}Parameters
Returns
unknownExample
Discover all changelog files within a workspace
import { discoverAllChangelogs } from '@hyperfrontend/versioning'
const changelogs = discoverAllChangelogs('/path/to/workspace')
for (const changelog of changelogs) {
console.log(`${changelog.projectPath} -> ${changelog.path}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§options | DiscoveryOptions | Discovery options (default: {}) |
Returns
DiscoveryResultExample
Discover all packages within a workspace
import { discoverPackages } from '@hyperfrontend/versioning'
// Discover all packages in current workspace
const result = discoverPackages()
// Discover with custom patterns
const result = discoverPackages({
patterns: ['packages/*/package.json'],
includeChangelogs: true
})
// Access discovered projects
for (const project of result.projects) {
console.log(`${project.name}@${project.version}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Path to project directory or package.json |
Returns
ProjectExample
Discover a single project by path
import { discoverProject } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
console.log(`Found ${project.name}@${project.version}`)
}
// Also accepts direct package.json path
const project2 = discoverProject('./libs/utils/package.json')Parameters
Returns
ProjectExample
Discover a project by name within a workspace
import { discoverProjectByName } from '@hyperfrontend/versioning'
const project = discoverProjectByName('@myorg/utils')
if (project) {
console.log(`Found at ${project.path}`)
}
// With custom workspace root
const project2 = discoverProjectByName('@myorg/core', { workspaceRoot: '/custom/path' })Parameters
Returns
Map<string, string>Example
Find changelog files for all packages
import { findChangelogs, discoverPackages } from '@hyperfrontend/versioning'
const { packages } = discoverPackages()
const changelogs = findChangelogs('/workspace', packages)
for (const [projectPath, changelogPath] of changelogs) {
console.log(`${projectPath} -> ${changelogPath}`)
}findInternalDependencies(packageJson: PackageJson, workspacePackageNames: Set<string>): string[]
Parameters
Returns
string[]Example
Find internal dependencies in a package
const internalDeps = findInternalDependencies(packageJson, allPackageNames)
// ['@scope/lib-a', '@scope/lib-b']findInternalDependenciesWithTypes(packageName: string, packageJson: PackageJson, workspacePackageNames: Set<string>): DependencyEdge[]
Parameters
Returns
DependencyEdge[]Example
Find internal dependencies with type information
import { findInternalDependenciesWithTypes, readPackageJson } from '@hyperfrontend/versioning'
const packageJson = readPackageJson('./libs/my-lib/package.json')
const workspacePackages = new Set(['@myorg/utils', '@myorg/core'])
const edges = findInternalDependenciesWithTypes('@myorg/my-lib', packageJson, workspacePackages)
for (const edge of edges) {
console.log(`${edge.from} -> ${edge.to} (${edge.type})`)
}Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Path to project directory |
Returns
stringExample
Find the changelog for a single project
import { findProjectChangelog } from '@hyperfrontend/versioning'
const changelogPath = findProjectChangelog('./libs/my-lib')
if (changelogPath) {
console.log('Found changelog:', changelogPath)
}Parameters
Returns
unknownExample
Get projects that the given project depends on
import { discoverWorkspace, getDependencies } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const deps = getDependencies(workspace, '@myorg/app')
console.log(`@myorg/app depends on:`, deps)Parameters
| Name | Type | Description |
|---|---|---|
§project | Project | Project instance to analyze |
Returns
numberExample
Get the dependency count for a project
import { discoverProject, getDependencyCount } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project) {
console.log(`${project.name} depends on ${getDependencyCount(project)} internal packages`)
}Parameters
| Name | Type | Description |
|---|---|---|
§project | Project | Project instance to analyze |
Returns
numberExample
Get the dependent count for a project
import { discoverProject, getDependentCount } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
console.log(`${project.name} is used by ${getDependentCount(project)} packages`)
}Parameters
Returns
unknownExample
Get projects that depend on a given project
import { discoverWorkspace, getDependents } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const dependents = getDependents(workspace, '@myorg/utils')
console.log(`Packages depending on @myorg/utils:`, dependents)Parameters
Returns
stringExample
Get expected changelog path for a project
import { getExpectedChangelogPath } from '@hyperfrontend/versioning'
const changelogPath = getExpectedChangelogPath('./libs/my-lib')
// => '/workspace/libs/my-lib/CHANGELOG.md'
const customPath = getExpectedChangelogPath('./libs/my-lib', 'HISTORY.md')
// => '/workspace/libs/my-lib/HISTORY.md'Parameters
Returns
ProjectExample
Get a project by name from the workspace
import { discoverWorkspace, getProject } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const project = getProject(workspace, '@myorg/utils')
if (project) {
console.log(`Version: ${project.version}`)
}Parameters
| Name | Type | Description |
|---|---|---|
§workspace | Workspace | Workspace to count projects in |
Returns
numberExample
Get the count of projects in the workspace
import { discoverWorkspace, getProjectCount } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
console.log(`Workspace contains ${getProjectCount(workspace)} projects`)Parameters
| Name | Type | Description |
|---|---|---|
§workspace | Workspace | Workspace to retrieve project names from |
Returns
unknownExample
Get all project names in the workspace
import { discoverWorkspace, getProjectNames } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const names = getProjectNames(workspace)
console.log(`Found ${names.length} projects:`, names)Parameters
| Name | Type | Description |
|---|---|---|
§analysis | DependencyGraphAnalysis | Dependency graph analysis result |
Returns
unknownExample
Get packages in topological order for building
const buildOrder = getTopologicalOrder(analysis)
for (const pkg of buildOrder) {
await build(pkg)
}Parameters
Returns
Set<string>Example
Get all transitive dependencies of a package
import { discoverWorkspace, getTransitiveDependencies } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
const allDeps = getTransitiveDependencies(workspace, '@myorg/app')
console.log(`@myorg/app transitively depends on ${allDeps.size} packages`)
for (const dep of allDeps) {
console.log(` - ${dep}`)
}Parameters
Returns
Set<string>Example
Get all transitive dependents of a package
// If lib-a depends on lib-utils and app-main depends on lib-a
// Then getTransitiveDependents('lib-utils') returns ['lib-a', 'app-main']Parameters
| Name | Type | Description |
|---|---|---|
§projectPath | string | Directory containing the project to check |
Returns
booleanExample
Check if a project has a changelog
import { hasChangelog } from '@hyperfrontend/versioning'
if (hasChangelog('./libs/my-lib')) {
console.log('Project has a changelog')
}Parameters
| Name | Type | Description |
|---|---|---|
§project | Project | The project to check |
Returns
booleanExample
Check if a project has internal dependencies
import { discoverProject, hasInternalDependencies } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && hasInternalDependencies(project)) {
console.log(`${project.name} depends on:`, project.internalDependencies)
}Parameters
| Name | Type | Description |
|---|---|---|
§project | Project | The project to check |
Returns
booleanExample
Check if a project has internal dependents
import { discoverProject, hasInternalDependents } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project && hasInternalDependents(project)) {
console.log(`${project.name} is used by:`, project.internalDependents)
}Parameters
Returns
booleanExample
Check if a project exists in the workspace
import { discoverWorkspace, hasProject } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (hasProject(workspace, '@myorg/utils')) {
console.log('Package exists in workspace')
}Parameters
| Name | Type | Description |
|---|---|---|
§project | Project | The project to check |
Returns
booleanExample
Check if a project has a changelog
import { discoverProject, hasChangelog } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && !hasChangelog(project)) {
console.log('Warning: No changelog found for', project.name)
}Parameters
| Name | Type | Description |
|---|---|---|
§project | Project | The project to check |
Returns
booleanExample
Check if a project is private
import { discoverProject, isPrivate } from '@hyperfrontend/versioning'
const project = discoverProject('./apps/internal-app')
if (project && isPrivate(project)) {
console.log('Skipping private package')
}Parameters
| Name | Type | Description |
|---|---|---|
§project | Project | The project to check |
Returns
booleanExample
Check if a project is publishable
import { discoverProject, isPublishable } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project && isPublishable(project)) {
console.log(`${project.name} can be published to npm`)
}Parameters
| Name | Type | Description |
|---|---|---|
§result | BatchUpdateResult | Result object from batch update operation |
Returns
stringExample
Create a summary of the batch update result
import { batchUpdateVersions, summarizeBatchUpdate } from '@hyperfrontend/versioning'
const result = batchUpdateVersions(workspace, updates)
console.log(summarizeBatchUpdate(result))
// Output:
// Successfully updated 3 package(s)
//
// Updated packages:
// @myorg/utils: 1.0.0 -> 1.1.0
// @myorg/core: 2.0.0 -> 2.1.0Parameters
| Name | Type | Description |
|---|---|---|
§result | CascadeBumpResult | Result object from cascade bump calculation |
Returns
stringExample
Get a summary of cascade bump results
import { calculateCascadeBumps, summarizeCascadeBumps } from '@hyperfrontend/versioning'
const result = calculateCascadeBumps(workspace, [{ name: '@myorg/utils', bumpType: 'patch' }])
console.log(summarizeCascadeBumps(result))
// Output:
// 3 package(s) affected:
// - 1 direct bump(s)
// - 2 cascade bump(s)Parameters
| Name | Type | Description |
|---|---|---|
§report | ValidationReport | Report object from workspace validation |
Returns
stringExample
Create a summary of the validation report
import { validateWorkspace, summarizeValidation } from '@hyperfrontend/versioning'
const report = validateWorkspace(workspace)
console.log(summarizeValidation(report))
// Output:
// Workspace validation passed
// 2 warning(s)Parameters
Returns
booleanExample
Check if one package transitively depends on another
import { discoverWorkspace, transitivelyDependsOn } from '@hyperfrontend/versioning'
const workspace = discoverWorkspace()
if (transitivelyDependsOn(workspace, '@myorg/app', '@myorg/utils')) {
console.log('Bumping @myorg/utils will affect @myorg/app')
}Parameters
Example
Update a package.json version using VFS Tree
import { updatePackageVersionInTree } from '@hyperfrontend/versioning'
// Inside an Nx generator
export default function bumpVersion(tree: Tree) {
updatePackageVersionInTree(tree, 'libs/my-lib/package.json', '2.0.0')
}Parameters
| Name | Type | Description |
|---|---|---|
§project | Project | The project to validate |
Returns
ValidationResultExample
Validate a single project
import { discoverProject, validateProject } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/my-lib')
if (project) {
const result = validateProject(project)
if (!result.valid) {
console.error('Validation failed:', result.error)
}
}Parameters
| Name | Type | Description |
|---|---|---|
§workspace | Workspace | The workspace to validate |
Returns
ValidationReportExample
Validate a workspace for common issues
import { validateWorkspace } from '@hyperfrontend/versioning'
const report = validateWorkspace(workspace)
if (!report.valid) {
console.error(`${report.errorCount} error(s) found`)
for (const result of report.results) {
if (!result.result.valid) {
console.error(` ${result.checkName}: ${result.result.error}`)
}
}
}Parameters
Returns
ProjectExample
Create a project copy with updated dependents
import { discoverProject, withDependents } from '@hyperfrontend/versioning'
const project = discoverProject('./libs/utils')
if (project) {
const updated = withDependents(project, ['@myorg/app', '@myorg/web'])
console.log('Updated dependents:', updated.internalDependents)
}◈ Interfaces
Properties
Properties
cascadeBumpType?:BumpType— includeDevDependencies?:boolean— includePeerDependencies?:boolean— Properties
Properties
Properties
Properties
Properties
Properties
◆ Types
type DependencyGraph = ReadonlyMap<string, unknown>type DependencyType = "dependencies" | "devDependencies" | "peerDependencies" | "optionalDependencies"type WorkspaceType = "nx" | "turbo" | "lerna" | "pnpm" | "npm" | "yarn" | "rush" | "unknown"