auri
Version:
Organize package changes and releases
179 lines (178 loc) • 6.28 kB
JavaScript
import * as fs from "fs/promises";
import * as childprocess from "child_process";
import { parsePackageJSON } from "../utils/package.js";
import { env } from "../utils/env.js";
import { parseSemver } from "../utils/semver.js";
import { parseGitHubGitRepositoryURL } from "../utils/github.js";
export async function publishScript() {
const npmToken = env("AURI_NPM_TOKEN");
if (!isSafeString(npmToken)) {
throw new Error("Invalid NPM token");
}
const npmTokenSafe = npmToken;
const githubToken = env("AURI_GITHUB_TOKEN");
let releaseFileBytes;
try {
releaseFileBytes = await fs.readFile(".RELEASE.md");
}
catch {
// File does no exist.
return;
}
const releaseFile = new TextDecoder().decode(releaseFileBytes);
const packageJSONFile = await fs.readFile("package.json");
const packageJSON = JSON.parse(packageJSONFile.toString());
const metadata = parsePackageJSON(packageJSON);
let repository;
try {
repository = parseGitHubGitRepositoryURL(metadata.repository);
}
catch {
throw new Error("Invalid GitHub repository URL");
}
const publishedVersions = await getPublishedVersions(metadata.name);
if (publishedVersions.includes(metadata.version)) {
return;
}
const releaseTag = calculateReleaseTag(metadata.version, publishedVersions);
try {
childprocess.execSync("npm install");
}
catch {
throw new Error("Failed to install dependencies");
}
try {
childprocess.execSync("npm run build");
}
catch {
throw new Error("Failed to build package");
}
if (releaseTag === ReleaseTag.Latest) {
try {
childprocess.execSync(`NODE_AUTH_TOKEN=${npmTokenSafe} npm publish --provenance --access=public`);
}
catch {
throw new Error("Failed to publish package as latest");
}
}
else if (releaseTag === ReleaseTag.Next) {
try {
childprocess.execSync(`NODE_AUTH_TOKEN=${npmTokenSafe} npm publish --provenance --access=public --tag=next`);
}
catch {
throw new Error("Failed to publish package as next");
}
}
else if (releaseTag === ReleaseTag.Legacy) {
try {
childprocess.execSync(`NODE_AUTH_TOKEN=${npmTokenSafe} npm publish --provenance --access=public --tag=legacy`);
}
catch {
throw new Error("Failed to publish package as legacy");
}
}
else {
throw new Error("Invalid state");
}
const currentBranch = getCurrentBranch();
try {
await createGitHubRelease(githubToken, repository, currentBranch, metadata.version, releaseTag, releaseFile);
}
catch {
throw new Error("Failed to create GitHub release");
}
childprocess.execSync(`git config user.name "github-actions[bot]"`);
childprocess.execSync(`git config user.email "41898282+github-actions[bot]@users.noreply.github.com"`);
try {
childprocess.execSync(`git remote set-url origin https://x-access-token:${githubToken}@github.com/${repository.owner}/${repository.name}.git`);
}
catch {
throw new Error("Failed to access repository.");
}
await fs.rm(".RELEASE.md");
try {
childprocess.execSync("git add .");
childprocess.execSync(`git commit -m "delete .RELEASE.md"`);
childprocess.execSync(`git push origin ${currentBranch}`);
}
catch {
throw new Error("Failed to delete .RELEASE.md");
}
}
async function getPublishedVersions(name) {
const npmRegistryUrl = new URL(name, "https://registry.npmjs.org");
const npmRegistryResponse = await fetch(npmRegistryUrl);
if (!npmRegistryResponse.ok) {
throw new Error("Failed to fetch NPM data");
}
const npmRegistry = await npmRegistryResponse.json();
if (typeof npmRegistry !== "object" || npmRegistry === null) {
throw new Error("Failed to parse NPM data");
}
let publishedVersions;
if ("time" in npmRegistry && typeof npmRegistry.time === "object" && npmRegistry.time !== null) {
publishedVersions = Object.keys(npmRegistry.time);
}
else {
throw new Error("Failed to parse NPM data");
}
return publishedVersions;
}
function calculateReleaseTag(currentVersion, publishedVersions) {
const currentSemver = parseSemver(currentVersion);
if (currentSemver.next !== null) {
return ReleaseTag.Next;
}
for (const publishedVersion of publishedVersions) {
let publishedSemver;
try {
publishedSemver = parseSemver(publishedVersion);
}
catch {
// Ignore unknown version formats.
continue;
}
if (publishedSemver.next === null && publishedSemver.major > currentSemver.major) {
return ReleaseTag.Legacy;
}
}
return ReleaseTag.Latest;
}
var ReleaseTag;
(function (ReleaseTag) {
ReleaseTag[ReleaseTag["Latest"] = 0] = "Latest";
ReleaseTag[ReleaseTag["Next"] = 1] = "Next";
ReleaseTag[ReleaseTag["Legacy"] = 2] = "Legacy";
})(ReleaseTag || (ReleaseTag = {}));
async function createGitHubRelease(token, repository, branch, version, releaseTag, body) {
const requestBody = JSON.stringify({
tag_name: `v${version}`,
target_commitish: branch,
name: `v${version}`,
body: body,
make_latest: releaseTag === ReleaseTag.Latest ? "true" : "false",
prerelease: releaseTag === ReleaseTag.Next
});
const response = await fetch(`https://api.github.com/repos/${repository.owner}/${repository.name}/releases`, {
method: "POST",
body: requestBody,
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json"
}
});
if (response.body !== null) {
await response.body.cancel();
}
if (!response.ok) {
throw new Error("Failed to create GitHub release");
}
}
function isSafeString(s) {
return /[a-zA-Z0-9.-_]*/.test(s);
}
function getCurrentBranch() {
const bytes = childprocess.execSync("git rev-parse --abbrev-ref HEAD");
const branch = new TextDecoder().decode(bytes).trim();
return branch;
}