update afrogweb api #241
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Binary | |
| on: | |
| push: | |
| tags: | |
| - "*" | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Run snapshot build without publishing" | |
| type: boolean | |
| default: true | |
| required: false | |
| ref: | |
| description: "Git ref to test (branch or commit SHA)" | |
| type: string | |
| default: "" | |
| required: false | |
| afrogweb_ref: | |
| description: "afrogweb Git ref to build (branch, tag, or commit SHA)" | |
| type: string | |
| default: "main" | |
| required: false | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| env: | |
| GOPROXY: https://proxy.golang.org,direct | |
| GOSUMDB: sum.golang.org | |
| AFROGWEB_REF: ${{ github.event.inputs.afrogweb_ref || 'main' }} | |
| steps: | |
| - uses: actions/checkout@v3 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event.inputs.ref }} | |
| - uses: actions/setup-go@v4 | |
| with: | |
| go-version: 1.24.1 | |
| cache: true | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Go modules download | |
| run: | | |
| set -euo pipefail | |
| go mod download | |
| - name: Build and copy afrogweb assets | |
| env: | |
| AFROGWEB_TOKEN: ${{ secrets.AFROGWEB_TOKEN }} | |
| DRY_RUN: ${{ github.event.inputs.dry_run }} | |
| run: | | |
| set -euo pipefail | |
| WEB_ASSETS_READY=false | |
| AFROGWEB_REPO_URL="https://github.com/zan8in/afrogweb.git" | |
| if [ -n "${AFROGWEB_TOKEN:-}" ]; then | |
| AFROGWEB_REPO_URL="https://x-access-token:${AFROGWEB_TOKEN}@github.com/zan8in/afrogweb.git" | |
| fi | |
| REF_PRIMARY="${AFROGWEB_REF}" | |
| if [ "${GITHUB_EVENT_NAME}" = "push" ] && [ -n "${GITHUB_REF_NAME:-}" ]; then | |
| REF_PRIMARY="${GITHUB_REF_NAME}" | |
| fi | |
| REF_FALLBACK="${AFROGWEB_REF}" | |
| try_clone() { | |
| local ref="$1" | |
| rm -rf _afrogweb_tmp | |
| git clone --depth 1 --branch "${ref}" "${AFROGWEB_REPO_URL}" _afrogweb_tmp | |
| } | |
| clone_with_log() { | |
| local ref="$1" | |
| local out | |
| out="$(try_clone "${ref}" 2>&1)" && return 0 | |
| out="$(echo "${out}" | sed -E 's#https://x-access-token:[^@]+@github.com#https://x-access-token:***@github.com#g')" | |
| echo "[afrogweb] clone failed for ref=${ref}" >&2 | |
| echo "${out}" | tail -n 80 >&2 | |
| return 1 | |
| } | |
| if clone_with_log "${REF_PRIMARY}"; then | |
| echo "AFROGWEB_REF_EFFECTIVE=${REF_PRIMARY}" | |
| elif [ "${REF_PRIMARY}" != "${REF_FALLBACK}" ] && clone_with_log "${REF_FALLBACK}"; then | |
| echo "AFROGWEB_REF_EFFECTIVE=${REF_FALLBACK}" | |
| else | |
| rm -rf _afrogweb_tmp || true | |
| fi | |
| if [ -d "_afrogweb_tmp" ]; then | |
| cd _afrogweb_tmp | |
| node -v | |
| npm -v | |
| git rev-parse HEAD | |
| if [ -d "static/docs" ] && [ -d "../docs" ] && [ -z "$(ls -A static/docs 2>/dev/null)" ]; then | |
| cp -R ../docs/* static/docs/ || true | |
| fi | |
| if [ -f package-lock.json ]; then | |
| npm ci | |
| else | |
| npm install | |
| fi | |
| npm run build | |
| cd .. | |
| rm -rf _afrogweb_new | |
| mkdir -p _afrogweb_new | |
| cp -r _afrogweb_tmp/build/* _afrogweb_new/ | |
| rm -rf _afrogweb_tmp | |
| if [ -d "_afrogweb_new/_app/immutable/entry" ] && [ $(ls _afrogweb_new/_app/immutable/entry/*.js 2>/dev/null | wc -l) -gt 0 ]; then | |
| rm -rf pkg/web/webpath | |
| mkdir -p pkg/web/webpath | |
| cp -r _afrogweb_new/* pkg/web/webpath/ | |
| WEB_ASSETS_READY=true | |
| echo "afrogweb assets copied successfully" | |
| else | |
| echo "afrogweb build or copy failed: no JS files in entry/" >&2 | |
| fi | |
| rm -rf _afrogweb_new | |
| fi | |
| echo "WEB_ASSETS_READY=${WEB_ASSETS_READY}" >> "${GITHUB_ENV}" | |
| if [ "${WEB_ASSETS_READY}" != "true" ]; then | |
| if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${DRY_RUN:-}" = "true" ]; then | |
| echo "afrogweb assets not available (dry_run=true); continuing with placeholder assets" | |
| else | |
| echo "afrogweb assets not available. Ensure secrets.AFROGWEB_TOKEN has read access to zan8in/afrogweb." >&2 | |
| exit 1 | |
| fi | |
| fi | |
| - name: Verify embedded web assets | |
| if: env.WEB_ASSETS_READY == 'true' | |
| run: | | |
| set -euo pipefail | |
| ls -la pkg/web/webpath || (echo "missing webpath directory" && exit 1) | |
| test -f pkg/web/webpath/index.html || (echo "missing index.html" && exit 1) | |
| test -d pkg/web/webpath/_app/immutable/entry || (echo "missing entry directory" && exit 1) | |
| ENTRY_COUNT=$(find pkg/web/webpath/_app/immutable/entry -maxdepth 1 -type f -name '*.js' | wc -l || echo 0) | |
| [ "$ENTRY_COUNT" -gt 0 ] || (echo "missing entry js" && exit 1) | |
| CHUNKS=$(ls pkg/web/webpath/_app/immutable/chunks/*.js | wc -l) | |
| [ "$CHUNKS" -gt 0 ] || (echo "missing chunks js" && exit 1) | |
| - name: Detect unmatched web assets (embed coverage guard) | |
| if: env.WEB_ASSETS_READY == 'true' | |
| run: | | |
| set -euo pipefail | |
| TOP_EXTS='html|ico|png|svg|webmanifest|json|txt' | |
| ALLOWED_TOP_RE="^pkg/web/webpath/[^/]+\.(${TOP_EXTS})$" | |
| ALLOWED_DIRS_RE='^pkg/web/webpath/(fonts/.+|_app/.+|docs/.+|pocs/.+|reports/.+)$' | |
| UNMATCHED=$(find pkg/web/webpath -type f | grep -v -E "$ALLOWED_TOP_RE" | grep -v -E "$ALLOWED_DIRS_RE" | grep -v -E "/\.DS_Store$" || true) | |
| if [ -n "$UNMATCHED" ]; then | |
| echo "[embed coverage] Detected files not covered by go:embed patterns:" >&2 | |
| echo "$UNMATCHED" >&2 | |
| echo "Please update pkg/web/embed.go to include these paths." >&2 | |
| exit 1 | |
| fi | |
| - name: Smoke build (validate go:embed) | |
| env: | |
| CGO_ENABLED: 0 | |
| run: | | |
| go build -trimpath -ldflags "-s -w" -o ./_afrog_smoke ./cmd/afrog | |
| file ./_afrog_smoke || true | |
| # - name: Validate embedded FS contents | |
| # env: | |
| # CGO_ENABLED: 0 | |
| # run: | | |
| # set -euo pipefail | |
| # # 创建临时 Go 脚本检查 embed.FS | |
| # cat <<EOF > embed_check.go | |
| # package main | |
| # import ( | |
| # "embed" | |
| # "fmt" | |
| # "io/fs" | |
| # "os" | |
| # "path/filepath" | |
| # "strings" | |
| # ) | |
| # //go:embed webpath/*.* | |
| # //go:embed webpath/fonts/*/*.* | |
| # //go:embed webpath/_app/*.* | |
| # //go:embed webpath/_app/immutable/*/*.* | |
| # var webpathFS embed.FS | |
| # func main() { | |
| # var hasEntryJS bool | |
| # err := fs.WalkDir(webpathFS, ".", func(path string, d fs.DirEntry, err error) error { | |
| # if err != nil { | |
| # return err | |
| # } | |
| # if !d.IsDir() && strings.HasPrefix(path, "webpath/_app/immutable/entry/") && strings.HasSuffix(path, ".js") { | |
| # hasEntryJS = true | |
| # fmt.Printf("Found entry JS: %s\n", path) | |
| # } | |
| # return nil | |
| # }) | |
| # if err != nil { | |
| # fmt.Printf("Error walking FS: %v\n", err) | |
| # os.Exit(1) | |
| # } | |
| # if !hasEntryJS { | |
| # fmt.Println("No JS files found in _app/immutable/entry/") | |
| # os.Exit(1) | |
| # } | |
| # fmt.Println("Embedded FS validation passed") | |
| # } | |
| # EOF | |
| # # 编译并运行检查 | |
| # go build -o embed_check embed_check.go | |
| # ./embed_check > embed_check.log 2>&1 | |
| # cat embed_check.log | |
| # if grep -q "validation passed" embed_check.log; then | |
| # echo "Embedded FS OK" | |
| # else | |
| # echo "Embedded FS validation failed" >&2 | |
| # exit 1 | |
| # fi | |
| - name: Web smoke test (-web mode assets) | |
| if: env.WEB_ASSETS_READY == 'true' | |
| env: | |
| CGO_ENABLED: 0 | |
| run: | | |
| set -euo pipefail | |
| ./_afrog_smoke -web > web_smoke.log 2>&1 & echo $! > _afrog_pid | |
| trap 'kill "$(cat _afrog_pid)" 2>/dev/null || true' EXIT | |
| for i in $(seq 1 20); do | |
| if ! kill -0 "$(cat _afrog_pid)" 2>/dev/null; then | |
| echo "web process exited early" >&2 | |
| tail -n 200 web_smoke.log >&2 || true | |
| exit 1 | |
| fi | |
| if curl -fsS http://127.0.0.1:16868/api/health | grep -q '"status":"ok"'; then | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| # 若健康检查仍未通过,输出日志并失败 | |
| if ! curl -fsS http://127.0.0.1:16868/api/health | grep -q '"status":"ok"'; then | |
| echo "health probe failed" >&2 | |
| tail -n 200 web_smoke.log >&2 || true | |
| exit 1 | |
| fi | |
| curl -fsS -I http://127.0.0.1:16868/ | grep -E "HTTP/.* 200" >/dev/null | |
| curl -fsS -I http://127.0.0.1:16868/ | grep -i "Content-Type: text/html" >/dev/null | |
| APPJS=$(ls pkg/web/webpath/_app/immutable/entry/*app*.js 2>/dev/null | head -n1 || true) | |
| if [ -z "$APPJS" ]; then APPJS=$(ls pkg/web/webpath/_app/immutable/entry/*.js | head -n1 || true); fi | |
| APPURL=$(echo "$APPJS" | sed 's#pkg/web/webpath##') | |
| if [ -n "$APPURL" ]; then | |
| curl -fsS -I "http://127.0.0.1:16868$APPURL" | grep -E "HTTP/.* 200" >/dev/null | |
| curl -fsS -I "http://127.0.0.1:16868$APPURL" | grep -i "Content-Type: application/javascript\|Content-Type: text/javascript" >/dev/null | |
| fi | |
| STARTJS=$(ls pkg/web/webpath/_app/immutable/entry/*start*.js 2>/dev/null | head -n1 || true) | |
| if [ -z "$STARTJS" ]; then STARTJS=$(ls pkg/web/webpath/_app/immutable/entry/*.js | head -n1 || true); fi | |
| STARTURL=$(echo "$STARTJS" | sed 's#pkg/web/webpath##') | |
| curl -fsS -I "http://127.0.0.1:16868$STARTURL" | grep -E "HTTP/.* 200" >/dev/null | |
| curl -fsS -I "http://127.0.0.1:16868$STARTURL" | grep -i "Content-Type: application/javascript\|Content-Type: text/javascript" >/dev/null | |
| ASSETCSS=$(ls pkg/web/webpath/_app/immutable/assets/*.css | head -n1 || true) | |
| if [ -n "$ASSETCSS" ]; then | |
| CSSURL=$(echo "$ASSETCSS" | sed 's#pkg/web/webpath##') | |
| curl -fsS -I "http://127.0.0.1:16868$CSSURL" | grep -E "HTTP/.* 200" >/dev/null | |
| curl -fsS -I "http://127.0.0.1:16868$CSSURL" | grep -i "Content-Type: text/css" >/dev/null | |
| fi | |
| - uses: actions/upload-artifact@v4 | |
| if: failure() | |
| with: | |
| name: afrog-smoke-debug | |
| path: | | |
| _afrog_smoke | |
| _afrog_pid | |
| web_smoke.log | |
| - name: Goreleaser snapshot (no publish) | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true' | |
| uses: goreleaser/goreleaser-action@v4 | |
| with: | |
| args: "release --clean --snapshot --skip=publish --skip=validate" | |
| version: latest | |
| workdir: . | |
| env: | |
| GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" | |
| - name: Upload snapshot artifacts | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: afrog-dist-snapshot | |
| path: | | |
| dist/** | |
| - name: Goreleaser publish | |
| if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run != 'true') | |
| uses: goreleaser/goreleaser-action@v4 | |
| with: | |
| args: "release --clean --skip=validate" | |
| version: latest | |
| workdir: . | |
| env: | |
| GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" |