Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/scripts/publish-packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ try {
}

const version = normalizedVersion.npmVersion;
const distTag = normalizedVersion.distTag;
const rootPackagePath = path.join(ROOT, "package.json");
const rootPackage = JSON.parse(fs.readFileSync(rootPackagePath, "utf8"));
const platforms = rootPackage.customerioCli?.platforms || [];
Expand Down Expand Up @@ -183,6 +184,9 @@ function publish(packageDir) {
const args = dryRun
? ["publish", "--dry-run", "--access", "public", "--registry", registry.url]
: ["publish", "--access", "public", "--registry", registry.url];
if (distTag !== "latest") {
args.push("--tag", distTag);
}
args.push(...registry.publishArgs);
execFileSync("npm", args, { cwd: packageDir, stdio: "inherit" });
}
Expand Down
11 changes: 9 additions & 2 deletions .github/scripts/release-version.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
function normalizeVersion(input) {
const match = String(input || "")
.trim()
.match(/^v?(\d+\.\d+\.\d+)$/);
.match(/^v?(\d+\.\d+\.\d+(?:-([a-zA-Z][a-zA-Z0-9]*(?:\.[a-zA-Z0-9]+)*))?)$/);

if (!match) {
throw new Error("version must use the exact X.Y.Z or vX.Y.Z format");
throw new Error(
"version must use X.Y.Z or X.Y.Z-<prerelease> format (e.g. 1.2.3, 1.2.3-alpha.1)"
);
}

const version = match[1];
const prerelease = match[2] || null;
const tag = `v${version}`;
const distTag = prerelease ? prerelease.split(".")[0] : "latest";

return {
npmVersion: version,
tag,
tagRef: `refs/tags/${tag}`,
prerelease,
distTag,
};
}

Expand Down
21 changes: 18 additions & 3 deletions .github/scripts/release-workflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ function validateDispatch(env = process.env) {
return ctx;
}

if (ctx.prerelease) {
if (!ctx.githubRef.startsWith("refs/heads/") && ctx.githubRef !== ctx.tagRef) {
fail(`prerelease must be dispatched from a branch or ${ctx.tagRef}`);
}
return ctx;
}

if (ctx.githubRef !== "refs/heads/main" && ctx.githubRef !== ctx.tagRef) {
fail(`real release must be dispatched from refs/heads/main or ${ctx.tagRef}`);
}
Expand Down Expand Up @@ -166,15 +173,23 @@ function assertLocalTagDoesNotExist(tag) {
function tagAndDispatch(env = process.env) {
const ctx = validateDispatch(env);

if (ctx.dryRun || ctx.resumeExistingNpm || ctx.githubRef !== "refs/heads/main") {
fail("tag-and-dispatch is only valid for real releases dispatched from refs/heads/main");
if (ctx.dryRun || ctx.resumeExistingNpm) {
fail("tag-and-dispatch is only valid for real releases dispatched from a branch");
}
if (!ctx.githubRef.startsWith("refs/heads/")) {
fail("tag-and-dispatch must be dispatched from a branch");
}
if (!ctx.prerelease && ctx.githubRef !== "refs/heads/main") {
fail("stable releases must be dispatched from refs/heads/main");
}
if (!ctx.githubRepository) {
fail("GITHUB_REPOSITORY must be set");
}

assertCheckoutSha(ctx);
assertOriginMainSha(ctx);
if (!ctx.prerelease) {
assertOriginMainSha(ctx);
}
assertLocalTagDoesNotExist(ctx.tag);
if (remoteTagExists(ctx.tag)) {
fail(`tag ${ctx.tag} already exists on origin`);
Expand Down
57 changes: 56 additions & 1 deletion .github/scripts/release-workflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,39 @@ assert.deepStrictEqual(normalizeVersion("1.2.3"), {
npmVersion: "1.2.3",
tag: "v1.2.3",
tagRef: "refs/tags/v1.2.3",
prerelease: null,
distTag: "latest",
});
assert.deepStrictEqual(normalizeVersion("v1.2.3"), {
npmVersion: "1.2.3",
tag: "v1.2.3",
tagRef: "refs/tags/v1.2.3",
prerelease: null,
distTag: "latest",
});
assert.deepStrictEqual(normalizeVersion("1.2.3-alpha.1"), {
npmVersion: "1.2.3-alpha.1",
tag: "v1.2.3-alpha.1",
tagRef: "refs/tags/v1.2.3-alpha.1",
prerelease: "alpha.1",
distTag: "alpha",
});
assert.deepStrictEqual(normalizeVersion("v1.2.3-beta.2"), {
npmVersion: "1.2.3-beta.2",
tag: "v1.2.3-beta.2",
tagRef: "refs/tags/v1.2.3-beta.2",
prerelease: "beta.2",
distTag: "beta",
});
assert.deepStrictEqual(normalizeVersion("1.0.0-rc.1"), {
npmVersion: "1.0.0-rc.1",
tag: "v1.0.0-rc.1",
tagRef: "refs/tags/v1.0.0-rc.1",
prerelease: "rc.1",
distTag: "rc",
});

for (const version of ["v1", "1.2", "version=foo", "1.2.3-beta.1", "1.2.3+build.1"]) {
for (const version of ["v1", "1.2", "version=foo", "1.2.3+build.1"]) {
assert.throws(() => normalizeVersion(version), /version must use/);
}

Expand Down Expand Up @@ -72,6 +97,27 @@ assert.throws(
/real release must be dispatched/
);

// prerelease: allowed from any branch or matching tag
assert.doesNotThrow(() =>
validateDispatch(env({
VERSION_INPUT: "1.2.3-alpha.1",
GITHUB_REF: "refs/heads/my-feature-branch",
}))
);
assert.doesNotThrow(() =>
validateDispatch(env({
VERSION_INPUT: "1.2.3-alpha.1",
GITHUB_REF: "refs/tags/v1.2.3-alpha.1",
}))
);
assert.throws(
() => validateDispatch(env({
VERSION_INPUT: "1.2.3-alpha.1",
GITHUB_REF: "refs/tags/v1.2.3-alpha.2",
})),
/prerelease must be dispatched/
);

assert.doesNotThrow(() =>
validateDispatch(env({
DRY_RUN: "false",
Expand Down Expand Up @@ -100,6 +146,15 @@ assert.deepStrictEqual(resolveVersion({ VERSION_INPUT: "v1.2.3" }), {
npmVersion: "1.2.3",
tag: "v1.2.3",
tagRef: "refs/tags/v1.2.3",
prerelease: null,
distTag: "latest",
});
assert.deepStrictEqual(resolveVersion({ VERSION_INPUT: "1.0.0-alpha.1" }), {
npmVersion: "1.0.0-alpha.1",
tag: "v1.0.0-alpha.1",
tagRef: "refs/tags/v1.0.0-alpha.1",
prerelease: "alpha.1",
distTag: "alpha",
});

console.log("release-workflow tests passed");
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
workflow_dispatch:
inputs:
version:
description: "Release version, e.g. 1.2.3 (blank = auto-bump the latest tag)"
description: "Release version, e.g. 1.2.3 or 1.2.3-alpha.1 (blank = auto-bump the latest tag)"
required: false
default: ""
type: string
Expand Down Expand Up @@ -71,7 +71,7 @@ jobs:
${{
!inputs.dry_run &&
!inputs.resume_existing_npm &&
github.ref == 'refs/heads/main'
startsWith(github.ref, 'refs/heads/')
}}
runs-on: ubuntu-latest
permissions:
Expand Down