Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: release

on:
push:
tags: ["v*"]

permissions:
contents: read

concurrency:
# 发布不允许被后续运行打断。
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
# 复用测试工作流的全部门禁(lint / test / 协议一致性),门禁不过不发布。
test:
uses: ./.github/workflows/test.yaml
permissions:
contents: read

build:
needs: test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: darwin
goarch: amd64
arch: x86_64
format: tar.gz
- goos: darwin
goarch: arm64
arch: arm64
format: tar.gz
- goos: linux
goarch: amd64
arch: x86_64
format: tar.gz
- goos: linux
goarch: arm64
arch: arm64
format: tar.gz
- goos: windows
goarch: amd64
arch: x86_64
format: zip
- goos: windows
goarch: arm64
arch: arm64
format: zip
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: 构建并打包
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#v}"
BUILD_DATE=$(git show -s --format=%cI HEAD)
COMMIT_TIMESTAMP=$(git show -s --format=%ct HEAD)
PACKAGE="sctl_${VERSION}_${GOOS}_${{ matrix.arch }}"
STAGE="dist/${PACKAGE}"
BINARY=sctl
if [ "$GOOS" = windows ]; then
BINARY=sctl.exe
fi

mkdir -p "$STAGE/docs"
go build -trimpath -ldflags "-s -w \
-X github.com/scriptscat/sctl/internal/cli.Version=${VERSION} \
-X github.com/scriptscat/sctl/internal/cli.Commit=${GITHUB_SHA} \
-X github.com/scriptscat/sctl/internal/cli.BuildDate=${BUILD_DATE}" \
-o "$STAGE/$BINARY" ./cmd/sctl
cp README.md LICENSE* "$STAGE/"
cp -R docs/. "$STAGE/docs/"
find "$STAGE" -exec touch -d "@${COMMIT_TIMESTAMP}" {} +

if [ "${{ matrix.format }}" = zip ]; then
(cd dist && zip -X -q -r "${PACKAGE}.zip" "$PACKAGE")
else
tar --sort=name --mtime="@${COMMIT_TIMESTAMP}" --owner=0 --group=0 --numeric-owner \
-C dist -czf "dist/${PACKAGE}.tar.gz" "$PACKAGE"
fi
- uses: actions/upload-artifact@v7
with:
name: release-${{ matrix.goos }}-${{ matrix.goarch }}
path: dist/*.${{ matrix.format }}
if-no-files-found: error

release:
name: Create Release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write # 创建 GitHub Release 并上传产物
id-token: write # 构建来源证明(build provenance)签名
attestations: write
steps:
- uses: actions/checkout@v5
- uses: actions/download-artifact@v8
with:
path: artifacts
pattern: release-*
merge-multiple: true
- name: 生成校验和
run: cd artifacts && sha256sum * > checksums.txt
- name: 判断是否为预发布
id: prerelease
env:
TAG: ${{ github.ref_name }}
run: |
if [[ "$TAG" == *-* ]]; then
echo "flag=--prerelease" >> "$GITHUB_OUTPUT"
fi
- name: 创建 GitHub 草稿 Release
env:
GH_TOKEN: ${{ github.token }}
PRERELEASE_FLAG: ${{ steps.prerelease.outputs.flag }}
run: |
gh release create "$GITHUB_REF_NAME" \
--draft \
--title "$GITHUB_REF_NAME" \
--generate-notes \
$PRERELEASE_FLAG \
artifacts/*
- name: 生成构建来源证明
# 只对 checksums.txt 签名即可:它覆盖了全部产物的 sha256,
# 用户先 `sha256sum -c` 再 `gh attestation verify checksums.txt` 就形成完整信任链。
uses: actions/attest-build-provenance@v4
with:
subject-path: artifacts/checksums.txt
- name: 输出发布摘要
run: |
{
echo "## sctl ${GITHUB_REF_NAME} 发布产物"
echo
echo '```'
cat artifacts/checksums.txt
echo '```'
echo
echo "Release 以**草稿**形式创建,确认 changelog 后手动点击 Publish。"
} >> "$GITHUB_STEP_SUMMARY"
74 changes: 74 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: test

on:
push:
branches: [main, "release/**"]
pull_request:
# 供 release 工作流复用:打 tag 发布前跑同一套门禁,避免两边规则漂移。
workflow_call:
workflow_dispatch:

permissions:
contents: read

concurrency:
# PR 上新提交会取消上一次运行;main / release 分支保留每次运行。
group: test-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
name: lint
runs-on: ubuntu-latest
env:
# 与本地开发保持同一版本,避免"本地过 CI 挂"。升级时同步改 docs/development.md。
GOLANGCI_LINT_VERSION: v2.12.2
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- uses: golangci/golangci-lint-action@v9
with:
version: ${{ env.GOLANGCI_LINT_VERSION }}

test:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- run: go build ./...
- run: go vet ./...
- run: go test -race ./...

protocol-drift:
# protocol.json 是扩展与守卫共用的单一事实源(见 docs/protocol.md):两份镜像一旦漂移,
# 双方对 action / scope / 限值的理解就会分叉,且只在运行时才暴露。这里做逐字节比对。
#
# 扩展侧该文件随 MCP 桥接 PR 合入 main 后此 job 才能通过;在那之前把仓库变量
# EXT_PROTOCOL_REF 指到对应 PR 分支。
name: protocol-drift
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: 拉取扩展侧协议镜像
env:
EXT_REF: ${{ vars.EXT_PROTOCOL_REF || 'main' }}
run: |
set -euo pipefail
url="https://raw.githubusercontent.com/scriptscat/scriptcat/${EXT_REF}/src/app/service/service_worker/mcp/protocol.json"
if ! curl -fsSL "$url" -o ext-protocol.json; then
echo "::error::拉取扩展侧 protocol.json 失败(ref=${EXT_REF})。扩展侧尚未合入时,请把仓库变量 EXT_PROTOCOL_REF 指到对应分支。"
exit 1
fi
- name: 比对协议单源
run: |
if ! diff -u ext-protocol.json internal/pkg/protocol/protocol.json; then
echo "::error::protocol.json 与扩展侧镜像不一致,两侧须同步更新。"
exit 1
fi
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/sctl
/bin/
dist/
# 一次性端到端验证脚本与证据(见 docs/verification.md),永远不进版本库
/e2e/scratch/
52 changes: 52 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
version: "2"

# 注意:golangci-lint 只分析当前 GOOS 下参与构建的文件,`_windows.go` 一类
# 带构建标签的代码在 Linux 测试 CI 上不会被检查;跨平台产物由发布流程编译。

linters:
default: standard # errcheck govet ineffassign staticcheck unused
enable:
- bodyclose # HTTP body 未关闭
- errorlint # %w / errors.Is 用法
- gocritic
- misspell
- nilerr # 拿到 err 却返回 nil
- nolintlint # //nolint 必须写明理由
- revive
- unconvert
- usestdlibvars
- whitespace
settings:
errcheck:
check-type-assertions: true
nolintlint:
require-explanation: true
require-specific: true
revive:
rules:
- name: exported
disabled: true # 内部包为主,不强制导出符号注释
- name: unused-parameter
disabled: true
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
# 测试里大量 defer close / 断言,放宽噪音较大的检查。
- path: _test\.go
linters:
- bodyclose
- errcheck

formatters:
enable:
- gofmt
- goimports
settings:
goimports:
local-prefixes:
- github.com/scriptscat/sctl
Loading
Loading