ci: add validation workflows and project tooling
This commit is contained in:
1 parent
c41b1d350a
commit
8794686c3a
4 files changed
+367
No files matched your search
@@ -0,0 +1,101 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
go:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- run: go run github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1 diff
|
||||
- run: go vet ./...
|
||||
- run: go run ./cmd/protocolgen -check
|
||||
- run: sudo apt-get update && sudo apt-get install --yes --no-install-recommends ffmpeg
|
||||
timeout-minutes: 30
|
||||
- run: go test -race ./...
|
||||
- uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.15.0
|
||||
|
||||
web:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: web
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- run: npm ci
|
||||
- run: npm run type-check
|
||||
- run: npm run lint
|
||||
- run: npm test
|
||||
- run: npm run build
|
||||
|
||||
images:
|
||||
needs: [go, web]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- image: server
|
||||
file: Dockerfile
|
||||
target: alisten
|
||||
- image: worker
|
||||
file: worker.Dockerfile
|
||||
target: alisten-worker
|
||||
- image: web
|
||||
file: web/Dockerfile
|
||||
target: ""
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: docker/setup-qemu-action@v4
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}/${{ matrix.image }}
|
||||
flavor: latest=false
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=semver,pattern={{version}}
|
||||
type=sha,prefix=
|
||||
- uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.file }}
|
||||
target: ${{ matrix.target }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
build-args: VERSION=${{ github.sha }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha,scope=${{ matrix.image }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.image }}
|
||||
@@ -0,0 +1,4 @@
|
||||
/alisten
|
||||
/web/node_modules/
|
||||
/web/dist/
|
||||
.env
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
version: "2"
|
||||
|
||||
linters:
|
||||
default: standard
|
||||
enable:
|
||||
- depguard
|
||||
- forbidigo
|
||||
exclusions:
|
||||
rules:
|
||||
# 这两个文件中未检查的错误不影响结果:decode.go 跳过 fmt 块的对齐填充失败时,读取下一个块头会报错;loudness_bench_test.go 的 binary.Write 写入 bytes.Buffer,不会失败。
|
||||
- path: internal/lib/wav/decode\.go|internal/lib/loudness/loudness_bench_test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
# forbidigo 只检查 domain,其他目录可以读系统时钟。
|
||||
- path-except: ^internal/domain/
|
||||
linters:
|
||||
- forbidigo
|
||||
settings:
|
||||
depguard:
|
||||
# 不以 $ 结尾的条目按前缀匹配。depguard 只拿排序后紧挨在导入路径之前的一项比较,已有前缀的子包不要再单列:
|
||||
# deny 中同时有 os 和 os/exec 时,os/signal 就不再被禁止。
|
||||
rules:
|
||||
domain:
|
||||
list-mode: strict
|
||||
files:
|
||||
- "**/internal/domain/**"
|
||||
- "!$test"
|
||||
allow:
|
||||
- $gostd
|
||||
- github.com/dihemoe/alisten/internal/domain
|
||||
- github.com/dihemoe/alisten/internal/lib
|
||||
deny:
|
||||
- pkg: os
|
||||
desc: domain 不做 I/O
|
||||
- pkg: net$
|
||||
desc: domain 不做 I/O
|
||||
- pkg: net/http
|
||||
desc: domain 不做 I/O
|
||||
- pkg: database/sql
|
||||
desc: domain 不做 I/O
|
||||
- pkg: io/fs
|
||||
desc: domain 不做 I/O
|
||||
- pkg: path/filepath
|
||||
desc: domain 不做 I/O
|
||||
- pkg: syscall
|
||||
desc: domain 不做 I/O
|
||||
- pkg: net/textproto
|
||||
desc: domain 不做 I/O
|
||||
- pkg: net/smtp
|
||||
desc: domain 不做 I/O
|
||||
- pkg: net/rpc
|
||||
desc: domain 不做 I/O
|
||||
- pkg: crypto/tls
|
||||
desc: domain 不做 I/O
|
||||
- pkg: log/syslog
|
||||
desc: domain 不做 I/O
|
||||
- pkg: plugin
|
||||
desc: domain 不做 I/O
|
||||
- pkg: sync
|
||||
desc: domain 不加锁
|
||||
domain-test:
|
||||
list-mode: strict
|
||||
files:
|
||||
- "**/internal/domain/**/*_test.go"
|
||||
allow:
|
||||
- $gostd
|
||||
- github.com/dihemoe/alisten/internal/domain
|
||||
- github.com/dihemoe/alisten/internal/lib
|
||||
- golang.org/x/tools/txtar
|
||||
app:
|
||||
list-mode: strict
|
||||
files:
|
||||
- "**/internal/app/**"
|
||||
- "!$test"
|
||||
allow:
|
||||
- $gostd
|
||||
- github.com/dihemoe/alisten/internal/domain
|
||||
- github.com/dihemoe/alisten/internal/lib
|
||||
- github.com/dihemoe/alisten/internal/app
|
||||
# 用于房间密码的 argon2id 哈希;纯计算,不做 I/O。
|
||||
- golang.org/x/crypto/argon2
|
||||
# 用于合并同一首歌的并发播放地址解析;只协调 goroutine,不做 I/O。
|
||||
- golang.org/x/sync/singleflight
|
||||
deny:
|
||||
- pkg: os
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: net$
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: net/http
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: database/sql
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: io/fs
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: path/filepath
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: syscall
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: net/textproto
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: net/smtp
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: net/rpc
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: crypto/tls
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: log/syslog
|
||||
desc: app 的 I/O 经由出站端口
|
||||
- pkg: plugin
|
||||
desc: app 的 I/O 经由出站端口
|
||||
app-test:
|
||||
list-mode: strict
|
||||
files:
|
||||
- "**/internal/app/**/*_test.go"
|
||||
allow:
|
||||
- $gostd
|
||||
- github.com/dihemoe/alisten/internal/domain
|
||||
- github.com/dihemoe/alisten/internal/lib
|
||||
- github.com/dihemoe/alisten/internal/app
|
||||
- golang.org/x/tools/txtar
|
||||
porttest:
|
||||
files:
|
||||
- "!$test"
|
||||
deny:
|
||||
- pkg: github.com/dihemoe/alisten/internal/app/port/porttest
|
||||
desc: 端口的内存实现只供测试使用
|
||||
lib:
|
||||
files:
|
||||
- "**/internal/lib/**"
|
||||
deny:
|
||||
- pkg: github.com/dihemoe/alisten/internal/domain
|
||||
desc: lib 与业务无关,不能依赖 domain
|
||||
- pkg: github.com/dihemoe/alisten/internal/app
|
||||
desc: lib 与业务无关,不能依赖 app
|
||||
- pkg: github.com/dihemoe/alisten/internal/adapter
|
||||
desc: lib 与业务无关,不能依赖 adapter
|
||||
- pkg: github.com/dihemoe/alisten/internal/bootstrap
|
||||
desc: lib 与业务无关,不能依赖 bootstrap
|
||||
- pkg: github.com/dihemoe/alisten/protocol
|
||||
desc: protocol 只能被 internal/adapter/in 使用
|
||||
# app 通过 logctx 记录日志的关联字段;logctx 只依赖标准库,app 才不会经由它依赖 OpenTelemetry。
|
||||
logctx:
|
||||
list-mode: strict
|
||||
files:
|
||||
- "**/internal/lib/logctx/**"
|
||||
allow:
|
||||
- $gostd
|
||||
adapter-in:
|
||||
files:
|
||||
- "**/internal/adapter/in/**"
|
||||
deny:
|
||||
- pkg: github.com/dihemoe/alisten/internal/adapter
|
||||
desc: 只有 bootstrap 可以 import adapter
|
||||
- pkg: github.com/dihemoe/alisten/internal/bootstrap
|
||||
desc: adapter 不能依赖 bootstrap
|
||||
adapter-out:
|
||||
list-mode: lax
|
||||
files:
|
||||
- "**/internal/adapter/out/**"
|
||||
# sqlc 把查询代码生成在 postgres 适配器的 internal 目录下;lax 模式让这个更长的 allow 前缀优先于 deny。
|
||||
allow:
|
||||
- github.com/dihemoe/alisten/internal/adapter/out/postgres/internal/query
|
||||
deny:
|
||||
- pkg: github.com/dihemoe/alisten/internal/adapter
|
||||
desc: 只有 bootstrap 可以 import adapter
|
||||
- pkg: github.com/dihemoe/alisten/internal/bootstrap
|
||||
desc: adapter 不能依赖 bootstrap
|
||||
- pkg: github.com/dihemoe/alisten/protocol
|
||||
desc: protocol 只能被 internal/adapter/in 使用
|
||||
bootstrap:
|
||||
files:
|
||||
- "**/internal/bootstrap/**"
|
||||
deny:
|
||||
- pkg: github.com/dihemoe/alisten/protocol
|
||||
desc: protocol 只能被 internal/adapter/in 使用
|
||||
cmd:
|
||||
files:
|
||||
- "**/cmd/**"
|
||||
deny:
|
||||
- pkg: github.com/dihemoe/alisten/internal/adapter
|
||||
desc: 只有 bootstrap 可以 import adapter
|
||||
- pkg: github.com/dihemoe/alisten/protocol
|
||||
desc: protocol 只能被 internal/adapter/in 使用
|
||||
protocol:
|
||||
list-mode: strict
|
||||
files:
|
||||
- "**/protocol/**"
|
||||
allow:
|
||||
- $gostd
|
||||
forbidigo:
|
||||
analyze-types: true
|
||||
forbid:
|
||||
- pattern: ^time\.(Now|Since|Until)$
|
||||
msg: domain 不读系统时钟,时间作为输入传入
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/dihemoe/alisten
|
||||
@@ -0,0 +1,59 @@
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
// 一个文件匹配到多个设置 no-restricted-imports 的配置对象时,只有最后一个生效:features 因此要带上这一条;core 和 shared 禁止引用 features 的任何路径,已经包含它。
|
||||
const featureInternals = {
|
||||
pattern: /^(?:@\/|\.\.?\/(?:.*\/)?)features\/[^/]+\/[^/]/i,
|
||||
message: '功能之间只能通过对方的 index.ts 引用。',
|
||||
}
|
||||
|
||||
const appImports = {
|
||||
pattern: /^(?:@\/|\.\.\/(?:.*\/)?)app(?:\/|$)/i,
|
||||
message: 'features、core 和 shared 不能依赖 app。',
|
||||
}
|
||||
|
||||
// no-restricted-imports 不检查 import(),动态导入由 no-restricted-syntax 用同一个正则检查。
|
||||
// no-restricted-syntax 同样只有最后一个配置对象生效,所以要带上 @antfu/eslint-config 设置的两项,升级 antfu 时要与它保持一致。
|
||||
function restrictImports(...restrictions) {
|
||||
return {
|
||||
'no-restricted-imports': ['error', { patterns: restrictions.map(({ pattern, message }) => ({ regex: pattern.source, message })) }],
|
||||
'no-restricted-syntax': ['error', 'TSEnumDeclaration[const=true]', 'TSExportAssignment', ...restrictions.map(({ pattern, message }) => ({ selector: `ImportExpression[source.value=${pattern}]`, message }))],
|
||||
}
|
||||
}
|
||||
|
||||
export default antfu(
|
||||
{
|
||||
react: true,
|
||||
// lint:fix 会改写生成物,之后 protocolgen -check 就会失败。
|
||||
ignores: ['src/**/*.gen.ts'],
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.{ts,tsx}'],
|
||||
rules: restrictImports(featureInternals),
|
||||
},
|
||||
{
|
||||
files: ['src/features/**/*.{ts,tsx}'],
|
||||
rules: restrictImports(featureInternals, {
|
||||
pattern: /^\.\.\//,
|
||||
message: '功能内部使用相对路径,功能之外的模块用 @/ 引用。',
|
||||
}, appImports),
|
||||
},
|
||||
{
|
||||
files: ['src/core/**/*.{ts,tsx}'],
|
||||
rules: restrictImports({
|
||||
pattern: /^(?:@\/|\.\.\/(?:.*\/)?)features(?:\/|$)/i,
|
||||
message: 'core 不能依赖 features。',
|
||||
}, appImports),
|
||||
},
|
||||
{
|
||||
files: ['src/shared/**/*.{ts,tsx}'],
|
||||
rules: restrictImports({
|
||||
pattern: /^(?:@\/|\.\.\/(?:.*\/)?)(?:core|features)(?:\/|$)/i,
|
||||
message: 'shared 不能依赖 core 和 features。',
|
||||
}, appImports),
|
||||
},
|
||||
{
|
||||
// @antfu/eslint-config 对声明文件关闭了 no-restricted-syntax,上面的配置对象会把它重新打开。
|
||||
files: ['**/*.d.?([cm])ts'],
|
||||
rules: { 'no-restricted-syntax': 'off' },
|
||||
},
|
||||
)
|
||||
Reference in new issue
Block a user