UNPKG

@mlaursen/release-script

Version:

The release script I normally use for packages I publish to npm

172 lines (164 loc) 5.56 kB
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