deer-flow/.github/workflows/nightly.yaml
Zheng Feng a9145e426c
feat(frontend): show real project version on About page (#4126)
* feat(frontend): show real project version on About page

The About page heading was hardcoded "About DeerFlow 2.0" while the real
version is 2.1.0, kept in lockstep across backend/pyproject.toml,
frontend/package.json, and Chart.yaml by scripts/verify_versions.sh.
With the nightly workflow shipping unreleased main daily, a hardcoded
version drifts further from reality every day.

Wire the heading to the real version, nightly-aware:
- src/version.ts reads NEXT_PUBLIC_APP_VERSION (nightly override) with a
  package.json fallback for release/local builds.
- about-content.ts interpolates APP_VERSION into the heading; the
  "DeerFlow 1.0 and 2.0" milestone copy in acknowledgments is left as-is.
- Dockerfile adds ARG APP_VERSION / ENV NEXT_PUBLIC_APP_VERSION in the
  builder stage so nightly CI can stamp the version.
- nightly.yaml computes the nightly string once in prepare
  (<base>-nightly.<date>-<sha>, mirroring the chart scheme) and feeds it
  to both the frontend image build-arg and the chart publish (refactored
  to consume the shared output, so the displayed version and chart
  version can't drift). Release builds (container.yaml) are untouched
  and fall back to package.json.

Tests cover the version branches and the heading interpolation;
RELEASING.md notes the About page as a derived version consumer.

* test(frontend): assert positive About-page version value

Address review feedback on #4126: the unset-env assertion was negative-only
(checked the stale "2.0" literal was gone), so it would still pass if
APP_VERSION interpolated to "" or undefined. Mirror version.test.ts by
asserting the heading carries the real resolved APP_VERSION.

* ci(nightly): fail loudly on empty base version

Address review feedback on #4126: the prepare step's BASE (parsed from
Chart.yaml) had no empty-guard. A malformed/missing version would yield
`-nightly.<date>-<sha>` (invalid semver) that now flows into both the
chart publish and the frontend About-page version. Add `set -euo pipefail`
plus a `test -n "$BASE"` guard mirroring publish-chart. `pipefail` matters
- without it the pipeline's exit code is awk's, so `set -e` alone wouldn't
catch a grep miss.
2026-07-12 22:54:03 +08:00

235 lines
11 KiB
YAML

name: Nightly Build
# Nightly build of the three DeerFlow container images (backend, frontend,
# provisioner) and the Helm chart, published to GHCR from the default branch.
#
# This mirrors the tag-driven release workflows (container.yaml + chart.yaml)
# but trades the `v*` tag for date-based tags, since nightly ships unreleased
# `main`. The `verify-versions` gate is intentionally skipped - there is no tag
# to match - and `latest` is left untouched so it keeps tracking the last `v*`
# release. Images are amd64-only to match the release builds.
#
# Artifacts (under the running repo's owner):
# ghcr.io/<owner>/deer-flow-{backend,frontend,provisioner}:nightly
# ghcr.io/<owner>/deer-flow-{backend,frontend,provisioner}:nightly-YYYYMMDD
# oci://ghcr.io/<owner>/deer-flow chart version <base>-nightly.YYYYMMDD-<sha>
#
# The nightly chart defaults image.tag=nightly and image.registry=ghcr.io/<owner>
# (patched in-workflow, never committed), so installing it pulls the matching
# nightly images with no values overrides.
#
# Restricted to the upstream repo: every job is gated on
# `github.repository == 'bytedance/deer-flow'`, so a scheduled run or manual
# dispatch on a fork skips all jobs rather than pushing to the fork's own GHCR
# namespace. Scheduled workflows also only fire on the default branch.
on:
schedule:
- cron: "0 16 * * *" # 16:00 UTC daily (adjust as needed)
workflow_dispatch:
concurrency:
group: nightly
# Don't cancel a running nightly - a mid-cancel leaves a half-pushed set.
cancel-in-progress: false
env:
REGISTRY: ghcr.io
jobs:
prepare:
if: github.repository == 'bytedance/deer-flow'
runs-on: ubuntu-latest
outputs:
date: ${{ steps.date.outputs.date }}
nightly_version: ${{ steps.nightly_version.outputs.nightly_version }}
steps:
- name: Set nightly date (UTC)
id: date
# Shared by build-images (image tag) and publish-chart (chart version)
# so the two stay in sync for a given run.
run: echo "date=$(date -u +%Y%m%d)" >> "$GITHUB_OUTPUT"
- name: Checkout repository
# Needed to read Chart.yaml's base version for the nightly version
# string computed below.
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3
- name: Compute nightly version
id: nightly_version
# Single source of truth for the nightly version string:
# <base>-nightly.<YYYYMMDD>-<short_sha> (mirrors the chart's nightly
# scheme). build-images injects it into the frontend image (About-page
# version) and publish-chart stamps it on Chart.yaml, so the version
# users see and the chart version can't drift apart.
run: |
set -euo pipefail
BASE=$(grep -m1 '^version:' deploy/helm/deer-flow/Chart.yaml | awk '{print $2}')
# A malformed/missing Chart.yaml version would yield an empty BASE ->
# `-nightly.<date>-<sha>` (invalid semver) that now flows into both
# the chart publish and the frontend About-page version. Fail loudly.
# `pipefail` matters: without it the pipeline's exit code is awk's,
# so `set -e` alone wouldn't catch a grep miss.
test -n "$BASE" || { echo "::error::empty base version from Chart.yaml"; exit 1; }
SHORT_SHA="${GITHUB_SHA::7}"
NIGHTLY="${BASE}-nightly.${{ steps.date.outputs.date }}-${SHORT_SHA}"
echo "nightly_version=${NIGHTLY}" >> "$GITHUB_OUTPUT"
echo "Nightly version: ${NIGHTLY}"
validate-chart:
if: github.repository == 'bytedance/deer-flow'
# Catch a broken render or a stale config_version before publish. A broken
# chart published under an OCI version is immutable (GHCR won't let you
# overwrite --version), so a regression must fail here, not on install.
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3
- name: Lint chart
run: helm lint deploy/helm/deer-flow
- name: Validate templates render
run: helm template deer-flow deploy/helm/deer-flow --include-crds >/dev/null
# The chart's `config:` block embeds a config_version that must not fall
# behind config.example.yaml. Shared with chart.yaml via
# scripts/check_config_version.sh so the two workflows can't drift.
- name: config_version drift check
run: bash scripts/check_config_version.sh
build-images:
needs: prepare
if: github.repository == 'bytedance/deer-flow'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
strategy:
fail-fast: false
matrix:
include:
- component: backend
context: .
file: backend/Dockerfile
# Bake the `postgres` extra so multi-replica (K8s/Helm) deployments
# can use shared Postgres. Matches container.yaml's release image.
build-args: "UV_EXTRAS=postgres"
- component: frontend
context: .
file: frontend/Dockerfile
build-args: ""
- component: provisioner
context: docker/provisioner
file: docker/provisioner/Dockerfile
build-args: ""
env:
IMAGE_NAME: ${{ github.repository }}-${{ matrix.component }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3
- name: Log in to the Container registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 #v5.7.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# `nightly` rolls forward each run; `nightly-YYYYMMDD` is pinned to a
# day but mutable within it (a same-day re-dispatch overwrites it);
# `sha-<short>` is the only truly immutable tag. No `latest` (that
# stays on v* releases) and no branch/tag refs.
tags: |
type=raw,value=nightly
type=raw,value=nightly-${{ needs.prepare.outputs.date }}
type=sha
- name: Build and push Docker image
id: push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0
with:
context: ${{ matrix.context }}
file: ${{ matrix.file }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# APP_VERSION is consumed only by the frontend Dockerfile (it stamps
# the About-page version); backend/provisioner Dockerfiles don't
# declare it. Passed via build-arg because build-push-action doesn't
# forward host env into the BuildKit build.
build-args: |
${{ matrix.build-args }}
${{ matrix.component == 'frontend' && format('APP_VERSION={0}', needs.prepare.outputs.nightly_version) || '' }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be #v2.4.0
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
publish-chart:
# Ship the chart only after images build - it references all three, so a
# failed image build withholds the chart rather than publishing one that
# would fail to pull in-cluster.
needs: [prepare, validate-chart, build-images]
if: github.repository == 'bytedance/deer-flow'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
OWNER: ${{ github.repository_owner }}
NIGHTLY: ${{ needs.prepare.outputs.nightly_version }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3
- name: Patch chart to a nightly version + nightly image defaults
# Bumps Chart.yaml version/appVersion to the nightly version computed in
# the prepare job (<base>-nightly.<date>-<sha>, a valid semver
# prerelease; the short SHA makes each dispatch's version unique, so a
# same-day re-dispatch re-publishes cleanly - OCI chart versions are
# immutable and otherwise can't be overwritten). The same string is
# injected into the frontend image, so the About-page version and the
# chart version match. Repoints the chart's default image registry/tag
# at the nightly build. Patches are in-workflow only - nothing is
# committed back.
run: |
set -eu
CHART=deploy/helm/deer-flow
echo "Nightly chart version: ${NIGHTLY}"
sed -i "s|^version:.*|version: ${NIGHTLY}|" "$CHART/Chart.yaml"
sed -i "s|^appVersion:.*|appVersion: \"${NIGHTLY}\"|" "$CHART/Chart.yaml"
sed -i "s|^ registry: \"\".*| registry: \"ghcr.io/${OWNER}\"|" "$CHART/values.yaml"
sed -i 's|^ tag: "latest"| tag: "nightly"|' "$CHART/values.yaml"
# Gate the patches: sed exits 0 even on zero matches, so a drifted
# Chart.yaml/values.yaml would otherwise ship a chart that silently
# pulls the wrong (release `latest`) images. Fail loudly if any patch
# missed.
grep -q "^version: ${NIGHTLY}$" "$CHART/Chart.yaml" || { echo "::error::Chart.yaml version sed did not apply"; exit 1; }
grep -q "^appVersion: \"${NIGHTLY}\"$" "$CHART/Chart.yaml" || { echo "::error::Chart.yaml appVersion sed did not apply"; exit 1; }
grep -q '^ registry: "ghcr.io/' "$CHART/values.yaml" || { echo "::error::values.yaml registry sed did not apply"; exit 1; }
grep -q '^ tag: "nightly"' "$CHART/values.yaml" || { echo "::error::values.yaml tag sed did not apply"; exit 1; }
echo "--- Chart.yaml (head) ---"; sed -n '1,7p' "$CHART/Chart.yaml"
echo "--- image block ---"; sed -n '11,14p' "$CHART/values.yaml"
- name: Lint patched chart
run: helm lint deploy/helm/deer-flow
- name: Log in to GHCR
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | \
helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Package and push chart
run: |
helm package deploy/helm/deer-flow --destination ./packages
for pkg in ./packages/*.tgz; do
echo "--- pushing $pkg"
helm push "$pkg" oci://ghcr.io/${{ github.repository_owner }}
done