@mlaursen/release-script
Version:
The release script I normally use for packages I publish to npm
172 lines (164 loc) • 5.56 kB
JavaScript
import confirm from '@inquirer/confirm';
import { Octokit } from '@octokit/core';
import dotenv from 'dotenv';
import { execSync } from 'node:child_process';
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import input from '@inquirer/input';
async function createRelease(options) {
const { body, override, owner = "mlaursen", repo, prerelease, envPath = ".env.local", tagName, tokenName = "GITHUB_RELEASE_TOKEN" } = options;
dotenv.config({
path: envPath,
override,
quiet: true
});
const octokit = new Octokit({
auth: process.env[tokenName]
});
try {
const response = await octokit.request("POST /repos/{owner}/{repo}/releases", {
owner,
repo,
tag_name: tagName,
body,
prerelease
});
console.log(`Created release: ${response.data.html_url}`);
} catch (error) {
console.error(error);
console.log();
console.log("The npm token is most likely expired or never created. Update the `.env.local` to include the latest GITHUB_TOKEN");
console.log("Regenerate the token: https://github.com/settings/personal-access-tokens");
if (!await confirm({
message: "Try creating the Github release again?"
})) {
throw new Error("Unable to create a Github release");
}
return createRelease({
...options,
override: true
});
}
}
async function continueRelease() {
const confirmed = await confirm({
message: "Continue the release?"
});
if (!confirmed) {
throw new Error("Release cancelled");
}
}
async function getPackageManager() {
const rawPackageJson = await readFile(resolve(process.cwd(), "package.json"), "utf8");
const packageJson = JSON.parse(rawPackageJson);
if (typeof packageJson.volta === "object" && packageJson.volta) {
const { volta } = packageJson;
if ("pnpm" in volta) {
return "pnpm";
}
if ("yarn" in volta) {
return "yarn";
}
return "npm";
}
if (typeof packageJson.packageManager === "string") {
const mgr = packageJson.packageManagerreplace(/@.+/, "");
if (mgr === "pnpm" || mgr === "yarn" || mgr === "npm") {
return mgr;
}
throw new Error(`Unsupported package mananger "${mgr}" in package.json`);
}
throw new Error("Unable to find a package manager");
}
function getTags(local) {
const command = local ? "git tag --sort=-creatordate" : "git ls-remote --tags origin";
const tags = execSync(command).toString().trim();
const lines = tags.split(/\r?\n/);
if (local) {
return new Set(lines);
}
return new Set(lines.map((line)=>line.replace(/^.+refs\/tags\//, "").replace("^{}", "")));
}
function getUnpushedTags() {
const localTags = getTags(true);
const pushedTags = getTags(false);
return [
...localTags.difference(pushedTags)
];
}
async function getPendingReleases(options) {
const { packagePaths = {} } = options;
const unpushedTags = getUnpushedTags();
if (unpushedTags.length === 0) {
throw new Error("Unable to find any pending releases");
}
const pending = [];
for (const unpushedTag of unpushedTags){
if (!await confirm({
message: `Include ${unpushedTag} in the release?`
})) {
continue;
}
const name = unpushedTag.replace(/@\d+.+$/, "");
const path = await input({
message: `${name} CHANGELOG exists at:`,
default: packagePaths[name] ?? "."
});
const changelog = await readFile(resolve(process.cwd(), path, "CHANGELOG.md"), "utf8");
let body = "";
let isTracking = false;
const lines = changelog.split(/\r?\n/);
for (const line of lines){
if (line.startsWith("## ")) {
if (isTracking) {
break;
}
isTracking = true;
body = line;
} else if (isTracking) {
body += `\n${line}`;
}
}
pending.push({
body,
tagName: unpushedTag
});
}
return pending;
}
const exec = (command, opts)=>{
console.log(command);
execSync(command, opts);
};
async function release(options) {
const { owner, repo, envPath, cleanCommand = "clean", buildCommand = "build", skipBuild = !buildCommand, versionMessage = "build(version): version package" } = options;
const pkgManager = await getPackageManager();
if (!skipBuild) {
exec(`${pkgManager} ${cleanCommand}`);
exec(`${pkgManager} ${buildCommand}`);
}
await continueRelease();
exec("pnpm changeset version", {
stdio: "inherit"
});
exec("git add -u");
await continueRelease();
exec(`git commit -m "${versionMessage}"`);
exec(`${pkgManager} changeset publish`, {
stdio: "inherit"
});
const pendingReleases = await getPendingReleases(options);
exec("git push --follow-tags");
for (const release of pendingReleases){
await createRelease({
owner,
repo,
body: release.body,
tagName: release.tagName,
envPath,
prerelease: /-(alpha|next|beta)\.\d+$/.test(release.tagName)
});
}
}
export { createRelease, release };
//# sourceMappingURL=index.mjs.map