Skip to content

Commit ee8f908

Browse files
committed
feat: automate version releases
1 parent 818dd47 commit ee8f908

16 files changed

Lines changed: 305 additions & 19 deletions

‎.github/workflows/docker-image.yml‎

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ name: Build and push Docker image
22

33
on:
44
workflow_dispatch:
5+
inputs:
6+
release_tag:
7+
description: Optional release tag created by Release Please
8+
required: false
9+
type: string
510
push:
611
branches:
712
- master
@@ -20,21 +25,71 @@ jobs:
2025
- name: Checkout
2126
uses: actions/checkout@v4
2227
with:
28+
ref: ${{ inputs.release_tag || github.ref }}
2329
submodules: true
2430

2531
- name: Extract metadata
2632
id: meta
2733
uses: docker/metadata-action@v5
2834
with:
2935
images: ${{ env.REGISTRY_IMAGE }}
36+
flavor: |
37+
latest=false
3038
tags: |
31-
type=ref,event=branch
32-
type=ref,event=pr
33-
type=semver,pattern={{version}}
34-
type=semver,pattern={{major}}.{{minor}}
39+
type=ref,event=branch,enable=${{ inputs.release_tag == '' }}
40+
type=ref,event=pr,enable=${{ inputs.release_tag == '' }}
41+
type=semver,pattern={{version}},enable=${{ inputs.release_tag == '' }}
42+
type=semver,pattern={{major}}.{{minor}},enable=${{ inputs.release_tag == '' }}
43+
type=semver,value=${{ inputs.release_tag }},pattern={{version}},enable=${{ inputs.release_tag != '' }}
44+
type=semver,value=${{ inputs.release_tag }},pattern={{major}}.{{minor}},enable=${{ inputs.release_tag != '' }}
3545
type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }}
36-
type=raw,value=beta,enable={{is_default_branch}}
37-
type=raw,value=latest,enable={{is_default_branch}}
46+
type=sha,prefix=edge-,enable=${{ github.ref == 'refs/heads/master' && inputs.release_tag == '' }}
47+
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }}
48+
49+
- name: Verify release version
50+
if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }}
51+
shell: bash
52+
env:
53+
RELEASE_TAG: ${{ inputs.release_tag || github.ref_name }}
54+
run: |
55+
source_version="$(tr -d '[:space:]' < VERSION)"
56+
tag_version="${RELEASE_TAG#v}"
57+
test "$source_version" = "$tag_version" || {
58+
echo "Version mismatch: VERSION=$source_version, tag=$tag_version" >&2
59+
exit 1
60+
}
61+
62+
- name: Resolve source revision
63+
id: source-revision
64+
shell: bash
65+
run: |
66+
revision="$(git rev-parse HEAD)"
67+
echo "value=$revision" >> "$GITHUB_OUTPUT"
68+
echo "short=${revision::12}" >> "$GITHUB_OUTPUT"
69+
70+
- name: Resolve latest frontend revisions
71+
id: frontend-revisions
72+
shell: bash
73+
run: |
74+
frontend_2024_ref="$(git ls-remote https://github.com/vastsa/FileCodeBoxFronted.git HEAD | cut -f1)"
75+
frontend_2023_ref="$(git ls-remote https://github.com/vastsa/FileCodeBoxFronted2023.git HEAD | cut -f1)"
76+
test -n "$frontend_2024_ref"
77+
test -n "$frontend_2023_ref"
78+
echo "frontend_2024=$frontend_2024_ref" >> "$GITHUB_OUTPUT"
79+
echo "frontend_2023=$frontend_2023_ref" >> "$GITHUB_OUTPUT"
80+
81+
- name: Resolve application version
82+
id: app-version
83+
shell: bash
84+
env:
85+
RELEASE_TAG: ${{ inputs.release_tag }}
86+
run: |
87+
source_version="$(tr -d '[:space:]' < VERSION)"
88+
if [[ "$GITHUB_REF" == refs/tags/v* || -n "$RELEASE_TAG" ]]; then
89+
echo "value=$source_version" >> "$GITHUB_OUTPUT"
90+
else
91+
echo "value=${source_version}-dev+${{ steps.source-revision.outputs.short }}" >> "$GITHUB_OUTPUT"
92+
fi
3893
3994
- name: Set up QEMU
4095
uses: docker/setup-qemu-action@v3
@@ -60,6 +115,11 @@ jobs:
60115
push: true
61116
tags: ${{ steps.meta.outputs.tags }}
62117
labels: ${{ steps.meta.outputs.labels }}
118+
build-args: |
119+
APP_VERSION=${{ steps.app-version.outputs.value }}
120+
VCS_REF=${{ steps.source-revision.outputs.value }}
121+
FRONTEND_2024_REF=${{ steps.frontend-revisions.outputs.frontend_2024 }}
122+
FRONTEND_2023_REF=${{ steps.frontend-revisions.outputs.frontend_2023 }}
63123
cache-from: type=gha
64124
cache-to: type=gha,mode=max
65125
provenance: false

‎.github/workflows/release.yml‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Create release
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
workflow_dispatch:
8+
9+
permissions:
10+
actions: write
11+
contents: write
12+
issues: write
13+
pull-requests: write
14+
15+
concurrency:
16+
group: release-${{ github.ref }}
17+
cancel-in-progress: false
18+
19+
jobs:
20+
release-please:
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 30
23+
steps:
24+
- name: Create or update release
25+
id: release
26+
uses: googleapis/release-please-action@v4
27+
with:
28+
config-file: release-please-config.json
29+
manifest-file: .release-please-manifest.json
30+
31+
- name: Merge release pull request
32+
if: ${{ steps.release.outputs.prs_created == 'true' }}
33+
env:
34+
GH_TOKEN: ${{ github.token }}
35+
RELEASE_PR: ${{ fromJSON(steps.release.outputs.pr).number }}
36+
run: |
37+
for attempt in {1..120}; do
38+
if gh pr merge "$RELEASE_PR" --squash --delete-branch; then
39+
break
40+
fi
41+
if [[ "$attempt" -eq 120 ]]; then
42+
echo "Timed out waiting for release PR #$RELEASE_PR to merge" >&2
43+
exit 1
44+
fi
45+
sleep 10
46+
done
47+
gh workflow run release.yml --ref master
48+
49+
- name: Publish release image
50+
if: ${{ steps.release.outputs.release_created == 'true' }}
51+
env:
52+
GH_TOKEN: ${{ github.token }}
53+
RELEASE_TAG: ${{ steps.release.outputs.tag_name }}
54+
run: gh workflow run docker-image.yml --ref master -f release_tag="$RELEASE_TAG"

‎.release-please-manifest.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "2.4.0"
3+
}

‎Dockerfile‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,53 @@
22
# 前端产物与 CPU 架构无关,使用原生构建平台避免在 QEMU 中运行 Node/pnpm。
33
FROM --platform=$BUILDPLATFORM node:20-alpine AS frontend-builder
44

5+
ARG FRONTEND_2024_REF=main
6+
ARG FRONTEND_2023_REF=main
7+
58
RUN apk add --no-cache git python3 make g++
69

710
RUN corepack enable && \
811
corepack prepare pnpm@9.15.9 --activate
912

1013
WORKDIR /build
1114

12-
# 克隆并构建 2024 主题
13-
RUN git clone --depth 1 https://github.com/vastsa/FileCodeBoxFronted.git /build/fronted-2024 && \
15+
# 克隆并构建固定版本的 2024 主题
16+
RUN git clone --filter=blob:none --no-checkout https://github.com/vastsa/FileCodeBoxFronted.git /build/fronted-2024 && \
1417
cd /build/fronted-2024 && \
18+
git fetch --depth 1 origin "${FRONTEND_2024_REF}" && \
19+
git checkout --detach FETCH_HEAD && \
1520
pnpm install --frozen-lockfile --prod=false && \
16-
pnpm run build
21+
VITE_GIT_COMMIT="$(git rev-parse HEAD)" pnpm run build
1722

18-
# 克隆并构建 2023 主题
19-
RUN git clone --depth 1 https://github.com/vastsa/FileCodeBoxFronted2023.git /build/fronted-2023 && \
23+
# 克隆并构建固定版本的 2023 主题
24+
RUN git clone --filter=blob:none --no-checkout https://github.com/vastsa/FileCodeBoxFronted2023.git /build/fronted-2023 && \
2025
cd /build/fronted-2023 && \
26+
git fetch --depth 1 origin "${FRONTEND_2023_REF}" && \
27+
git checkout --detach FETCH_HEAD && \
2128
npm install --legacy-peer-deps && \
2229
npm run build
2330

2431
# 第二阶段:构建最终镜像
2532
FROM python:3.12-slim-bookworm
33+
ARG APP_VERSION
34+
ARG VCS_REF=unknown
35+
ARG FRONTEND_2024_REF=main
36+
ARG FRONTEND_2023_REF=main
2637
LABEL author="Lan"
2738
LABEL email="xzu@live.com"
39+
LABEL org.opencontainers.image.version="${APP_VERSION}"
40+
LABEL org.opencontainers.image.revision="${VCS_REF}"
41+
LABEL org.opencontainers.image.filecodebox.frontend-2024-revision="${FRONTEND_2024_REF}"
42+
LABEL org.opencontainers.image.filecodebox.frontend-2023-revision="${FRONTEND_2023_REF}"
2843

2944
WORKDIR /app
3045

3146
# 复制项目文件(通过 .dockerignore 排除不必要的文件)
3247
COPY . .
3348

49+
# 分支镜像使用带提交号的开发版本;正式镜像使用 VERSION 中的版本。
50+
ENV APP_VERSION="${APP_VERSION}"
51+
3452
# 设置时区
3553
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
3654
echo 'Asia/Shanghai' > /etc/timezone

‎VERSION‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2.4.0

‎core/version.py‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,20 @@
1-
APP_VERSION = "2.4.0"
1+
import os
2+
import re
3+
from pathlib import Path
4+
5+
6+
VERSION_FILE = Path(__file__).resolve().parents[1] / "VERSION"
7+
VERSION_PATTERN = re.compile(
8+
r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$"
9+
)
10+
11+
12+
def load_app_version() -> str:
13+
version = os.getenv("APP_VERSION") or VERSION_FILE.read_text(encoding="utf-8")
14+
version = version.strip()
15+
if not VERSION_PATTERN.fullmatch(version):
16+
raise RuntimeError(f"无效的应用版本号: {version!r}")
17+
return version
18+
19+
20+
APP_VERSION = load_app_version()

‎docker-compose.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
version: "3"
22
services:
33
file-code-box:
4-
image: lanol/filecodebox:latest
4+
image: lanol/filecodebox:2.4.0 # x-release-please-version
55
volumes:
66
- fcb-data:/app/data:rw
77
restart: unless-stopped
88
ports:
99
- "12345:12345"
1010
volumes:
1111
fcb-data:
12-
external: false
12+
external: false

‎docs/.vitepress/config.mts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export default defineConfig({
5555
{ text: '存储配置', link: '/guide/storage' },
5656
{ text: '安全设置', link: '/guide/security' },
5757
{ text: '系统配置', link: '/guide/configuration' },
58+
{ text: '版本发布', link: '/guide/releasing' },
5859
],
5960
},
6061
],
@@ -148,6 +149,7 @@ export default defineConfig({
148149
text: 'System Configuration',
149150
link: '/en/guide/configuration',
150151
},
152+
{ text: 'Releasing', link: '/en/guide/releasing' },
151153
],
152154
},
153155
],

‎docs/en/changelog.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## [Unreleased]
22

3+
<!-- Current release: 2.4.0 x-release-please-version -->
4+
35
### Added
46

57
- Added a global storage capacity limit with concurrency-safe reservations for regular, chunked, presigned, and admin file uploads.

‎docs/en/guide/releasing.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Releasing
2+
3+
The project uses Semantic Versioning and Conventional Commits.
4+
5+
Complete these settings once in both GitHub repositories:
6+
7+
- In `Settings → Actions → General → Workflow permissions`, grant read/write
8+
access and allow GitHub Actions to create pull requests.
9+
- Keep squash merging enabled. The release workflow waits for required checks
10+
before merging the release pull request.
11+
- Add `DOCKER_USERNAME` and `DOCKER_PASSWORD` Actions secrets to the backend repository.
12+
13+
1. Merge commits using Conventional Commit types such as `feat:` and `fix:` into the default branch.
14+
2. Release Please creates and merges the release pull request and updates `VERSION` and the changelog.
15+
3. The workflow then creates `vX.Y.Z`, a GitHub Release, and dispatches the production image build.
16+
4. Releases publish `X.Y.Z`, `X.Y`, and `latest`; branches publish `dev` or `edge-*` images.
17+
5. Each image build resolves the latest commit from both frontend default branches automatically.
18+
19+
Every `fix:`, `feat:`, or breaking change merged into the default branch therefore
20+
creates a Patch, Minor, or Major release automatically. Non-release commits such
21+
as `docs:` and `chore:` do not publish a release on their own.
22+
23+
The backend runtime version comes from `VERSION`. Container branch builds inject a
24+
commit-qualified development version through `APP_VERSION`; release tags must exactly
25+
match `VERSION`. OCI image labels record the resolved backend and frontend commits for traceability.

0 commit comments

Comments
 (0)