feat(web-release): enhance release metadata handling and testing
- Updated `release_metadata` function to support manual testing from the main branch without publishing. - Added tests to verify behavior for manual main runs, tag releases, and restrictions on other branches. - Modified workflow to conditionally publish images based on the event type and added logging for manual test results. - Enhanced documentation to clarify manual testing procedures and workflow behavior.
This commit is contained in:
1 parent
29be430b6a
commit
446e1a768c
5 files changed
+57
-11
No files matched your search
@@ -24,6 +24,7 @@ class VersionTests(unittest.TestCase):
|
||||
self.assertEqual(metadata["version"], version)
|
||||
self.assertEqual(metadata["image_tag"], version)
|
||||
self.assertEqual(metadata["stable"], stable)
|
||||
self.assertEqual(metadata["publish"], "true")
|
||||
self.assertEqual(metadata["image"], "ghcr.io/nyakang/nyaterm-web")
|
||||
|
||||
def test_build_metadata_is_preserved_in_version_and_encoded_in_tag(self):
|
||||
@@ -38,6 +39,25 @@ class VersionTests(unittest.TestCase):
|
||||
with self.subTest(ref=ref), self.assertRaises(ValueError):
|
||||
release.release_metadata(ref, "1.2.12", "nyakang/nyaterm")
|
||||
|
||||
def test_manual_main_runs_without_publishing(self):
|
||||
metadata = release.release_metadata(
|
||||
"refs/heads/main", "1.2.12", "nyakang/nyaterm", "workflow_dispatch"
|
||||
)
|
||||
self.assertEqual(metadata["version"], "1.2.12")
|
||||
self.assertEqual(metadata["publish"], "false")
|
||||
|
||||
def test_manual_tag_still_publishes(self):
|
||||
metadata = release.release_metadata(
|
||||
"refs/tags/v1.2.12", "1.2.12", "nyakang/nyaterm", "workflow_dispatch"
|
||||
)
|
||||
self.assertEqual(metadata["publish"], "true")
|
||||
|
||||
def test_other_branches_cannot_be_manually_released(self):
|
||||
with self.assertRaises(ValueError):
|
||||
release.release_metadata(
|
||||
"refs/heads/feature", "1.2.12", "nyakang/nyaterm", "workflow_dispatch"
|
||||
)
|
||||
|
||||
def test_invalid_semver_fails(self):
|
||||
for version in [
|
||||
"1.2",
|
||||
|
||||
@@ -33,10 +33,13 @@ def version_parts(version):
|
||||
return tuple(int(match[i]) for i in (1, 2, 3)), prerelease
|
||||
|
||||
|
||||
def release_metadata(ref, package_version, repository):
|
||||
if not ref.startswith("refs/tags/v"):
|
||||
raise ValueError("Dispatch against a version tag (refs/tags/v<SemVer>).")
|
||||
version = ref.removeprefix("refs/tags/v")
|
||||
def release_metadata(ref, package_version, repository, event_name="push"):
|
||||
test_only = ref == "refs/heads/main" and event_name == "workflow_dispatch"
|
||||
if not test_only and not ref.startswith("refs/tags/v"):
|
||||
raise ValueError(
|
||||
"Select main for a manual test, or a version tag for a release."
|
||||
)
|
||||
version = package_version if test_only else ref.removeprefix("refs/tags/v")
|
||||
_, prerelease = version_parts(version)
|
||||
if version != package_version:
|
||||
raise ValueError(
|
||||
@@ -51,6 +54,7 @@ def release_metadata(ref, package_version, repository):
|
||||
"version": version,
|
||||
"image_tag": image_tag,
|
||||
"stable": str(prerelease is None).lower(),
|
||||
"publish": str(not test_only).lower(),
|
||||
"image": f"ghcr.io/{owner}/nyaterm-web",
|
||||
}
|
||||
|
||||
@@ -199,11 +203,16 @@ def main():
|
||||
os.environ["GITHUB_REF"],
|
||||
json.loads(Path("package.json").read_text())["version"],
|
||||
os.environ["GITHUB_REPOSITORY"],
|
||||
os.environ["GITHUB_EVENT_NAME"],
|
||||
)
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
|
||||
for key, value in metadata.items():
|
||||
output.write(f"{key}={value}\n")
|
||||
print(f"Validated Web release {metadata['version']}")
|
||||
mode = "release" if metadata["publish"] == "true" else "test (no publishing)"
|
||||
message = f"Validated Web {mode}: {metadata['version']}"
|
||||
print(message)
|
||||
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as summary:
|
||||
summary.write(message + "\n")
|
||||
elif args.command == "check-manifest":
|
||||
digest = verify_manifest(
|
||||
Path("manifest.json").read_bytes(),
|
||||
|
||||
@@ -22,23 +22,24 @@ jobs:
|
||||
image_tag: ${{ steps.metadata.outputs.image_tag }}
|
||||
image: ${{ steps.metadata.outputs.image }}
|
||||
stable: ${{ steps.metadata.outputs.stable }}
|
||||
publish: ${{ steps.metadata.outputs.publish }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Test release policy
|
||||
run: python3 -m unittest discover -s .github/scripts -p 'test_web_release.py'
|
||||
- name: Validate version tag
|
||||
- name: Resolve release or manual test mode
|
||||
id: metadata
|
||||
run: python3 .github/scripts/web-release.py metadata
|
||||
- name: Verify tag commit belongs to main
|
||||
- name: Verify selected commit belongs to main
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin main:refs/remotes/origin/main
|
||||
tag_commit=$(git rev-parse "${GITHUB_REF}^{commit}")
|
||||
if [ "$tag_commit" != "$(git rev-parse HEAD)" ] || ! git merge-base --is-ancestor "$tag_commit" origin/main; then
|
||||
echo "::error::The checked-out tag commit must belong to origin/main."
|
||||
ref_commit=$(git rev-parse "${GITHUB_REF}^{commit}")
|
||||
if [ "$ref_commit" != "$(git rev-parse HEAD)" ] || ! git merge-base --is-ancestor "$ref_commit" origin/main; then
|
||||
echo "::error::The checked-out commit must belong to origin/main."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -111,22 +112,32 @@ jobs:
|
||||
env:
|
||||
NYATERM_SMOKE_IMAGE: nyaterm-web:smoke
|
||||
run: node .github/scripts/web-release-smoke.mjs
|
||||
- name: Record manual test result
|
||||
if: needs.prepare.outputs.publish != 'true'
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
echo "Manual main test passed: linux/$ARCH, version $VERSION. No image was published." >> "$GITHUB_STEP_SUMMARY"
|
||||
- uses: actions/setup-go@v6
|
||||
if: needs.prepare.outputs.publish == 'true'
|
||||
with:
|
||||
go-version: stable
|
||||
cache: false
|
||||
- name: Install crane
|
||||
if: needs.prepare.outputs.publish == 'true'
|
||||
env:
|
||||
GOBIN: ${{ runner.temp }}/web-tools
|
||||
run: |
|
||||
go install github.com/google/go-containerregistry/cmd/crane@v0.20.3
|
||||
echo "$GOBIN" >> "$GITHUB_PATH"
|
||||
- uses: docker/login-action@v3
|
||||
if: needs.prepare.outputs.publish == 'true'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push tested image by digest
|
||||
if: needs.prepare.outputs.publish == 'true'
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
shell: bash
|
||||
@@ -144,6 +155,7 @@ jobs:
|
||||
echo "Image: $IMAGE@$digest"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: needs.prepare.outputs.publish == 'true'
|
||||
with:
|
||||
name: web-release-digest-${{ matrix.arch }}
|
||||
path: digests/
|
||||
@@ -159,6 +171,7 @@ jobs:
|
||||
publish:
|
||||
name: Publish multi-architecture version
|
||||
needs: [prepare, build]
|
||||
if: needs.prepare.outputs.publish == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
@@ -209,7 +222,7 @@ jobs:
|
||||
latest:
|
||||
name: Promote newer stable version to latest
|
||||
needs: [prepare, publish]
|
||||
if: needs.prepare.outputs.stable == 'true'
|
||||
if: needs.prepare.outputs.publish == 'true' && needs.prepare.outputs.stable == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
|
||||
@@ -47,6 +47,8 @@ docker compose -f deploy/web/docker-compose.yml down
|
||||
|
||||
[Build and Release Web](../../.github/workflows/build-web-release.yml) 在推送 `v<SemVer>` 标签时自动构建,也可在 Actions 中选择版本标签手动补发。标签版本必须与 `package.json` 一致,且提交属于 `main`。镜像发版与桌面发版独立,不等待 GitHub Release 公开。
|
||||
|
||||
测试 workflow 时,在 **Actions → Build and Release Web → Run workflow** 中选择 `main`。此模式运行完整部署 E2E、amd64 / arm64 构建及容器冒烟测试,不登录 GHCR、不发布镜像,也不更新 `latest`;结果显示在 Actions 摘要中。手动选择版本标签仍执行正常发版。Workflow 需先合入默认分支,才会出现在手动运行列表中。
|
||||
|
||||
完整版本标签不带 `v`,如 `1.2.12`、`1.3.0-beta.1`。SemVer 构建元数据中的 `+` 在镜像标签中编码为 `_`,OCI 版本标签仍保留原始版本。预发布、旧版本及同版本补发均不会更新 `latest`,也不发布 major/minor 浮动标签。版本和架构 digest 可在 Actions 摘要中查看。
|
||||
|
||||
首次发布后,维护者需在 GitHub 的 `nyaterm-web` package 设置中确认可见性为 **Public**,以支持匿名拉取。Workflow 使用仓库的 `GITHUB_TOKEN` 发布,无需额外注册表密码;仓库及组织策略须允许 GitHub Actions 写入该 package。
|
||||
|
||||
@@ -62,6 +62,8 @@ docker compose -f deploy/web/docker-compose.image.yml up -d
|
||||
|
||||
`latest` 仅由更高稳定版本更新;预发布及旧版本补发不会使它回退。首次发布后维护者需确认 GHCR package 为 Public,才能匿名拉取。发版触发条件、版本标签规则和补发入口见 [镜像发版说明](../deploy/web/README.md#镜像发版)。
|
||||
|
||||
如需先测试发版 workflow,可在 Actions 中手动选择 `main`,运行完整部署验收及双架构构建;此模式不发布镜像或修改 `latest`。选择版本标签则正常发版。
|
||||
|
||||
官方镜像构建路径固定为 `/`,运行时不能仅修改 `NYATERM_WEB_BASE_PATH` 来改为子路径;子路径部署需使用原源码构建方式。镜像 Compose 沿用原来的 `8080:8080`,使用本机反向代理时应改为 `127.0.0.1:8080:8080`。
|
||||
|
||||
### 从源码构建
|
||||
|
||||
Reference in new issue
Block a user