UNPKG

semantic-release-npm-workspaces-monorepo

Version:
80 lines (79 loc) 3.11 kB
import fs from 'fs/promises'; import * as path from 'node:path'; import { SETTINGS } from '../settings.js'; export default class ScanPublishOrder { get packageOrder() { return [...this._packageOrder]; } get packagesOrderPath() { const entries = Object.entries(this._packageContentMap); return this._packageOrder.map(packageName => { const packageFound = entries.find(([, packageContent]) => packageContent.name === packageName); return packageFound[0]; }); } constructor(_scanLocation = SETTINGS.workspace) { this._scanLocation = _scanLocation; this._packageContentMap = {}; this._packageOrder = []; } async _readAllPackages() { const packages = await fs.readdir(this._scanLocation, { withFileTypes: true, }); for (const packageState of packages) { if (packageState.isFile()) continue; const packagePath = path.join(this._scanLocation, packageState.name); const packageJsonPath = path.join(packagePath, 'package.json'); try { if (await fs.stat(packageJsonPath).then(stat => stat.isFile())) { const packageContent = await fs .readFile(packageJsonPath, 'utf-8') .then(JSON.parse); this._packageContentMap[packagePath] = packageContent; } } catch (error) { console.warn(`Skipping package at ${packagePath}: ${error.message}`); continue; } } } async _orderByDependencies() { let somethingChanged = true; while (somethingChanged) { somethingChanged = false; for (const content of Object.values(this._packageContentMap)) { if (this._packageOrder.includes(content.name)) { continue; } if (this._checkOkToBeNextInOrder(content.dependencies)) { this._packageOrder.push(content.name); somethingChanged = true; } } } } _checkOkToBeNextInOrder(dependencies = {}) { const monorepoIncludesDeps = Object.values(this._packageContentMap).map(x => x.name); for (const [key] of Object.entries(dependencies)) { if (monorepoIncludesDeps.includes(key) && !this._packageOrder.includes(key)) { return false; } } return true; } _assertMissingPackages() { const missingPackages = Object.values(this._packageContentMap).filter(content => !this._packageOrder.includes(content.name)); if (missingPackages.length) { throw new Error(`Missing packages: ${missingPackages.map(content => content.name).join(', ')}, probably circular dependencies`); } } async scan() { await this._readAllPackages(); await this._orderByDependencies(); this._assertMissingPackages(); } }