Compare commits

...
3 changed files with 151 additions and 33 deletions
+35 -22
View File
@@ -1,11 +1,15 @@
import { Buffer } from "node:buffer";
import path from "node:path";
import { exec } from "@actions/exec";
import { exec, getExecOutput } from "@actions/exec";
import major from "semver/functions/major.js";
import prerelease from "semver/functions/prerelease.js";
import pkgJson from "../package.json" with { type: "json" };
const tag = `v${pkgJson.version}`;
const releaseLine = `v${pkgJson.version.split(".")[0]}`;
const isPrerelease = pkgJson.version.includes("-");
const prereleaseTag = prerelease(pkgJson.version)?.[0];
const releaseLine = `v${major(pkgJson.version)}${
prereleaseTag === undefined ? "" : `-${prereleaseTag}`
}`;
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required");
@@ -21,27 +25,36 @@ const gitEnv = {
process.chdir(path.join(import.meta.dirname, ".."));
await exec("git", ["checkout", "--detach"]);
// Stable timestamps make retries produce the same commit when dist is unchanged.
const { stdout } = await getExecOutput("git", [
"show",
"--no-patch",
"--format=%cI",
"HEAD",
]);
const commitDate = stdout.trim();
await exec("git", ["add", "--force", "dist"]);
await exec("git", ["commit", "-m", tag]);
await exec("git", ["commit", "-m", tag], {
env: {
...process.env,
GIT_AUTHOR_DATE: commitDate,
GIT_COMMITTER_DATE: commitDate,
},
});
await exec("changeset", ["git-tag"]);
if (isPrerelease) {
await exec("git", ["push", "origin", `refs/tags/${tag}`], {
await exec(
"git",
[
"push",
"--force",
// The action pushes tags through the API; override any push.followTags config.
"--no-follow-tags",
"origin",
`HEAD:refs/heads/${releaseLine}`,
],
{
env: gitEnv,
});
} else {
await exec(
"git",
[
"push",
"--force",
"--follow-tags",
"origin",
`HEAD:refs/heads/${releaseLine}`,
],
{
env: gitEnv,
},
);
}
},
);
+61 -8
View File
@@ -1,3 +1,4 @@
import assert from "node:assert/strict";
import { Buffer } from "node:buffer";
import * as core from "@actions/core";
import { exec, getExecOutput } from "@actions/exec";
@@ -67,6 +68,19 @@ function getHttpUrl(remoteUrl: string): string | undefined {
}
}
function isAlreadyExistingRefError(error: unknown) {
return (
typeof error === "object" &&
error !== null &&
"status" in error &&
"message" in error &&
typeof error.status === "number" &&
typeof error.message === "string" &&
error.status === 422 &&
error.message.includes("Reference already exists")
);
}
export class GitHub {
readonly #githubToken: string;
readonly octokit: Octokit;
@@ -212,18 +226,57 @@ export class GitHub {
);
}
async pushTag(tag: string) {
async #getRemoteTagCommit(tag: string) {
const { data: ref } = await this.octokit.rest.git.getRef({
...context.repo,
ref: `tags/${tag}`,
});
let target: { type: string; sha: string } = ref.object;
while (target.type === "tag") {
const { data: tagObject } = await this.octokit.rest.git.getTag({
...context.repo,
tag_sha: target.sha,
});
target = tagObject.object;
}
assert.equal(
target.type,
"commit",
`Tag ${tag} points to a ${target.type}, not a commit`,
);
return target.sha;
}
async pushTag(tag: string, commit = context.sha) {
if (!this.pushWithGitCli) {
return this.octokit.rest.git
.createRef({
try {
const { data: tagObject } = await this.octokit.rest.git.createTag({
...context.repo,
tag,
message: tag,
object: commit,
type: "commit",
});
await this.octokit.rest.git.createRef({
...context.repo,
ref: `refs/tags/${tag}`,
sha: context.sha,
})
.catch((err) => {
// Assuming tag was manually pushed in custom publish script
core.warning(`Failed to create tag ${tag}: ${err.message}`);
sha: tagObject.sha,
});
} catch (err) {
if (isAlreadyExistingRefError(err)) {
const remoteCommit = await this.#getRemoteTagCommit(tag);
assert.equal(
remoteCommit,
commit,
`Tag ${tag} points to commit ${remoteCommit}, expected ${commit}`,
);
// The tag was likely pushed by a custom publish script.
core.info(`Tag ${tag} already exists`);
return;
}
throw err;
}
return;
}
await exec("git", ["push", "origin", tag], {
cwd: this.cwd,
+55 -3
View File
@@ -1,3 +1,5 @@
import assert from "node:assert/strict";
import { Buffer } from "node:buffer";
import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import os from "node:os";
@@ -155,6 +157,45 @@ async function readChangesetsOutput(outputPath: string) {
return events;
}
type Release = {
pkg: Package;
tag: string;
commit?: string;
};
async function getTagCommits(cwd: string, tags: string[]) {
if (tags.length === 0) {
return [];
}
const refs = tags.map((tag) => `refs/tags/${tag}^{}`);
const { stdout } = await getExecOutput(
"git",
["cat-file", "--batch-check=%(objectname) %(objecttype)"],
{
cwd,
input: Buffer.from(`${refs.join("\n")}\n`),
},
);
const lines = stdout.trim().split(/\r?\n/);
assert.equal(
lines.length,
tags.length,
"Git returned an unexpected number of tag targets",
);
return lines.map((line, index) => {
if (line === `${refs[index]} missing`) {
return undefined;
}
const [commit, type] = line.split(" ");
assert.equal(
type,
"commit",
`Tag ${tags[index]} does not point to a commit`,
);
return commit;
});
}
export async function runPublish({
script,
fromPackDir,
@@ -214,7 +255,7 @@ export async function runPublish({
);
output = [];
}
let releases = output.map((event) => {
let releases: Release[] = output.map((event) => {
let pkg = packagesByName.get(event.packageName);
if (pkg === undefined) {
throw new Error(
@@ -232,11 +273,22 @@ export async function runPublish({
);
}
if (pushGitTags) {
const commits = await getTagCommits(
cwd,
releases.map(({ tag }) => tag),
);
releases = releases.map((release, index) => ({
...release,
commit: commits[index],
}));
}
if (createGithubReleases || pushGitTags) {
await Promise.all(
releases.map(async ({ pkg, tag }) => {
releases.map(async ({ pkg, tag, commit }) => {
if (pushGitTags) {
await github.pushTag(tag);
await github.pushTag(tag, commit);
}
if (createGithubReleases) {
await createRelease(octokit, { pkg, tagName: tag });