semantic-release-npm-workspaces-monorepo
Version:
Help you use semantic-release with npm workspaces
112 lines (111 loc) • 4.77 kB
JavaScript
import fs from 'fs/promises';
import { generatePackageSettings, SETTINGS } from '../settings.js';
import fetchRetry from 'fetch-retry';
import { readCacheStorage } from '../storage.js';
import semver from 'semver';
import * as simpleGit from 'simple-git';
import micromatch from 'micromatch';
import path from 'path';
const nodeFetchWithRetry = fetchRetry(fetch);
const FETCH_RETRY_OPTIONS = {
retries: 3,
retryDelay: 1000,
};
export default class UpdatePackages {
constructor(packagePath, _packages) {
this._packages = _packages;
this._packageJSONPath = path.join(packagePath, 'package.json');
const relativePath = path.relative(process.cwd(), packagePath);
this.settings = generatePackageSettings(relativePath);
}
async _updateVersions(data = {}) {
for (const key in data) {
if (this._packages.includes(key)) {
const { version, isPrerelease } = await UpdatePackages.getLatestVersion(key, this.settings);
data[key] = this.useVersionTemplate(data[key], version, isPrerelease);
}
}
}
async _readPackageJson() {
this._originalPackageContent = await fs
.readFile(this._packageJSONPath, 'utf-8')
.then(JSON.parse);
this.packageContent = structuredClone(this._originalPackageContent);
}
async savePackageJson(content = this.packageContent) {
await fs.writeFile(this._packageJSONPath, JSON.stringify(content, null, 2));
}
async restoreOriginalPackageJson() {
await this.savePackageJson(this._originalPackageContent);
}
async updateDeps() {
await this._readPackageJson();
await this._updateVersions(this.packageContent.dependencies);
await this._updateVersions(this.packageContent.devDependencies);
}
static async getLatestVersion(packageName, settings = SETTINGS) {
const cache = await readCacheStorage();
const cacheVersion = cache[packageName];
const branchInfo = await this.findBranchInfo(settings);
const isPrereleaseChannel = Boolean(branchInfo?.prerelease);
if (cacheVersion) {
console.log('Cache version for', packageName, 'is', cacheVersion);
return {
version: cacheVersion,
isPrerelease: isPrereleaseChannel,
};
}
else {
console.log('Cache version for', packageName, 'is not found');
}
try {
const packageSearch = await nodeFetchWithRetry(`${settings.registry}/${packageName}`, FETCH_RETRY_OPTIONS);
const packageSearchJson = await packageSearch.json();
const versions = packageSearchJson['dist-tags'];
const template = typeof branchInfo?.prerelease === 'string'
? branchInfo.prerelease
: branchInfo?.channel || '';
const channelNameReplaced = template.replaceAll('${name}', branchInfo?.name || '') ||
branchInfo?.name ||
'';
if (isPrereleaseChannel) {
const prereleaseVersion = versions[channelNameReplaced];
if (prereleaseVersion) {
return {
version: prereleaseVersion,
isPrerelease: true,
};
}
}
return {
version: versions[channelNameReplaced] ?? versions.latest,
isPrerelease: false,
};
}
catch (error) {
console.error(`Failed to get version from NPM for ${packageName}: ${error.message}`);
return {
version: '0.0.1',
isPrerelease: false,
};
}
}
useVersionTemplate(templateVersion, toVersion, isPrerelease = false) {
if (isPrerelease && this.settings.preReleaseVersionTemplate) {
return this.settings.preReleaseVersionTemplate.replace('${version}', toVersion);
}
const originalVersion = (semver.coerce(templateVersion, { includePrerelease: true }) ||
templateVersion).toString();
return templateVersion.replace(originalVersion, toVersion);
}
static async findBranchInfo(settings = SETTINGS) {
const ciBranch = process.env.CI_COMMIT_BRANCH ||
process.env.GITHUB_REF_NAME ||
process.env.CIRCLE_BRANCH ||
process.env.TRAVIS_BRANCH ||
process.env.BITBUCKET_BRANCH;
const branch = (this._cacheCurrentBranch ??=
ciBranch || (await simpleGit.simpleGit().branch()).current);
return settings.release.branches.find(x => typeof x === 'object' && micromatch.isMatch(branch, x.name));
}
}