UNPKG

@travetto/repo

Version:

Monorepo utilities

131 lines (116 loc) 4.27 kB
import path from 'node:path'; import { spawn, type ChildProcess } from 'node:child_process'; import fs from 'node:fs/promises'; import { type ExecutionResult, JSONUtil, Runtime } from '@travetto/runtime'; import { type IndexedModule, type Package, PackageUtil } from '@travetto/manifest'; import { CliModuleUtil } from '@travetto/cli'; export type SemverLevel = 'minor' | 'patch' | 'major' | 'prerelease' | 'premajor' | 'preminor' | 'prepatch'; /** * Utilities for working with package managers */ export class PackageManager { /** * Is a module already published */ static isPublished(module: IndexedModule): ChildProcess { let args: string[]; switch (Runtime.workspace.manager) { case 'npm': case 'yarn': case 'pnpm': args = ['info', `${module.name}@${module.version}`, '--json']; break; } return spawn(Runtime.workspace.manager, args, { cwd: module.sourceFolder }); } /** * Validate published result */ static validatePublishedResult(result: ExecutionResult<string>): boolean { if (!result.valid && !result.stderr.includes('E404')) { throw new Error(result.stderr); } const parsed = JSONUtil.parseSafe<{ data: { dist?: { integrity?: string } } }>(result.stdout || '{}'); return parsed.data?.dist?.integrity !== undefined; } /** * Setting the version */ static version(modules: IndexedModule[], level: SemverLevel, preid?: string): ChildProcess { const moduleArgs = modules.flatMap(module => ['-w', module.sourceFolder]); let args: string[]; switch (Runtime.workspace.manager) { case 'npm': case 'yarn': case 'pnpm': args = ['version', '--no-workspaces-update', level, ...(preid ? ['--preid', preid] : [])]; break; } return spawn(Runtime.workspace.manager, [...args, ...moduleArgs], { cwd: Runtime.workspace.path, stdio: 'inherit' }); } /** * Dry-run packaging */ static dryRunPackaging(module: IndexedModule): ChildProcess { let args: string[]; switch (Runtime.workspace.manager) { case 'npm': case 'yarn': case 'pnpm': args = ['pack', '--dry-run']; break; } return spawn(Runtime.workspace.manager, args, { cwd: module.sourcePath }); } /** * Publish a module */ static publish(module: IndexedModule, dryRun: boolean | undefined): ChildProcess { if (dryRun) { return this.dryRunPackaging(module); } const versionTag = module.version.match(/^.*-(rc|alpha|beta|next)[.]\d+/)?.[1] ?? 'latest'; let args: string[]; switch (Runtime.workspace.manager) { case 'npm': case 'yarn': case 'pnpm': args = ['publish', '--tag', versionTag, '--access', 'public']; break; } return spawn(Runtime.workspace.manager, args, { cwd: module.sourcePath }); } /** * Write package */ static async writePackageIfChanged(modulePath: string, pkg: Package): Promise<void> { const final = JSON.stringify(pkg, null, 2); const target = path.resolve(modulePath, 'package.json'); const current = (await fs.readFile(target, 'utf8').catch(() => '')).trim(); if (final !== current) { await fs.writeFile(target, `${final}\n`, 'utf8'); } } /** * Synchronize all workspace modules to have the correct versions from the current packages */ static async synchronizeVersions(): Promise<Record<string, string>> { const versions: Record<string, string> = {}; const folders = (await CliModuleUtil.findModules('workspace')).map(module => module.sourcePath); const packages = folders.map(folder => { const pkg = PackageUtil.readPackage(folder, true); versions[pkg.name] = `^${pkg.version}`; return { folder, pkg }; }); for (const { pkg } of packages) { for (const group of [ pkg.dependencies ?? {}, pkg.devDependencies ?? {}, pkg.optionalDependencies ?? {}, pkg.peerDependencies ?? {} ]) { for (const [module, ver] of Object.entries(versions)) { if (module in group && !/^[*]|(file:.*)$/.test(group[module])) { group[module] = ver; } } } } for (const { folder, pkg } of packages) { await this.writePackageIfChanged(folder, pkg); } return versions; } }