WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
name: Anydoc
|
||||
|
||||
# Compiles the vendored Rust archive, audits it, and exercises the `anydoc`
|
||||
# build tag. That is minutes of rustc + cargo-audit, so this workflow stays
|
||||
# off PRs that only touch migrations, frontend, CLI, or unrelated Go packages.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "third_party/anydoc-go/**"
|
||||
- "scripts/build-anydoc-lib.sh"
|
||||
- "internal/infrastructure/docparser/**"
|
||||
- ".github/workflows/anydoc.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "third_party/anydoc-go/**"
|
||||
- "scripts/build-anydoc-lib.sh"
|
||||
- "internal/infrastructure/docparser/**"
|
||||
- ".github/workflows/anydoc.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.26"
|
||||
GOPROXY: https://proxy.golang.org,direct
|
||||
|
||||
jobs:
|
||||
anydoc:
|
||||
name: anydoc parser engine
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo-audit
|
||||
id: cargo-audit-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cargo/bin/cargo-audit
|
||||
key: cargo-audit-${{ runner.os }}
|
||||
|
||||
- name: Install cargo-audit
|
||||
if: steps.cargo-audit-cache.outputs.cache-hit != 'true'
|
||||
run: cargo install cargo-audit --locked
|
||||
|
||||
# The archive is a build artifact, not a committed blob, so CI is what
|
||||
# keeps the vendored bindings in third_party/anydoc-go compiling.
|
||||
# This also materializes patched-anydoc/ (gitignored); cargo audit below
|
||||
# cannot load the crates-io patch until that tree exists.
|
||||
- name: Build the anydoc static archive
|
||||
run: ./scripts/build-anydoc-lib.sh
|
||||
|
||||
# The vendored bindings pull anydoc and its PDF stack from crates.io, and
|
||||
# WeKnora parses untrusted uploads in-process. A Rust advisory there (as
|
||||
# in RUSTSEC-2026-0187, an uncatchable abort on a hostile PDF) has to
|
||||
# break the build, not wait for someone to notice. Vulnerabilities fail
|
||||
# the job; unmaintained-crate warnings only report, since a transitive
|
||||
# crate going unmaintained is not something this repo can act on.
|
||||
- name: Audit the vendored dependency tree
|
||||
working-directory: third_party/anydoc-go
|
||||
run: cargo audit
|
||||
|
||||
- name: Vet, test, and build with the engine linked in
|
||||
run: |
|
||||
go vet -tags anydoc ./internal/infrastructure/docparser/...
|
||||
go test -tags anydoc -count=1 ./internal/infrastructure/docparser/...
|
||||
go build -tags anydoc ./cmd/server
|
||||
@@ -0,0 +1,107 @@
|
||||
name: App
|
||||
|
||||
# Anydoc (Rust archive + `anydoc` build tag) lives in anydoc.yml so a
|
||||
# migrations-only or unrelated Go PR does not compile the parser engine.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "!cli/**"
|
||||
- "!client/**"
|
||||
- "!docreader/**"
|
||||
- "!third_party/anydoc-go/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "Makefile"
|
||||
- "config/**"
|
||||
- "migrations/**"
|
||||
- "skills/preloaded/**"
|
||||
- "testdata/**"
|
||||
- "docker/Dockerfile.app"
|
||||
- ".github/workflows/app.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "!cli/**"
|
||||
- "!client/**"
|
||||
- "!docreader/**"
|
||||
- "!third_party/anydoc-go/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "Makefile"
|
||||
- "config/**"
|
||||
- "migrations/**"
|
||||
- "skills/preloaded/**"
|
||||
- "testdata/**"
|
||||
- "docker/Dockerfile.app"
|
||||
- ".github/workflows/app.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.26"
|
||||
GOPROXY: https://proxy.golang.org,direct
|
||||
# Keep tests independent of a developer machine's custom log template.
|
||||
LOG_FORMAT: ""
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Format, vet, test, and build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
# v6 defaults to go.mod for the cache key (more stable than go.sum).
|
||||
|
||||
- name: Download modules
|
||||
run: go mod download
|
||||
|
||||
- name: Check formatting
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
# Compare base..head, not the PR merge commit, so files changed only
|
||||
# on main after the branch point are not attributed to this PR.
|
||||
diff_range="${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}"
|
||||
else
|
||||
base="$BASE_SHA"
|
||||
if [ -z "$base" ] || [[ "$base" =~ ^0+$ ]]; then
|
||||
base=$(git rev-parse HEAD^)
|
||||
fi
|
||||
diff_range="$base..$GITHUB_SHA"
|
||||
fi
|
||||
unformatted=$(
|
||||
git diff --name-only --diff-filter=ACMR -z "$diff_range" -- '*.go' ':!cli/**' |
|
||||
xargs -0 -r gofmt -l
|
||||
)
|
||||
if [ -n "$unformatted" ]; then
|
||||
echo "The following Go files are not gofmt-formatted:"
|
||||
echo "$unformatted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Vet and test
|
||||
shell: bash
|
||||
run: |
|
||||
mapfile -t pkgs < <(go list ./... | grep -v '/docreader/')
|
||||
go vet "${pkgs[@]}"
|
||||
go test "${pkgs[@]}"
|
||||
|
||||
- name: Build server
|
||||
run: go build ./cmd/server
|
||||
@@ -0,0 +1,66 @@
|
||||
name: CLI E2E
|
||||
|
||||
# Runs the cli/acceptance/e2e/ suite against a real WeKnora server.
|
||||
# Cost-gated so it doesn't fire on every PR:
|
||||
# - workflow_dispatch: maintainer can trigger manually
|
||||
# - pull_request labeled "acceptance-e2e": adding the label kicks a run
|
||||
#
|
||||
# Required repo secrets (only set on Tencent/WeKnora; cross-fork PRs have no
|
||||
# access and the job exits early via the secrets-present guard):
|
||||
# WEKNORA_E2E_HOST - https URL of the test server
|
||||
# WEKNORA_E2E_TOKEN - bearer token / API key for that host
|
||||
#
|
||||
# Mirrors gh acceptance/ trigger pattern: env-gated, opt-in, single matrix.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [labeled, synchronize]
|
||||
paths:
|
||||
- 'cli/**'
|
||||
- '.github/workflows/cli-e2e.yml'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: cli
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
name: rag full loop
|
||||
# Skip auto-trigger on label-add unless the label is exactly "acceptance-e2e".
|
||||
# Manual workflow_dispatch always runs.
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'acceptance-e2e'))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26'
|
||||
cache-dependency-path: cli/go.sum
|
||||
|
||||
- name: secrets present
|
||||
id: secrets
|
||||
env:
|
||||
E2E_HOST: ${{ secrets.WEKNORA_E2E_HOST }}
|
||||
E2E_TOKEN: ${{ secrets.WEKNORA_E2E_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$E2E_HOST" ] || [ -z "$E2E_TOKEN" ]; then
|
||||
echo "::warning::WEKNORA_E2E_HOST or WEKNORA_E2E_TOKEN not set; skipping e2e."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: go test -tags=acceptance_e2e
|
||||
if: steps.secrets.outputs.skip == 'false'
|
||||
env:
|
||||
WEKNORA_E2E_HOST: ${{ secrets.WEKNORA_E2E_HOST }}
|
||||
WEKNORA_E2E_TOKEN: ${{ secrets.WEKNORA_E2E_TOKEN }}
|
||||
run: go test -tags=acceptance_e2e -v -timeout=8m ./acceptance/e2e/...
|
||||
@@ -0,0 +1,58 @@
|
||||
name: CLI
|
||||
|
||||
# Builds and tests the cli/ Go module on every push / PR that touches it.
|
||||
# Foundation PR (PR-1) gates: 3-platform `go build` + `go test ./...` green.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'cli/**'
|
||||
- '.github/workflows/cli.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'cli/**'
|
||||
- '.github/workflows/cli.yml'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: cli
|
||||
# Windows default is PowerShell, which tokenizes `go test -coverprofile=coverage.out ./...`
|
||||
# in a way that detaches `.out` as a separate positional package arg.
|
||||
# Force bash everywhere so the run lines parse identically across platforms
|
||||
# (Git Bash is preinstalled on windows-latest runners).
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test (${{ matrix.os }} / go ${{ matrix.go }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
go: ['1.26']
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
cache-dependency-path: cli/go.sum
|
||||
- name: go build
|
||||
run: go build ./...
|
||||
- name: go test
|
||||
run: go test -race -coverprofile=coverage.out ./...
|
||||
- name: go vet
|
||||
run: go vet ./...
|
||||
- name: Check agent-facing doc wire vocabulary
|
||||
if: runner.os == 'Linux'
|
||||
run: scripts/check-skill-wire-vocab.sh
|
||||
- name: Check for committed credentials in docs
|
||||
if: runner.os == 'Linux'
|
||||
run: scripts/check-secret-tokens.sh
|
||||
- name: coverage report
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: go tool cover -func=coverage.out
|
||||
@@ -0,0 +1,293 @@
|
||||
name: Build and Push Docker Image
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
NODE_VERSION: "24"
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
build-ui:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare version info
|
||||
id: version
|
||||
run: |
|
||||
eval "$(./scripts/get_version.sh env)"
|
||||
echo "commit_id=$COMMIT_ID" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: npm
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Build frontend
|
||||
env:
|
||||
VITE_IS_DOCKER: true
|
||||
VITE_FRONTEND_COMMIT: ${{ steps.version.outputs.commit_id }}
|
||||
run: ./scripts/build_frontend_dist.sh
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-ui
|
||||
|
||||
- name: Build ui Image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: frontend/Dockerfile
|
||||
context: ./frontend
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=build-ui
|
||||
cache-to: type=gha,scope=build-ui,mode=max
|
||||
|
||||
build-docreader:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-docreader
|
||||
|
||||
- name: Build docreader Image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: docker/Dockerfile.docreader
|
||||
context: .
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=build-docreader
|
||||
cache-to: type=gha,scope=build-docreader,mode=max
|
||||
|
||||
build-sandbox:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-sandbox
|
||||
|
||||
- name: Build sandbox Image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: docker/Dockerfile.sandbox
|
||||
target: sandbox
|
||||
context: .
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=build-sandbox
|
||||
cache-to: type=gha,scope=build-sandbox,mode=max
|
||||
|
||||
# Cube builds templates straight from the image and gates the build on
|
||||
# GET :49983/health, so it needs the variant with envd baked in.
|
||||
- name: Docker meta (Cube variant)
|
||||
id: meta-cube
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-sandbox
|
||||
flavor: |
|
||||
suffix=-cube,onlatest=true
|
||||
|
||||
# amd64 only: cubesandbox-base, the source of the envd binary, publishes
|
||||
# no arm64 image.
|
||||
- name: Build sandbox Cube Image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
file: docker/Dockerfile.sandbox
|
||||
target: cube
|
||||
context: .
|
||||
labels: ${{ steps.meta-cube.outputs.labels }}
|
||||
tags: ${{ steps.meta-cube.outputs.tags }}
|
||||
cache-from: type=gha,scope=build-sandbox-cube
|
||||
cache-to: type=gha,scope=build-sandbox-cube,mode=max
|
||||
|
||||
build-app:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
platform: linux/amd64
|
||||
runs: ubuntu-latest
|
||||
- arch: arm64
|
||||
platform: linux/arm64
|
||||
runs: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runs }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-app
|
||||
|
||||
- name: Prepare version info
|
||||
id: version
|
||||
run: |
|
||||
# 使用统一的版本管理脚本
|
||||
eval "$(./scripts/get_version.sh env)"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "commit_id=$COMMIT_ID" >> $GITHUB_OUTPUT
|
||||
echo "build_time=$BUILD_TIME" >> $GITHUB_OUTPUT
|
||||
echo "go_version=$GO_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
# 显示版本信息
|
||||
./scripts/get_version.sh info
|
||||
|
||||
- name: Build app Image
|
||||
id: build
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
push: true
|
||||
platforms: ${{ matrix.platform }}
|
||||
file: docker/Dockerfile.app
|
||||
context: .
|
||||
build-args: |
|
||||
${{ format('VERSION_ARG={0}', steps.version.outputs.version) }}
|
||||
${{ format('COMMIT_ID_ARG={0}', steps.version.outputs.commit_id) }}
|
||||
${{ format('BUILD_TIME_ARG={0}', steps.version.outputs.build_time) }}
|
||||
${{ format('GO_VERSION_ARG={0}', steps.version.outputs.go_version) }}
|
||||
WITH_ANYDOC=1
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-app
|
||||
cache-from: type=gha,scope=build-app-${{ env.PLATFORM_PAIR }}
|
||||
cache-to: type=gha,scope=build-app-${{ env.PLATFORM_PAIR }},mode=max
|
||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-app
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ secrets.DOCKERHUB_USERNAME }}/weknora-app
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ secrets.DOCKERHUB_USERNAME }}/weknora-app@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ secrets.DOCKERHUB_USERNAME }}/weknora-app:${{ steps.meta.outputs.version }}
|
||||
@@ -0,0 +1,107 @@
|
||||
name: DocReader
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "docreader/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "testdata/**"
|
||||
- "docker/Dockerfile.docreader"
|
||||
- "docker/Dockerfile.odl-hybrid"
|
||||
- ".github/workflows/docreader.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "docreader/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "testdata/**"
|
||||
- "docker/Dockerfile.docreader"
|
||||
- "docker/Dockerfile.odl-hybrid"
|
||||
- ".github/workflows/docreader.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.10.18"
|
||||
GO_VERSION: "1.26"
|
||||
GOPROXY: https://proxy.golang.org,direct
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Python tests and Go client tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: docreader/uv.lock
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: uv sync --project docreader --locked --no-dev --python ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Compile Python sources
|
||||
run: uv run --project docreader --python ${{ env.PYTHON_VERSION }} python -m compileall -q docreader
|
||||
|
||||
- name: Run Python tests
|
||||
run: uv run --project docreader --python ${{ env.PYTHON_VERSION }} python -m unittest discover -s docreader/tests -p "test_*.py" -v
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
|
||||
# TestReadURL drives the live WebParser (Playwright WebKit). Without the
|
||||
# browser binary, scrape used to return "Error parsing web page: …" as
|
||||
# markdown and the Go client treated that as success. After #2883 the
|
||||
# parser raises, so CI must install WebKit the same way the image does.
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-webkit-${{ hashFiles('docreader/uv.lock') }}
|
||||
|
||||
- name: Install Playwright WebKit
|
||||
run: uv run --project docreader --python ${{ env.PYTHON_VERSION }} python -m playwright install --with-deps webkit
|
||||
|
||||
- name: Start DocReader gRPC server
|
||||
run: |
|
||||
uv run --project docreader --python ${{ env.PYTHON_VERSION }} python -m docreader.main &
|
||||
echo $! > /tmp/docreader.pid
|
||||
for i in $(seq 1 30); do
|
||||
if nc -z 127.0.0.1 50051 2>/dev/null; then
|
||||
echo "DocReader gRPC server is ready"
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "DocReader gRPC server failed to start"
|
||||
exit 1
|
||||
|
||||
- name: Run Go client tests
|
||||
run: go test ./docreader/client ./docreader/proto
|
||||
|
||||
- name: Stop DocReader gRPC server
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f /tmp/docreader.pid ]; then
|
||||
kill "$(cat /tmp/docreader.pid)" 2>/dev/null || true
|
||||
fi
|
||||
@@ -0,0 +1,268 @@
|
||||
name: Build & Publish (dsh-weknora)
|
||||
|
||||
# Build, test, and publish the DeepSeek Harness plugin (packages/dsh-weknora/) to npm.
|
||||
#
|
||||
# Three jobs:
|
||||
# test typecheck, unit tests, both halves of the contract, and a
|
||||
# packaging check
|
||||
# e2e install the package into a throwaway dsh profile and drive real
|
||||
# tool calls through the harness agent loop against a mock backend
|
||||
# publish on a `dsh-weknora-v*` tag only, publish to npm with provenance
|
||||
#
|
||||
# The e2e job pins the harness version so a plugin change is the only thing that
|
||||
# can turn it red. The weekly drift job runs the same check against dsh@latest and
|
||||
# is allowed to fail: it exists to notice a harness breaking change early, since
|
||||
# dsh is in developer preview.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags:
|
||||
- "dsh-weknora-v*"
|
||||
paths:
|
||||
- "packages/dsh-weknora/**"
|
||||
- ".github/workflows/dsh-plugin.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/dsh-weknora/**"
|
||||
- ".github/workflows/dsh-plugin.yml"
|
||||
schedule:
|
||||
# Monday 03:00 UTC: catch a harness release that breaks the plugin.
|
||||
- cron: "0 3 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# The harness release this plugin is verified against. Bump it together with
|
||||
# the compatibility note in packages/dsh-weknora/README.md.
|
||||
DSH_PINNED_SPEC: "@deepseek-ai/dsh@0.1.0-rc.8"
|
||||
# The contract's Go half lives in this repository's module. app.yml runs it
|
||||
# only for a Go diff, so a change to the fixture or to the plugin alone would
|
||||
# otherwise skip the side that checks WeKnora still serves those calls.
|
||||
GO_VERSION: "1.26"
|
||||
# `dsh plugin add` shells out to pnpm to install into the profile, and the
|
||||
# runner image ships only npm and yarn. The profile it writes carries its
|
||||
# settings in pnpm-workspace.yaml, which pnpm 10 is the first to read.
|
||||
PNPM_VERSION: "10"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Typecheck, test, pack
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/dsh-weknora
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
cache-dependency-path: packages/dsh-weknora/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci || npm install
|
||||
|
||||
- name: Typecheck
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Unit and contract tests
|
||||
run: node --test "test/*.test.mjs"
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Contract tests against WeKnora's Go types
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: go test ./packages/dsh-weknora/contract/...
|
||||
|
||||
- name: Pack and verify the published file list
|
||||
run: |
|
||||
set -e
|
||||
npm pack --dry-run --json > pack.json
|
||||
node -e "
|
||||
const files = require('./pack.json')[0].files.map(f => f.path);
|
||||
const required = ['dist/index.js', 'dist/index.d.ts', 'cordis.patch.yml', 'README.md', 'package.json'];
|
||||
const missing = required.filter(name => !files.includes(name));
|
||||
if (missing.length > 0) {
|
||||
console.error('missing from the tarball: ' + missing.join(', '));
|
||||
process.exit(1);
|
||||
}
|
||||
const leaked = files.filter(name => name.startsWith('test/') || name.startsWith('src/'));
|
||||
if (leaked.length > 0) {
|
||||
console.error('unexpected files in the tarball: ' + leaked.join(', '));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('tarball contents verified: ' + files.length + ' files');
|
||||
"
|
||||
rm pack.json
|
||||
|
||||
e2e:
|
||||
name: End-to-end inside dsh (pinned)
|
||||
runs-on: ubuntu-latest
|
||||
# Cold cache: npm install @deepseek-ai/dsh takes ~15 min; keep headroom.
|
||||
timeout-minutes: 25
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/dsh-weknora
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
cache-dependency-path: packages/dsh-weknora/package-lock.json
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
# The e2e script installs @deepseek-ai/dsh on first run (~15 min uncached).
|
||||
# Reuse the install across workflow runs; bump DSH_PINNED_SPEC to refresh.
|
||||
- name: Cache pinned dsh install
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .cache/dsh-e2e-install
|
||||
key: dsh-e2e-${{ env.DSH_PINNED_SPEC }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci || npm install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Run the plugin inside a real dsh profile
|
||||
env:
|
||||
DSH_PACKAGE_SPEC: ${{ env.DSH_PINNED_SPEC }}
|
||||
DSH_INSTALL_DIR: ${{ github.workspace }}/.cache/dsh-e2e-install
|
||||
DSH_E2E_LOG: ${{ runner.temp }}/dsh-weknora-e2e.log
|
||||
run: node test/e2e/run-in-dsh.mjs
|
||||
|
||||
- name: Upload the transcript
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dsh-weknora-e2e-transcript
|
||||
path: ${{ runner.temp }}/dsh-weknora-e2e.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
drift:
|
||||
name: End-to-end inside dsh (latest, advisory)
|
||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
continue-on-error: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/dsh-weknora
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
cache-dependency-path: packages/dsh-weknora/package-lock.json
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Week id for dsh@latest cache
|
||||
id: week
|
||||
run: echo "id=$(date -u +%G-W%V)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# dsh@latest moves; refresh the cache weekly.
|
||||
- name: Cache latest dsh install
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .cache/dsh-e2e-install-latest
|
||||
key: dsh-e2e-latest-${{ steps.week.outputs.id }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci || npm install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Run against the latest harness release
|
||||
env:
|
||||
DSH_PACKAGE_SPEC: "@deepseek-ai/dsh@latest"
|
||||
DSH_INSTALL_DIR: ${{ github.workspace }}/.cache/dsh-e2e-install-latest
|
||||
run: node test/e2e/run-in-dsh.mjs
|
||||
|
||||
publish:
|
||||
name: Publish to npm
|
||||
needs: [test, e2e]
|
||||
if: startsWith(github.ref, 'refs/tags/dsh-weknora-v')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
environment:
|
||||
name: npm-publish
|
||||
url: https://www.npmjs.com/package/@wxg-prc-cpg/dsh-weknora
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/dsh-weknora
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
cache: npm
|
||||
cache-dependency-path: packages/dsh-weknora/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci || npm install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Check the tag names the packaged version
|
||||
run: |
|
||||
set -e
|
||||
version="$(node -p "require('./package.json').version")"
|
||||
expected="dsh-weknora-v${version}"
|
||||
if [ "$GITHUB_REF_NAME" != "$expected" ]; then
|
||||
echo "::error::tag $GITHUB_REF_NAME does not name the packaged version $version (expected $expected)."
|
||||
echo "::error::Bump packages/dsh-weknora/package.json, or retag — publishing here would ship a version the tag does not name."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check whether this version is already published
|
||||
id: check_npm
|
||||
run: |
|
||||
set -e
|
||||
name="$(node -p "require('./package.json').name")"
|
||||
version="$(node -p "require('./package.json').version")"
|
||||
echo "packaged: $name@$version"
|
||||
if npm view "$name@$version" version >/dev/null 2>&1; then
|
||||
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::$name $version is already on npm; skipping publish. Bump package.json to release."
|
||||
else
|
||||
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Publish
|
||||
if: steps.check_npm.outputs.should_publish == 'true'
|
||||
run: npm publish --provenance --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Frontend
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "frontend/**"
|
||||
- "scripts/build_frontend_dist.sh"
|
||||
- ".github/workflows/frontend.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "frontend/**"
|
||||
- "scripts/build_frontend_dist.sh"
|
||||
- ".github/workflows/frontend.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NODE_VERSION: "24"
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Test, type-check, and build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
working-directory: frontend
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: npm
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --no-audit --no-fund
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Type-check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Go Lint Cache
|
||||
|
||||
# Action caches written by a pull request run are scoped to that pull request,
|
||||
# so only runs on the default branch produce caches every pull request can
|
||||
# restore. Without this workflow each pull request would recompile the whole
|
||||
# package graph from scratch (~6 minutes for the root module, versus ~40s warm).
|
||||
#
|
||||
# This lives apart from go-lint.yml rather than as a conditional job inside it:
|
||||
# a job skipped by `if` still surfaces as a check on every pull request, and
|
||||
# GitHub renders its name with the matrix expression unexpanded.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "**/go.mod"
|
||||
- "**/go.sum"
|
||||
- ".golangci.yml"
|
||||
- ".github/workflows/go-lint-cache.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.26"
|
||||
GOLANGCI_LINT_VERSION: "v2.12.2"
|
||||
GOPROXY: https://proxy.golang.org,direct
|
||||
|
||||
jobs:
|
||||
warm-cache:
|
||||
name: warm lint cache (${{ matrix.module }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
module: [".", "cli", "client"]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: ${{ matrix.module }}/go.sum
|
||||
|
||||
- uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: ${{ env.GOLANGCI_LINT_VERSION }}
|
||||
working-directory: ${{ matrix.module }}
|
||||
# This workflow exists for its cache side effect only; the backlog of
|
||||
# existing findings must not turn main red. The run still prints the
|
||||
# full report, which makes a manual dispatch a useful backlog view.
|
||||
args: --issues-exit-code=0
|
||||
@@ -0,0 +1,104 @@
|
||||
name: Go Lint
|
||||
|
||||
# The repository carries a large backlog of pre-existing golangci-lint findings,
|
||||
# so pull requests are only judged on the code they introduce (`only-new-issues`).
|
||||
# That filter needs a pull request diff, which is why this workflow has no other
|
||||
# trigger; the companion go-lint-cache.yml keeps the caches this job restores
|
||||
# from warm on main.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "**/go.mod"
|
||||
- "**/go.sum"
|
||||
- ".golangci.yml"
|
||||
- ".github/workflows/go-lint.yml"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.26"
|
||||
GOLANGCI_LINT_VERSION: "v2.12.2"
|
||||
GOPROXY: https://proxy.golang.org,direct
|
||||
|
||||
jobs:
|
||||
# Path filters above start this workflow for any Go change; the matrix still
|
||||
# used to lint all three modules. Detect which modules the PR actually
|
||||
# touches so a root-only change (migrations test, handler, …) does not queue
|
||||
# cli/ and client/ runners. Changing .golangci.yml or this workflow still
|
||||
# lints every module, since they share the root config.
|
||||
changes:
|
||||
name: detect lint modules
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
modules: ${{ steps.detect.outputs.modules }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Detect changed modules
|
||||
id: detect
|
||||
shell: bash
|
||||
run: |
|
||||
diff_range="${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}"
|
||||
modules=()
|
||||
if git diff --name-only --diff-filter=ACMR "$diff_range" -- \
|
||||
.golangci.yml .github/workflows/go-lint.yml | grep -q .; then
|
||||
modules=("." "cli" "client")
|
||||
else
|
||||
if git diff --name-only --diff-filter=ACMR "$diff_range" -- \
|
||||
'*.go' ':!cli/**' ':!client/**' go.mod go.sum | grep -q .; then
|
||||
modules+=(".")
|
||||
fi
|
||||
if git diff --name-only --diff-filter=ACMR "$diff_range" -- 'cli/**' | grep -q .; then
|
||||
modules+=("cli")
|
||||
fi
|
||||
if git diff --name-only --diff-filter=ACMR "$diff_range" -- 'client/**' | grep -q .; then
|
||||
modules+=("client")
|
||||
fi
|
||||
fi
|
||||
json="["
|
||||
first=1
|
||||
for m in "${modules[@]}"; do
|
||||
if [ "$first" -eq 1 ]; then
|
||||
first=0
|
||||
else
|
||||
json+=","
|
||||
fi
|
||||
json+="\"${m}\""
|
||||
done
|
||||
json+="]"
|
||||
echo "modules=${json}" >> "$GITHUB_OUTPUT"
|
||||
echo "Lint modules: ${json}"
|
||||
|
||||
golangci:
|
||||
name: golangci-lint (${{ matrix.module }})
|
||||
needs: changes
|
||||
if: needs.changes.outputs.modules != '[]'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
module: ${{ fromJSON(needs.changes.outputs.modules) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: ${{ matrix.module }}/go.sum
|
||||
|
||||
- uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: ${{ env.GOLANGCI_LINT_VERSION }}
|
||||
working-directory: ${{ matrix.module }}
|
||||
only-new-issues: true
|
||||
@@ -0,0 +1,149 @@
|
||||
name: Build & Publish (mcp-server)
|
||||
|
||||
# Build, test, and publish the Python MCP server (mcp-server/) to PyPI.
|
||||
# Publishing uses PyPI Trusted Publishing (OIDC) — no API token required,
|
||||
# only the GitHub→PyPI OIDC trust configured on the repo.
|
||||
#
|
||||
# Release policy: publishing does NOT depend on tags. Bump the version in
|
||||
# mcp-server/pyproject.toml (and the synced version strings in setup.py,
|
||||
# __init__.py, weknora_mcp_server.py server_version, main.py --version),
|
||||
# merge to main, and the pipeline builds + publishes automatically.
|
||||
# A pre-publish check queries PyPI and skips upload if that version is
|
||||
# already published, so re-running on an unchanged version stays green.
|
||||
# Releases also still trigger on mcp-server-v* tags (back-compat).
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags:
|
||||
- "mcp-server-v*"
|
||||
paths:
|
||||
- "mcp-server/**"
|
||||
- ".github/workflows/mcp-server.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "mcp-server/**"
|
||||
- ".github/workflows/mcp-server.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test (Python ${{ matrix.python-version }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mcp-server
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
run: uv python install ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies (test extras)
|
||||
run: uv sync --python ${{ matrix.python-version }} --extra test
|
||||
|
||||
- name: Run tests
|
||||
# unittest avoids pytest importing __init__.py as a broken package root.
|
||||
# Manual diagnostics live in check_imports.py (not matched by test_*.py).
|
||||
run: uv run --python ${{ matrix.python-version }} python -m unittest discover -s . -p "test_*.py" -v
|
||||
|
||||
build:
|
||||
name: Build sdist + wheel
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
# Build on push to main, manual runs, and release tags — but never on PRs.
|
||||
if: github.event_name != 'pull_request'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mcp-server
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install 3.12
|
||||
|
||||
- name: Build package
|
||||
run: uv build
|
||||
|
||||
- name: Verify wheel contains upload_paths.py (regression guard)
|
||||
run: |
|
||||
set -e
|
||||
if ! python3 -c "import glob, zipfile, sys; whl=glob.glob('dist/*.whl')[0]; z=zipfile.ZipFile(whl); sys.exit(0 if 'upload_paths.py' in z.namelist() else 1)"; then
|
||||
echo "::error::upload_paths.py is missing from the wheel — check py-modules in pyproject.toml/setup.py"
|
||||
exit 1
|
||||
fi
|
||||
echo "upload_paths.py present in wheel ✓"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: mcp-server/dist/
|
||||
|
||||
publish:
|
||||
name: Publish to PyPI
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
# Publish on push to main, manual runs (main/tags only), and release tags — never on PRs.
|
||||
if: >-
|
||||
github.event_name != 'pull_request' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/mcp-server-v'))
|
||||
permissions:
|
||||
id-token: write
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/project/tencent-weknora-mcp/
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check if version is already published
|
||||
id: check_pypi
|
||||
working-directory: mcp-server
|
||||
# Read the version from pyproject.toml and query PyPI. If that exact
|
||||
# version already exists, skip publishing (so re-runs on an unchanged
|
||||
# version stay green instead of failing with "File already exists").
|
||||
run: |
|
||||
set -e
|
||||
version="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
|
||||
echo "packaged version: $version"
|
||||
exists="$(python3 -c '
|
||||
import json, sys, urllib.request
|
||||
v = sys.argv[1]
|
||||
try:
|
||||
d = json.load(urllib.request.urlopen("https://pypi.org/pypi/tencent-weknora-mcp/json"))
|
||||
print("true" if v in d.get("releases", {}) else "false")
|
||||
except Exception:
|
||||
# If PyPI is unreachable or the project is brand-new (404), treat as unpublished.
|
||||
print("false")
|
||||
' "$version")"
|
||||
echo "already on PyPI: $exists"
|
||||
if [ "$exists" = "true" ]; then
|
||||
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::tencent-weknora-mcp $version is already on PyPI; skipping publish. Bump pyproject.toml (and synced version strings) to release new changes."
|
||||
else
|
||||
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Download artifacts
|
||||
if: steps.check_pypi.outputs.should_publish == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
- name: Publish to PyPI
|
||||
if: steps.check_pypi.outputs.should_publish == 'true'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
@@ -0,0 +1,451 @@
|
||||
name: Release Lite Binaries
|
||||
|
||||
on:
|
||||
# push:
|
||||
# tags:
|
||||
# - "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag (e.g. v0.2.0)"
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
group: release-lite-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.24"
|
||||
NODE_VERSION: "22"
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
# ── 1. Build frontend once, share as artifact (for Web CLI) ──
|
||||
build-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: npm
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Build frontend
|
||||
working-directory: frontend
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: frontend/dist/
|
||||
retention-days: 1
|
||||
|
||||
# ── 2a. Build Web CLI binary per platform (native CGO) ──
|
||||
build-binary:
|
||||
needs: build-frontend
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
- os: ubuntu-24.04-arm
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
- os: macos-14
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
- os: macos-14
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: web/
|
||||
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
run: |
|
||||
if [ -n "${{ inputs.tag }}" ]; then
|
||||
VERSION="${{ inputs.tag }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
fi
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "Building ${VERSION} for ${{ matrix.goos }}/${{ matrix.goarch }}"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
export EDITION=lite
|
||||
eval "$(./scripts/get_version.sh env)"
|
||||
LDFLAGS="-w -s $(./scripts/get_version.sh ldflags)"
|
||||
export CGO_CFLAGS="-Wno-deprecated-declarations"
|
||||
if [ "${{ matrix.goos }}" = "darwin" ]; then
|
||||
export CGO_LDFLAGS="-Wl,-no_warn_duplicate_libraries"
|
||||
fi
|
||||
go build -tags "sqlite_fts5" -ldflags="${LDFLAGS}" \
|
||||
-o WeKnora-lite ./cmd/server
|
||||
|
||||
- name: Package tarball
|
||||
run: |
|
||||
ARCHIVE="WeKnora-lite_${{ steps.ver.outputs.version }}_${{ matrix.goos }}_${{ matrix.goarch }}"
|
||||
mkdir -p "${ARCHIVE}/web"
|
||||
cp WeKnora-lite "${ARCHIVE}/"
|
||||
cp -r web/* "${ARCHIVE}/web/"
|
||||
cp .env.lite.example "${ARCHIVE}/"
|
||||
cp docs/LITE.md "${ARCHIVE}/README.md"
|
||||
if [ -d config ]; then
|
||||
cp -r config "${ARCHIVE}/config"
|
||||
fi
|
||||
if [ -d migrations/sqlite ]; then
|
||||
mkdir -p "${ARCHIVE}/migrations/sqlite"
|
||||
cp -r migrations/sqlite/* "${ARCHIVE}/migrations/sqlite/"
|
||||
fi
|
||||
if [ -f deploy/weknora-lite.service ]; then
|
||||
cp deploy/weknora-lite.service "${ARCHIVE}/"
|
||||
fi
|
||||
tar czf "${ARCHIVE}.tar.gz" "${ARCHIVE}"
|
||||
shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: release-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: |
|
||||
WeKnora-lite_*.tar.gz
|
||||
WeKnora-lite_*.tar.gz.sha256
|
||||
retention-days: 3
|
||||
|
||||
# ── 2b. Build Desktop APP per platform (Wails) ──
|
||||
build-desktop-app:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-14
|
||||
platform: darwin/universal
|
||||
arch_label: macOS_universal
|
||||
artifact_name: desktop-macos-universal
|
||||
- os: macos-14
|
||||
platform: darwin/amd64
|
||||
arch_label: macOS_amd64
|
||||
artifact_name: desktop-macos-amd64
|
||||
- os: macos-14
|
||||
platform: darwin/arm64
|
||||
arch_label: macOS_arm64
|
||||
artifact_name: desktop-macos-arm64
|
||||
- os: ubuntu-latest
|
||||
platform: linux/amd64
|
||||
arch_label: linux_amd64
|
||||
artifact_name: desktop-linux-amd64
|
||||
- os: windows-latest
|
||||
platform: windows/amd64
|
||||
arch_label: windows_amd64
|
||||
artifact_name: desktop-windows-amd64
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: npm
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Install Wails CLI
|
||||
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||
|
||||
- name: Install NSIS (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: choco install nsis -y
|
||||
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "${{ inputs.tag }}" ]; then
|
||||
VERSION="${{ inputs.tag }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
fi
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Pre-compile Go packages (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
export GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn
|
||||
export CGO_CFLAGS="-Wno-deprecated-declarations"
|
||||
cd cmd/desktop
|
||||
go build -p 1 -buildvcs=false -tags "desktop,sqlite_fts5,production" -o _prebuild_tmp.exe . || true
|
||||
rm -f _prebuild_tmp.exe
|
||||
|
||||
- name: Build desktop app
|
||||
shell: bash
|
||||
run: |
|
||||
export EDITION=lite
|
||||
export GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn
|
||||
eval "$(./scripts/get_version.sh env)"
|
||||
LDFLAGS="$(./scripts/get_version.sh ldflags) -X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'"
|
||||
export CGO_CFLAGS="-Wno-deprecated-declarations"
|
||||
|
||||
BUILD_TAGS="sqlite_fts5"
|
||||
NSIS_FLAG=""
|
||||
|
||||
if [ "${{ runner.os }}" = "Linux" ]; then
|
||||
BUILD_TAGS="sqlite_fts5 webkit2_41"
|
||||
fi
|
||||
if [ "${{ runner.os }}" = "macOS" ]; then
|
||||
export CGO_LDFLAGS="-Wl,-no_warn_duplicate_libraries"
|
||||
fi
|
||||
if [ "${{ runner.os }}" = "Windows" ]; then
|
||||
NSIS_FLAG="-nsis"
|
||||
fi
|
||||
|
||||
cd cmd/desktop
|
||||
wails build -clean \
|
||||
-tags "${BUILD_TAGS}" \
|
||||
-platform "${{ matrix.platform }}" \
|
||||
${NSIS_FLAG} \
|
||||
-ldflags="${LDFLAGS}" \
|
||||
-o "WeKnora Lite"
|
||||
|
||||
# ── macOS: bundle .app with resources, then create DMG ──
|
||||
- name: Package macOS app
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
APP_BUNDLE="WeKnora Lite.app"
|
||||
SRC="cmd/desktop/build/bin/${APP_BUNDLE}"
|
||||
RESOURCES="${SRC}/Contents/Resources"
|
||||
|
||||
mkdir -p "${RESOURCES}/config"
|
||||
mkdir -p "${RESOURCES}/migrations/sqlite"
|
||||
|
||||
if [ -f .env.lite.example ]; then
|
||||
cp .env.lite.example "${RESOURCES}/.env"
|
||||
fi
|
||||
if [ -d migrations/sqlite ]; then
|
||||
cp -r migrations/sqlite/* "${RESOURCES}/migrations/sqlite/"
|
||||
fi
|
||||
if [ -d config ]; then
|
||||
cp -r config/* "${RESOURCES}/config/"
|
||||
fi
|
||||
|
||||
DMG_NAME="WeKnora-Lite-App_${{ steps.ver.outputs.version }}_${{ matrix.arch_label }}"
|
||||
|
||||
DMG_SRC="dmg_staging"
|
||||
mkdir -p "${DMG_SRC}"
|
||||
cp -R "cmd/desktop/build/bin/${APP_BUNDLE}" "${DMG_SRC}/"
|
||||
ln -s /Applications "${DMG_SRC}/Applications"
|
||||
|
||||
hdiutil create -volname "WeKnora Lite" \
|
||||
-srcfolder "${DMG_SRC}" \
|
||||
-ov -format UDZO \
|
||||
"${DMG_NAME}.dmg"
|
||||
|
||||
shasum -a 256 "${DMG_NAME}.dmg" > "${DMG_NAME}.dmg.sha256"
|
||||
|
||||
# ── Linux: package tarball ──
|
||||
- name: Package Linux app
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
ARCHIVE="WeKnora-Lite-App_${{ steps.ver.outputs.version }}_${{ matrix.arch_label }}"
|
||||
mkdir -p "${ARCHIVE}"
|
||||
cp "cmd/desktop/build/bin/WeKnora Lite" "${ARCHIVE}/"
|
||||
if [ -f .env.lite.example ]; then
|
||||
cp .env.lite.example "${ARCHIVE}/"
|
||||
fi
|
||||
cp docs/LITE.md "${ARCHIVE}/README.md"
|
||||
if [ -d config ]; then
|
||||
cp -r config "${ARCHIVE}/config"
|
||||
fi
|
||||
if [ -d migrations/sqlite ]; then
|
||||
mkdir -p "${ARCHIVE}/migrations/sqlite"
|
||||
cp -r migrations/sqlite/* "${ARCHIVE}/migrations/sqlite/"
|
||||
fi
|
||||
tar czf "${ARCHIVE}.tar.gz" "${ARCHIVE}"
|
||||
shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256"
|
||||
|
||||
# ── Windows: package NSIS installer or plain exe ──
|
||||
- name: Package Windows app
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
ARCHIVE="WeKnora-Lite-App_${{ steps.ver.outputs.version }}_${{ matrix.arch_label }}"
|
||||
# Wails -nsis generates an installer named {app}-amd64-installer.exe
|
||||
INSTALLER="cmd/desktop/build/bin/WeKnora Lite-amd64-installer.exe"
|
||||
if [ -f "$INSTALLER" ]; then
|
||||
cp "$INSTALLER" "${ARCHIVE}_setup.exe"
|
||||
sha256sum "${ARCHIVE}_setup.exe" > "${ARCHIVE}_setup.exe.sha256"
|
||||
else
|
||||
cp "cmd/desktop/build/bin/WeKnora Lite.exe" "${ARCHIVE}.exe"
|
||||
sha256sum "${ARCHIVE}.exe" > "${ARCHIVE}.exe.sha256"
|
||||
fi
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: |
|
||||
WeKnora-Lite-App_*
|
||||
retention-days: 3
|
||||
|
||||
# ── 3. Create GitHub Release ──
|
||||
release:
|
||||
needs: [build-binary, build-desktop-app]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: release-*
|
||||
merge-multiple: true
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: desktop-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
run: |
|
||||
if [ -n "${{ inputs.tag }}" ]; then
|
||||
VERSION="${{ inputs.tag }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
fi
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate release notes
|
||||
id: notes
|
||||
run: |
|
||||
cat > notes.md << 'NOTES'
|
||||
## WeKnora Lite ${{ steps.ver.outputs.version }}
|
||||
|
||||
### Web CLI 版(命令行 / 服务器部署)
|
||||
|
||||
单二进制部署,零外部依赖(无需 Docker / PostgreSQL / Redis)。
|
||||
|
||||
```bash
|
||||
# 1. 解压
|
||||
tar xzf WeKnora-lite_${{ steps.ver.outputs.version }}_<os>_<arch>.tar.gz
|
||||
cd WeKnora-lite_${{ steps.ver.outputs.version }}_<os>_<arch>
|
||||
|
||||
# 2. 配置
|
||||
cp .env.lite.example .env.lite
|
||||
# 编辑 .env.lite,至少确认 OLLAMA_BASE_URL 正确
|
||||
|
||||
# 3. 启动 Ollama(如尚未运行)
|
||||
ollama serve &
|
||||
ollama pull qwen2.5:7b
|
||||
ollama pull nomic-embed-text
|
||||
|
||||
# 4. 运行
|
||||
set -a && source .env.lite && set +a
|
||||
./WeKnora-lite
|
||||
# 访问 http://localhost:8080
|
||||
```
|
||||
|
||||
| 文件 | 平台 |
|
||||
|------|------|
|
||||
| `WeKnora-lite_*_linux_amd64.tar.gz` | Linux x86_64 |
|
||||
| `WeKnora-lite_*_linux_arm64.tar.gz` | Linux ARM64 |
|
||||
| `WeKnora-lite_*_darwin_amd64.tar.gz` | macOS Intel |
|
||||
| `WeKnora-lite_*_darwin_arm64.tar.gz` | macOS Apple Silicon |
|
||||
|
||||
---
|
||||
|
||||
### 桌面 APP 版(开箱即用,无需命令行)
|
||||
|
||||
原生桌面应用,内置 Web 服务,双击即可运行。
|
||||
|
||||
| 文件 | 平台 |
|
||||
|------|------|
|
||||
| `WeKnora-Lite-App_*_macOS_universal.dmg` | macOS (Intel + Apple Silicon) |
|
||||
| `WeKnora-Lite-App_*_macOS_amd64.dmg` | macOS Intel |
|
||||
| `WeKnora-Lite-App_*_macOS_arm64.dmg` | macOS Apple Silicon |
|
||||
| `WeKnora-Lite-App_*_linux_amd64.tar.gz` | Linux x86_64 |
|
||||
| `WeKnora-Lite-App_*_windows_amd64_setup.exe` | Windows x86_64 (安装包) |
|
||||
|
||||
详细文档见 [LITE.md](docs/LITE.md)。
|
||||
NOTES
|
||||
|
||||
- name: Create release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release create "${{ steps.ver.outputs.version }}" \
|
||||
--title "WeKnora Lite ${{ steps.ver.outputs.version }}" \
|
||||
--notes-file notes.md \
|
||||
WeKnora-lite_*.tar.gz \
|
||||
WeKnora-lite_*.tar.gz.sha256 \
|
||||
WeKnora-Lite-App_* || true
|
||||
|
||||
# ── 4. Update Homebrew Formula ──
|
||||
update-homebrew:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
run: |
|
||||
if [ -n "${{ inputs.tag }}" ]; then
|
||||
VERSION="${{ inputs.tag }}"
|
||||
else
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
fi
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Wait for release assets to be available
|
||||
run: sleep 15
|
||||
|
||||
- name: Update Formula
|
||||
run: ./scripts/update-homebrew-formula.sh "${{ steps.ver.outputs.version }}"
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add Formula/weknora-lite.rb
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to Formula"
|
||||
else
|
||||
git commit -m "formula: update weknora-lite to ${{ steps.ver.outputs.version }}"
|
||||
git push origin main
|
||||
fi
|
||||
Reference in New Issue
Block a user