mirror of
https://github.com/changesets/action
synced 2026-10-26 16:39:56 +08:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8187684e2 | ||
|
|
82b288dc38 | ||
|
|
7056145900 | ||
|
|
5e8919cc41 | ||
|
|
f8d9125b62 | ||
|
|
3fedbeed23 | ||
|
|
a60abaee45 |
+35
-22
@@ -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
@@ -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
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user