init
This commit is contained in:
638
docs/DEVELOPMENT.md
Normal file
638
docs/DEVELOPMENT.md
Normal file
@@ -0,0 +1,638 @@
|
||||
# DevPack 开发指南
|
||||
|
||||
> 版本:v1.0.0-draft
|
||||
> 更新日期:2026-03-03
|
||||
> 作者:DevPack Team
|
||||
|
||||
---
|
||||
|
||||
## 1. 开发环境搭建
|
||||
|
||||
### 1.1 前置要求
|
||||
|
||||
| 工具 | 最低版本 | 安装方式 |
|
||||
|------|---------|---------|
|
||||
| Go | 1.22+ | [golang.org](https://golang.org/dl/) |
|
||||
| Git | 2.30+ | [git-scm.com](https://git-scm.com/) |
|
||||
| Make | 3.81+ | 系统包管理器(Windows 推荐用 `choco install make`) |
|
||||
| golangci-lint | 1.55+ | `go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest` |
|
||||
|
||||
### 1.2 获取代码
|
||||
|
||||
```bash
|
||||
git clone https://github.com/user/devpack.git
|
||||
cd devpack
|
||||
```
|
||||
|
||||
### 1.3 安装依赖
|
||||
|
||||
```bash
|
||||
go mod download
|
||||
```
|
||||
|
||||
### 1.4 验证环境
|
||||
|
||||
```bash
|
||||
# 运行测试
|
||||
go test ./...
|
||||
|
||||
# 构建
|
||||
go build -o devpack ./cmd/devpack/
|
||||
|
||||
# 代码检查
|
||||
golangci-lint run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 项目结构详解
|
||||
|
||||
```
|
||||
DevPack/
|
||||
├── cmd/ # 应用入口
|
||||
│ └── devpack/
|
||||
│ ├── main.go # 主入口
|
||||
│ └── commands/ # CLI 命令
|
||||
│ ├── root.go # 根命令
|
||||
│ ├── init.go # devpack init
|
||||
│ ├── scan.go # devpack scan
|
||||
│ ├── capture.go # devpack capture
|
||||
│ ├── restore.go # devpack restore
|
||||
│ ├── diff.go # devpack diff
|
||||
│ ├── export.go # devpack export
|
||||
│ ├── import.go # devpack import
|
||||
│ ├── list.go # devpack list
|
||||
│ ├── profile.go # devpack profile
|
||||
│ └── version.go # devpack version
|
||||
│
|
||||
├── internal/ # 内部包
|
||||
│ ├── collector/ # 采集器核心
|
||||
│ │ ├── collector.go # Collector 接口定义
|
||||
│ │ ├── registry.go # 采集器注册中心
|
||||
│ │ ├── options.go # 扫描/捕获/还原选项
|
||||
│ │ ├── result.go # 结果类型定义
|
||||
│ │ │
|
||||
│ │ ├── runtime/ # 运行时采集器
|
||||
│ │ │ ├── go.go # Go 采集器
|
||||
│ │ │ ├── node.go # Node.js 采集器
|
||||
│ │ │ ├── python.go # Python 采集器
|
||||
│ │ │ ├── java.go # Java 采集器
|
||||
│ │ │ ├── rust.go # Rust 采集器
|
||||
│ │ │ ├── dotnet.go # .NET 采集器
|
||||
│ │ │ └── runtime_test.go # 运行时采集器测试
|
||||
│ │ │
|
||||
│ │ ├── editor/ # 编辑器采集器
|
||||
│ │ │ ├── vscode.go # VS Code 采集器
|
||||
│ │ │ ├── jetbrains.go # JetBrains 采集器
|
||||
│ │ │ ├── vim.go # Vim/Neovim 采集器
|
||||
│ │ │ └── editor_test.go
|
||||
│ │ │
|
||||
│ │ ├── shell/ # Shell 采集器
|
||||
│ │ │ ├── powershell.go # PowerShell 采集器
|
||||
│ │ │ ├── bash.go # Bash 采集器
|
||||
│ │ │ ├── zsh.go # Zsh 采集器
|
||||
│ │ │ ├── fish.go # Fish 采集器
|
||||
│ │ │ ├── terminal.go # 终端模拟器配置(Windows Terminal 等)
|
||||
│ │ │ └── shell_test.go
|
||||
│ │ │
|
||||
│ │ ├── pkgmgr/ # 包管理器采集器
|
||||
│ │ │ ├── scoop.go # Scoop 采集器
|
||||
│ │ │ ├── chocolatey.go # Chocolatey 采集器
|
||||
│ │ │ ├── winget.go # Winget 采集器
|
||||
│ │ │ ├── homebrew.go # Homebrew 采集器
|
||||
│ │ │ ├── apt.go # APT 采集器
|
||||
│ │ │ └── pkgmgr_test.go
|
||||
│ │ │
|
||||
│ │ ├── git/ # Git 配置采集器
|
||||
│ │ │ ├── gitconfig.go
|
||||
│ │ │ └── git_test.go
|
||||
│ │ │
|
||||
│ │ ├── env/ # 环境变量采集器
|
||||
│ │ │ ├── envvar.go
|
||||
│ │ │ └── env_test.go
|
||||
│ │ │
|
||||
│ │ ├── font/ # 字体采集器
|
||||
│ │ │ ├── font.go
|
||||
│ │ │ └── font_test.go
|
||||
│ │ │
|
||||
│ │ └── ssh/ # SSH/GPG 采集器
|
||||
│ │ ├── ssh.go
|
||||
│ │ └── ssh_test.go
|
||||
│ │
|
||||
│ ├── pack/ # 打包引擎
|
||||
│ │ ├── packer.go # 打包器
|
||||
│ │ ├── unpacker.go # 解包器
|
||||
│ │ ├── format.go # Pack 格式定义
|
||||
│ │ └── pack_test.go
|
||||
│ │
|
||||
│ ├── restore/ # 还原引擎
|
||||
│ │ ├── engine.go # 还原核心逻辑
|
||||
│ │ ├── planner.go # 还原计划生成
|
||||
│ │ ├── conflict.go # 冲突检测与处理
|
||||
│ │ ├── rollback.go # 回滚管理
|
||||
│ │ ├── verifier.go # 还原验证
|
||||
│ │ └── restore_test.go
|
||||
│ │
|
||||
│ ├── profile/ # 配置文件管理
|
||||
│ │ ├── profile.go # Profile 定义与操作
|
||||
│ │ ├── parser.go # YAML 解析
|
||||
│ │ └── profile_test.go
|
||||
│ │
|
||||
│ ├── diff/ # 差异对比
|
||||
│ │ ├── differ.go # 差异计算
|
||||
│ │ ├── renderer.go # 差异展示
|
||||
│ │ └── diff_test.go
|
||||
│ │
|
||||
│ ├── crypto/ # 加密模块
|
||||
│ │ ├── aesgcm.go # AES-256-GCM 实现
|
||||
│ │ ├── argon2.go # Argon2id 密钥派生
|
||||
│ │ └── crypto_test.go
|
||||
│ │
|
||||
│ ├── platform/ # 平台适配层
|
||||
│ │ ├── platform.go # Platform 接口
|
||||
│ │ ├── detect.go # 平台检测
|
||||
│ │ ├── windows.go # Windows 实现
|
||||
│ │ ├── darwin.go # macOS 实现
|
||||
│ │ ├── linux.go # Linux 实现
|
||||
│ │ └── platform_test.go
|
||||
│ │
|
||||
│ ├── config/ # 应用配置
|
||||
│ │ ├── config.go # 全局配置
|
||||
│ │ └── paths.go # 路径管理
|
||||
│ │
|
||||
│ ├── ui/ # 终端 UI
|
||||
│ │ ├── printer.go # 格式化输出
|
||||
│ │ ├── progress.go # 进度条
|
||||
│ │ ├── table.go # 表格输出
|
||||
│ │ └── prompt.go # 用户交互
|
||||
│ │
|
||||
│ └── logger/ # 日志
|
||||
│ └── logger.go # 日志初始化
|
||||
│
|
||||
├── pkg/ # 公共包
|
||||
│ ├── manifest/ # Pack 清单
|
||||
│ │ ├── manifest.go # Manifest 结构定义
|
||||
│ │ └── validate.go # Manifest 验证
|
||||
│ └── version/ # 版本信息
|
||||
│ └── version.go # 版本号管理
|
||||
│
|
||||
├── plugins/ # 外部插件目录
|
||||
│ └── README.md # 插件开发指南
|
||||
│
|
||||
├── test/ # 集成与端到端测试
|
||||
│ ├── e2e/ # 端到端测试
|
||||
│ │ ├── capture_restore_test.go
|
||||
│ │ └── testdata/ # 测试数据
|
||||
│ └── fixtures/ # 测试 fixtures
|
||||
│ └── sample-pack/
|
||||
│
|
||||
├── scripts/ # 脚本
|
||||
│ ├── build.sh # 构建脚本
|
||||
│ ├── install.sh # 安装脚本
|
||||
│ └── release.sh # 发布脚本
|
||||
│
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ ├── ci.yml # CI 流水线
|
||||
│ └── release.yml # 发布流水线
|
||||
│
|
||||
├── .golangci.yml # Lint 配置
|
||||
├── .gitignore
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── Makefile
|
||||
├── LICENSE
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 编码规范
|
||||
|
||||
### 3.1 Go 编码规范
|
||||
|
||||
遵循 [Effective Go](https://golang.org/doc/effective_go) 和 [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)。
|
||||
|
||||
#### 命名规范
|
||||
|
||||
```go
|
||||
// ✅ 包名:小写单词,不用下划线
|
||||
package collector
|
||||
package pkgmgr
|
||||
|
||||
// ✅ 接口名:动词或名词,单方法接口用 -er 后缀
|
||||
type Collector interface { ... }
|
||||
type Scanner interface { ... }
|
||||
|
||||
// ✅ 导出函数/方法:大驼峰
|
||||
func NewRegistry() *Registry { ... }
|
||||
func (r *Registry) Register(c Collector) error { ... }
|
||||
|
||||
// ✅ 非导出函数/方法:小驼峰
|
||||
func parseVersion(raw string) (string, error) { ... }
|
||||
|
||||
// ✅ 常量:大驼峰(导出)或小驼峰(非导出)
|
||||
const DefaultTimeout = 30 * time.Second
|
||||
const maxRetries = 3
|
||||
|
||||
// ✅ 错误变量:Err 前缀
|
||||
var ErrCollectorNotFound = errors.New("collector not found")
|
||||
var ErrPackCorrupted = errors.New("pack file corrupted")
|
||||
```
|
||||
|
||||
#### 错误处理
|
||||
|
||||
```go
|
||||
// ✅ 使用 fmt.Errorf 包装错误,提供上下文
|
||||
func (c *GoCollector) Scan(ctx context.Context) (*ScanResult, error) {
|
||||
version, err := c.detectVersion(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("go collector: detect version: %w", err)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
// ✅ 自定义错误类型用于需要类型判断的场景
|
||||
type CollectorError struct {
|
||||
Collector string
|
||||
Op string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *CollectorError) Error() string {
|
||||
return fmt.Sprintf("%s: %s: %v", e.Collector, e.Op, e.Err)
|
||||
}
|
||||
|
||||
func (e *CollectorError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
```
|
||||
|
||||
#### Context 使用
|
||||
|
||||
```go
|
||||
// ✅ 第一个参数传递 context
|
||||
func (c *GoCollector) Scan(ctx context.Context, opts ScanOptions) (*ScanResult, error) {
|
||||
// 使用 context 控制超时
|
||||
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "go", "version")
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 项目约定
|
||||
|
||||
| 约定 | 说明 |
|
||||
|------|------|
|
||||
| 文件命名 | 小写 + 下划线:`go_collector.go`, `scan_result.go` |
|
||||
| 测试文件 | `*_test.go` 放在同一包下 |
|
||||
| 平台特定 | 使用 Go build tag:`//go:build windows` |
|
||||
| 依赖注入 | 通过接口 + 构造函数实现 |
|
||||
| 配置 | 使用 Viper,支持文件 + 环境变量 + 命令行参数 |
|
||||
| 日志 | 使用 zerolog,结构化日志 |
|
||||
|
||||
### 3.3 Git 提交规范
|
||||
|
||||
使用 [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
**类型 (type):**
|
||||
| Type | 说明 |
|
||||
|------|------|
|
||||
| `feat` | 新功能 |
|
||||
| `fix` | Bug 修复 |
|
||||
| `docs` | 文档 |
|
||||
| `style` | 代码格式 |
|
||||
| `refactor` | 重构 |
|
||||
| `perf` | 性能优化 |
|
||||
| `test` | 测试 |
|
||||
| `chore` | 构建/工具 |
|
||||
|
||||
**Scope 示例:** `collector`, `pack`, `restore`, `cli`, `platform`, `crypto`
|
||||
|
||||
**示例:**
|
||||
```
|
||||
feat(collector): add VS Code extension collector
|
||||
|
||||
Scan and capture VS Code extensions list and settings.
|
||||
|
||||
Closes #42
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 开发工作流
|
||||
|
||||
### 4.1 分支策略
|
||||
|
||||
```
|
||||
main (稳定发布)
|
||||
│
|
||||
├── develop (开发主干)
|
||||
│ │
|
||||
│ ├── feature/collector-vscode (功能分支)
|
||||
│ ├── feature/restore-engine (功能分支)
|
||||
│ └── fix/scan-timeout (修复分支)
|
||||
│
|
||||
└── release/v0.1.0 (发布分支)
|
||||
```
|
||||
|
||||
### 4.2 开发流程
|
||||
|
||||
1. **创建分支:** `git checkout -b feature/xxx develop`
|
||||
2. **开发 & 测试:** 编写代码 + 单元测试
|
||||
3. **代码检查:** `make lint`
|
||||
4. **提交:** 遵循 Conventional Commits
|
||||
5. **推送 & PR:** 推送分支,创建 Pull Request
|
||||
6. **Code Review:** 至少一人 Review 通过
|
||||
7. **合并:** Squash Merge 到 develop
|
||||
|
||||
### 4.3 常用 Make 命令
|
||||
|
||||
```makefile
|
||||
# 构建
|
||||
make build # 构建当前平台
|
||||
make build-all # 构建所有平台
|
||||
make build-windows # 构建 Windows 版本
|
||||
make build-darwin # 构建 macOS 版本
|
||||
make build-linux # 构建 Linux 版本
|
||||
|
||||
# 测试
|
||||
make test # 运行所有测试
|
||||
make test-unit # 运行单元测试
|
||||
make test-integration # 运行集成测试
|
||||
make test-e2e # 运行端到端测试
|
||||
make test-coverage # 生成覆盖率报告
|
||||
|
||||
# 代码质量
|
||||
make lint # 运行 golangci-lint
|
||||
make fmt # 格式化代码
|
||||
make vet # go vet 检查
|
||||
|
||||
# 开发
|
||||
make run # 编译并运行
|
||||
make dev # 开发模式(热重载)
|
||||
make clean # 清理构建产物
|
||||
|
||||
# 发布
|
||||
make release # 使用 GoReleaser 发布
|
||||
make snapshot # GoReleaser 快照(不发布)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 添加新采集器指南
|
||||
|
||||
### 5.1 步骤
|
||||
|
||||
以添加 "Docker" 采集器为例:
|
||||
|
||||
#### Step 1: 创建采集器文件
|
||||
|
||||
```go
|
||||
// internal/collector/runtime/docker.go
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/user/devpack/internal/collector"
|
||||
)
|
||||
|
||||
// DockerCollector Docker 环境采集器
|
||||
type DockerCollector struct{}
|
||||
|
||||
// 确保实现了 Collector 接口
|
||||
var _ collector.Collector = (*DockerCollector)(nil)
|
||||
|
||||
func NewDockerCollector() *DockerCollector {
|
||||
return &DockerCollector{}
|
||||
}
|
||||
|
||||
func (c *DockerCollector) Name() string { return "docker" }
|
||||
func (c *DockerCollector) DisplayName() string { return "Docker" }
|
||||
func (c *DockerCollector) Description() string {
|
||||
return "Captures Docker version, images, and configuration"
|
||||
}
|
||||
func (c *DockerCollector) Category() collector.Category {
|
||||
return collector.CategoryRuntime
|
||||
}
|
||||
|
||||
func (c *DockerCollector) IsAvailable(ctx context.Context) bool {
|
||||
_, err := exec.LookPath("docker")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *DockerCollector) Scan(ctx context.Context, opts collector.ScanOptions) (*collector.ScanResult, error) {
|
||||
// 实现扫描逻辑
|
||||
// 1. 检测 Docker 版本
|
||||
// 2. 列出本地镜像
|
||||
// 3. 获取 Docker daemon 配置
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *DockerCollector) Capture(ctx context.Context, targetDir string, opts collector.CaptureOptions) error {
|
||||
// 实现捕获逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DockerCollector) Restore(ctx context.Context, sourceDir string, opts collector.RestoreOptions) error {
|
||||
// 实现还原逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DockerCollector) Verify(ctx context.Context) (*collector.VerifyResult, error) {
|
||||
// 实现验证逻辑
|
||||
return nil, nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 2: 注册采集器
|
||||
|
||||
```go
|
||||
// internal/collector/registry.go 中添加注册
|
||||
|
||||
func NewDefaultRegistry() *Registry {
|
||||
r := NewRegistry()
|
||||
|
||||
// Runtime collectors
|
||||
r.Register(runtime.NewGoCollector())
|
||||
r.Register(runtime.NewNodeCollector())
|
||||
r.Register(runtime.NewDockerCollector()) // 新增
|
||||
// ...
|
||||
|
||||
return r
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 3: 编写测试
|
||||
|
||||
```go
|
||||
// internal/collector/runtime/docker_test.go
|
||||
|
||||
func TestDockerCollector_Scan(t *testing.T) {
|
||||
c := NewDockerCollector()
|
||||
|
||||
if !c.IsAvailable(context.Background()) {
|
||||
t.Skip("Docker not available")
|
||||
}
|
||||
|
||||
result, err := c.Scan(context.Background(), collector.ScanOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, result.Items)
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 4: 更新文档
|
||||
|
||||
在 README.md 和 PRD.md 中添加 Docker 采集器的说明。
|
||||
|
||||
---
|
||||
|
||||
## 6. 调试指南
|
||||
|
||||
### 6.1 日志级别
|
||||
|
||||
```bash
|
||||
# 设置日志级别
|
||||
DEVPACK_LOG_LEVEL=debug devpack scan
|
||||
|
||||
# 或通过命令行参数
|
||||
devpack scan --log-level debug
|
||||
```
|
||||
|
||||
| 级别 | 用途 |
|
||||
|------|------|
|
||||
| `trace` | 非常详细的调试信息 |
|
||||
| `debug` | 调试信息 |
|
||||
| `info` | 正常操作信息(默认) |
|
||||
| `warn` | 警告 |
|
||||
| `error` | 错误 |
|
||||
| `fatal` | 致命错误 |
|
||||
|
||||
### 6.2 VS Code 调试配置
|
||||
|
||||
```json
|
||||
// .vscode/launch.json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug DevPack Scan",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/cmd/devpack",
|
||||
"args": ["scan", "--verbose"],
|
||||
"env": {
|
||||
"DEVPACK_LOG_LEVEL": "debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug DevPack Capture",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/cmd/devpack",
|
||||
"args": ["capture", "--name", "test-env"],
|
||||
"env": {
|
||||
"DEVPACK_LOG_LEVEL": "debug"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 常见问题排查
|
||||
|
||||
| 问题 | 排查方向 |
|
||||
|------|---------|
|
||||
| 采集器找不到工具 | 检查 PATH 环境变量,确认工具已安装 |
|
||||
| Pack 文件损坏 | 使用 `devpack verify <pack>` 校验 |
|
||||
| 还原权限不足 | 以管理员身份运行 |
|
||||
| 跨版本不兼容 | 检查 Manifest 中的 format_ver |
|
||||
|
||||
---
|
||||
|
||||
## 7. 发布流程
|
||||
|
||||
### 7.1 版本号规范
|
||||
|
||||
遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/):
|
||||
|
||||
```
|
||||
MAJOR.MINOR.PATCH[-PRERELEASE]
|
||||
|
||||
- MAJOR: 不兼容的 API 变更
|
||||
- MINOR: 向下兼容的功能新增
|
||||
- PATCH: 向下兼容的 Bug 修复
|
||||
```
|
||||
|
||||
### 7.2 发布步骤
|
||||
|
||||
```bash
|
||||
# 1. 确保所有测试通过
|
||||
make test
|
||||
|
||||
# 2. 更新 CHANGELOG
|
||||
# 编辑 CHANGELOG.md
|
||||
|
||||
# 3. 创建 Release Tag
|
||||
git tag -a v0.1.0 -m "Release v0.1.0"
|
||||
git push origin v0.1.0
|
||||
|
||||
# 4. GoReleaser 自动构建发布(CI 触发)
|
||||
# 或手动:
|
||||
make release
|
||||
```
|
||||
|
||||
### 7.3 GoReleaser 配置
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- main: ./cmd/devpack
|
||||
binary: devpack
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- windows
|
||||
- darwin
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X github.com/user/devpack/pkg/version.Version={{.Version}}
|
||||
- -X github.com/user/devpack/pkg/version.Commit={{.Commit}}
|
||||
- -X github.com/user/devpack/pkg/version.Date={{.Date}}
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^chore:'
|
||||
```
|
||||
Reference in New Issue
Block a user