name: CI # 只认 main:push 到 main、以及任何以 main 为目标的 PR。 # 开发分支(feat/*)自己 push 不跑 CI,靠合入 main 前的 PR 把关。 on: push: branches: [main] pull_request: branches: [main] # 同一分支新 push 取消上一次未完成的运行,省 CI 时间。 concurrency: group: ci-${{ github.ref }} cancel-in-progress: true jobs: go: name: Go · build + vet + test # 本 CI 只在 GitHub 跑;Gitea(git.sundynix.cn)只跑 .gitea/workflows/deploy.yml,不误跑这套。 if: ${{ !contains(github.server_url, 'sundynix.cn') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: "1.25" cache-dependency-path: "**/go.sum" - name: build + vet + test -race(4 模块;bus 用内嵌 NATS,无需外部服务) run: | set -e for m in sundynix-shared sundynix-gateway sundynix-dispatcher sundynix-mcp-go; do echo "::group::$m" # -race:数据竞争一票否决(4 模块本地已验证 race-clean)。ubuntu runner 自带 gcc,CGO 可用。 (cd "$m" && go build ./... && go vet ./... && go test -race ./...) echo "::endgroup::" done # golangci-lint:只卡「新问题」——存量约 42 处(未检 Close/死代码)单独消化,不拿存量红门。 # 仅在 PR 跑:dev→main 的 PR 是唯一合入口,据 base 算 diff;push 到 main 不重复跑。 lint: name: Go · golangci-lint (new issues) if: ${{ github.event_name == 'pull_request' && !contains(github.server_url, 'sundynix.cn') }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: module: [sundynix-shared, sundynix-gateway, sundynix-dispatcher, sundynix-mcp-go] steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # only-new-issues 要据 base 分支算 diff,需完整历史 - uses: actions/setup-go@v5 with: go-version: "1.25" cache-dependency-path: "**/go.sum" - uses: golangci/golangci-lint-action@v6 with: version: latest working-directory: ${{ matrix.module }} only-new-issues: true # 安全扫描:govulncheck(依赖 CVE) advisory —— stdlib/nats CVE 要靠 toolchain/依赖升级, # 不拿它红门,只做可见性;gitleaks(密钥泄漏)在 PR 上扫 diff,硬拦提交密钥。 security: name: Security · govulncheck + gitleaks if: ${{ !contains(github.server_url, 'sundynix.cn') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-go@v5 with: go-version: "1.25" cache-dependency-path: "**/go.sum" - name: govulncheck(advisory:报告不阻断) continue-on-error: true run: | go install golang.org/x/vuln/cmd/govulncheck@latest for m in sundynix-shared sundynix-gateway sundynix-dispatcher sundynix-mcp-go; do echo "::group::govulncheck $m" (cd "$m" && govulncheck ./...) || true echo "::endgroup::" done - name: gitleaks(扫提交历史,命中即失败) uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} web: name: Frontend · tsc + vitest if: ${{ !contains(github.server_url, 'sundynix.cn') }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: dir: [sundynix-desktop/frontend, sundynix-admin, sundynix-web] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: npm cache-dependency-path: ${{ matrix.dir }}/package-lock.json - name: install + typecheck + test(三个前端都有 vitest,此前只跑 tsc,测试从没进过关卡) working-directory: ${{ matrix.dir }} run: | npm ci npx tsc --noEmit npm test # desktop 的 Go 模块不在 go.work 里,上面的 go job 从来没测过它(app.go 的 # download/另存为等桥方法有单测但 CI 一次没跑过)。用 macos runner:一来免装 # gtk/webkit(linux 编 wails 要一堆 CGO 头),二来 darwin 才是实际发行目标。 # go:embed frontend/dist 要求先出前端产物,故先 npm build。 desktop: name: Desktop · go build + test (macOS) if: ${{ !contains(github.server_url, 'sundynix.cn') }} runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: "1.25" cache-dependency-path: sundynix-desktop/go.sum - uses: actions/setup-node@v4 with: node-version: "20" cache: npm cache-dependency-path: sundynix-desktop/frontend/package-lock.json - name: 前端构建(供 go:embed) working-directory: sundynix-desktop/frontend run: | npm ci npm run build - name: go build + vet + test working-directory: sundynix-desktop env: GOWORK: "off" # 必须加引号:YAML 裸 off 会被解析成布尔 false run: | go build ./... go vet ./... go test ./... py: name: mcp-py · sandbox guard if: ${{ !contains(github.server_url, 'sundynix.cn') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip - name: install + test(无 Docker 时测降级路径) working-directory: sundynix-mcp-py run: | python -m pip install --upgrade pip python -m pip install -e . pytest PYTHONPATH=src:tests python -m pytest tests/ -q || \ PYTHONPATH=src:tests python -c "import test_sandbox as t; \ fns=[getattr(t,n) for n in dir(t) if n.startswith('test_')]; \ [ (f(),print('PASS',f.__name__)) for f in fns ]; print(f'{len(fns)} passed')"