chore: Update final release workflow (#6875)

This commit is contained in:
Louis Lam
2026-02-07 09:45:35 +08:00
committed by GitHub
parent ea8631f407
commit 258cdec026
10 changed files with 166 additions and 179 deletions

93
.github/workflows/release-final.yml vendored Normal file
View File

@@ -0,0 +1,93 @@
name: Final Release
on:
workflow_dispatch:
inputs:
version:
description: "Release version number (e.g., 2.1.0)"
required: true
type: string
previous_version:
description: "Previous version tag for changelog (e.g., 2.1.0-beta.3)"
required: true
type: string
dry_run:
description: "Dry Run (The docker image will not be pushed to registries. PR will still be created.)"
required: false
type: boolean
default: false
permissions:
contents: write
pull-requests: write
jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: master
persist-credentials: true
fetch-depth: 0 # Fetch all history for changelog generation
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: 24
- name: Create release branch
env:
VERSION: ${{ inputs.version }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git"
# Delete remote branch if it exists
git push origin --delete "release-${VERSION}" || true
# Delete local branch if it exists
git branch -D "release-${VERSION}" || true
# For testing purpose
# git checkout beta-workflow
git checkout -b "release-${VERSION}"
- name: Install dependencies
run: npm clean-install --no-fund
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Run release-final
env:
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_PREVIOUS_VERSION: ${{ inputs.previous_version }}
DRY_RUN: ${{ inputs.dry_run }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_RUN_ID: ${{ github.run_id }}
run: npm run release-final
- name: Upload dist.tar.gz as artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: dist-${{ inputs.version }}
path: ./tmp/dist.tar.gz
retention-days: 90

View File

@@ -7,7 +7,6 @@ import {
checkTagExists,
checkVersionFormat,
getRepoNames,
execSync,
checkReleaseBranch,
createDistTarGz,
createReleasePR,

View File

@@ -7,26 +7,39 @@ import {
checkTagExists,
checkVersionFormat,
getRepoNames,
pressAnyKey, execSync, uploadArtifacts, checkReleaseBranch
checkReleaseBranch,
createDistTarGz,
createReleasePR,
} from "./lib.mjs";
import semver from "semver";
const repoNames = getRepoNames();
const version = process.env.RELEASE_VERSION;
const githubToken = process.env.RELEASE_GITHUB_TOKEN;
const dryRun = process.env.DRY_RUN === "true";
const previousVersion = process.env.RELEASE_PREVIOUS_VERSION;
const branchName = `release-${version}`;
const githubRunId = process.env.GITHUB_RUN_ID;
if (dryRun) {
console.log("Dry run mode enabled. No images will be pushed.");
}
console.log("RELEASE_VERSION:", version);
if (!githubToken) {
console.error("GITHUB_TOKEN is required");
process.exit(1);
}
// Check if the current branch is "release"
checkReleaseBranch();
// Check if the current branch is "release-{version}"
checkReleaseBranch(branchName);
// Check if the version is a valid semver
checkVersionFormat(version);
// Check if the semver identifier is empty
const semverIdentifier = semver.prerelease(version);
console.log("Semver identifier:", semverIdentifier);
if (semverIdentifier) {
console.error("VERSION should not have a semver identifier for final release");
process.exit(1);
}
// Check if docker is running
checkDocker();
@@ -34,27 +47,42 @@ checkDocker();
await checkTagExists(repoNames, version);
// node extra/beta/update-version.js
execSync("node extra/update-version.js");
await import("../update-version.mjs");
// Create Pull Request (gh pr create will handle pushing the branch)
await createReleasePR(version, previousVersion, dryRun, branchName, githubRunId);
// Build frontend dist
buildDist();
// Build slim image (rootless)
buildImage(repoNames, [ "2-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
if (!dryRun) {
// Build slim image (rootless)
buildImage(
repoNames,
["2-slim-rootless", ver(version, "slim-rootless")],
"rootless",
"BASE_IMAGE=louislam/uptime-kuma:base2-slim"
);
// Build full image (rootless)
buildImage(repoNames, [ "2-rootless", ver(version, "rootless") ], "rootless");
// Build full image (rootless)
buildImage(repoNames, ["2-rootless", ver(version, "rootless")], "rootless");
// Build slim image
buildImage(repoNames, [ "next-slim", "2-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
// Build slim image
buildImage(
repoNames,
["next-slim", "2-slim", ver(version, "slim")],
"release",
"BASE_IMAGE=louislam/uptime-kuma:base2-slim"
);
// Build full image
buildImage(repoNames, [ "next", "2", version ], "release");
// Build full image
buildImage(repoNames, ["next", "2", version], "release");
} else {
console.log("Dry run mode - skipping image build and push.");
}
await pressAnyKey();
// Create dist.tar.gz
await createDistTarGz();
// npm run upload-artifacts
uploadArtifacts(version, githubToken);
// node extra/update-wiki-version.js
execSync("node extra/update-wiki-version.js");
// Removed update wiki to keep it simple
// Do this in the wiki repo instead

View File

@@ -1,22 +0,0 @@
const fs = require("fs");
// Read the file from private/sort-contributors.txt
const file = fs.readFileSync("private/sort-contributors.txt", "utf8");
// Convert to an array of lines
let lines = file.split("\n");
// Remove empty lines
lines = lines.filter((line) => line !== "");
// Remove duplicates
lines = [...new Set(lines)];
// Remove @weblate and @UptimeKumaBot
lines = lines.filter((line) => line !== "@weblate" && line !== "@UptimeKumaBot" && line !== "@louislam");
// Sort the lines
lines = lines.sort();
// Output the lines, concat with " "
console.log(lines.join(" "));

View File

@@ -1,3 +1,6 @@
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const pkg = require("../package.json");
const fs = require("fs");
const childProcess = require("child_process");
@@ -5,28 +8,28 @@ const util = require("../src/util");
util.polyfill();
const newVersion = process.env.RELEASE_VERSION;
const version = process.env.RELEASE_VERSION;
console.log("New Version: " + newVersion);
console.log("New Version: " + version);
if (!newVersion) {
if (!version) {
console.error("invalid version");
process.exit(1);
}
const exists = tagExists(newVersion);
const exists = tagExists(version);
if (!exists) {
// Process package.json
pkg.version = newVersion;
pkg.version = version;
// Replace the version: https://regex101.com/r/hmj2Bc/1
pkg.scripts.setup = pkg.scripts.setup.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
pkg.scripts.setup = pkg.scripts.setup.replace(/(git checkout )([^\s]+)/, `$1${version}`);
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 4) + "\n");
// Also update package-lock.json
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
const resultVersion = childProcess.spawnSync(npm, ["--no-git-tag-version", "version", newVersion], { shell: true });
const resultVersion = childProcess.spawnSync(npm, ["--no-git-tag-version", "version", version], { shell: true });
if (resultVersion.error) {
console.error(resultVersion.error);
console.error("error npm version!");
@@ -38,9 +41,10 @@ if (!exists) {
console.error("error update package-lock!");
process.exit(1);
}
commit(newVersion);
commit(version);
} else {
console.log("version exists");
console.log("version tag exists, please delete the tag or use another tag");
process.exit(1);
}
/**
@@ -59,6 +63,14 @@ function commit(version) {
if (stdout.includes("no changes added to commit")) {
throw new Error("commit error");
}
// Get the current branch name
res = childProcess.spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
let branchName = res.stdout.toString().trim();
console.log("Current branch:", branchName);
// Git push the branch
childProcess.spawnSync("git", ["push", "origin", branchName, "--force"], { stdio: "inherit" });
}
/**

View File

@@ -1,58 +0,0 @@
const childProcess = require("child_process");
const fs = require("fs");
const newVersion = process.env.RELEASE_VERSION;
if (!newVersion) {
console.log("Missing version");
process.exit(1);
}
updateWiki(newVersion);
/**
* Update the wiki with new version number
* @param {string} newVersion Version to update to
* @returns {void}
*/
function updateWiki(newVersion) {
const wikiDir = "./tmp/wiki";
const howToUpdateFilename = "./tmp/wiki/🆙-How-to-Update.md";
safeDelete(wikiDir);
childProcess.spawnSync("git", ["clone", "https://github.com/louislam/uptime-kuma.wiki.git", wikiDir]);
let content = fs.readFileSync(howToUpdateFilename).toString();
// Replace the version: https://regex101.com/r/hmj2Bc/1
content = content.replace(/(git checkout )([^\s]+)/, `$1${newVersion}`);
fs.writeFileSync(howToUpdateFilename, content);
childProcess.spawnSync("git", ["add", "-A"], {
cwd: wikiDir,
});
childProcess.spawnSync("git", ["commit", "-m", `Update to ${newVersion}`], {
cwd: wikiDir,
});
console.log("Pushing to Github");
childProcess.spawnSync("git", ["push"], {
cwd: wikiDir,
});
safeDelete(wikiDir);
}
/**
* Check if a directory exists and then delete it
* @param {string} dir Directory to delete
* @returns {void}
*/
function safeDelete(dir) {
if (fs.existsSync(dir)) {
fs.rm(dir, {
recursive: true,
});
}
}

View File

@@ -1,64 +0,0 @@
#!/usr/bin/env bash
#
# Author: Stefan Buck
# License: MIT
# https://gist.github.com/stefanbuck/ce788fee19ab6eb0b4447a85fc99f447
#
#
# This script accepts the following parameters:
#
# * owner
# * repo
# * tag
# * filename
# * github_api_token
#
# Script to upload a release asset using the GitHub API v3.
#
# Example:
#
# upload-github-release-asset.sh github_api_token=TOKEN owner=stefanbuck repo=playground tag=v0.1.0 filename=./build.zip
#
# Check dependencies.
set -e
xargs=$(which gxargs || which xargs)
# Validate settings.
[ "$TRACE" ] && set -x
CONFIG=$@
for line in $CONFIG; do
eval "$line"
done
# Define variables.
GH_API="https://api.github.com"
GH_REPO="$GH_API/repos/$owner/$repo"
GH_TAGS="$GH_REPO/releases/tags/$tag"
AUTH="Authorization: token $github_api_token"
WGET_ARGS="--content-disposition --auth-no-challenge --no-cookie"
CURL_ARGS="-LJO#"
if [[ "$tag" == 'LATEST' ]]; then
GH_TAGS="$GH_REPO/releases/latest"
fi
# Validate token.
curl -o /dev/null -sH "$AUTH" $GH_REPO || { echo "Error: Invalid repo, token or network issue!"; exit 1; }
# Read asset tags.
response=$(curl -sH "$AUTH" $GH_TAGS)
# Get ID of the asset based on given filename.
eval $(echo "$response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
[ "$id" ] || { echo "Error: Failed to get release id for tag: $tag"; echo "$response" | awk 'length($0)<100' >&2; exit 1; }
# Upload asset
echo "Uploading asset... "
# Construct url
GH_ASSET="https://uploads.github.com/repos/$owner/$repo/releases/$id/assets?name=$(basename $filename)"
curl "$GITHUB_OAUTH_BASIC" --data-binary @"$filename" -H "Authorization: token $github_api_token" -H "Content-Type: application/octet-stream" $GH_ASSET

View File

@@ -63,7 +63,6 @@
"start-pr-test": "node extra/checkout-pr.mjs && npm install && npm run dev",
"build-healthcheck-armv7": "cross-env GOOS=linux GOARCH=arm GOARM=7 go build -x -o ./extra/healthcheck-armv7 ./extra/healthcheck.go",
"deploy-demo-server": "node extra/deploy-demo-server.js",
"sort-contributors": "node extra/sort-contributors.js",
"quick-run-nightly": "docker run --rm --env NODE_ENV=development -p 3001:3001 louislam/uptime-kuma:nightly2",
"start-dev-container": "cd docker && docker-compose -f docker-compose-dev.yml up --force-recreate",
"rebase-pr-to-1.23.X": "node extra/rebase-pr.js 1.23.X",