diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1281692 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,42 @@ +# Git +.git +.gitignore +.gitattributes + +# Documentation +*.md +!README.md +DEPLOYMENT.md +CONFIGURATION.md + +# CI/CD +.github + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# Build artifacts +dist/ +bin/ +dist/ +feishu-github-tracker +webhook +*.exe + +# Logs +logs/ +*.log + +# Test files +*_test.go + +# OS +.DS_Store +Thumbs.db + +# Local config overrides +*.local.yaml diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml new file mode 100644 index 0000000..3d129a7 --- /dev/null +++ b/.github/actions/setup-go/action.yml @@ -0,0 +1,62 @@ +name: "Setup Go with Cache" +description: "Set up Go environment with module cache optimization" +inputs: + go-version: + description: "Go version to use" + required: false + default: "1.21" + +runs: + using: "composite" + steps: + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ inputs.go-version }} + + - name: Get GOMODCACHE + id: gomodcache + shell: bash + run: echo "path=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT + + - name: Prepare temp cache directory + id: cacheprep + shell: bash + run: | + echo "cache_tmp=${RUNNER_TEMP}/gomodcache_cache" >> $GITHUB_OUTPUT + mkdir -p "${RUNNER_TEMP}/gomodcache_cache" + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ${{ steps.cacheprep.outputs.cache_tmp }} + ~/.cache/go-build + key: ${{ runner.os }}-go-v3-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-v3- + + - name: Populate GOMODCACHE + shell: bash + run: | + set -euo pipefail + GOMODCACHE=${{ steps.gomodcache.outputs.path }} + CACHE_TMP=${{ steps.cacheprep.outputs.cache_tmp }} + echo "GOMODCACHE=${GOMODCACHE}" + echo "CACHE_TMP=${CACHE_TMP}" + if [ -d "${GOMODCACHE}" ] && [ "$(ls -A "${GOMODCACHE}" 2>/dev/null)" ]; then + echo "GOMODCACHE not empty — skipping populate" + else + echo "GOMODCACHE empty — populating from cache tmp" + mkdir -p "${GOMODCACHE}" + if command -v rsync >/dev/null 2>&1; then + rsync -a "${CACHE_TMP}/" "${GOMODCACHE}/" || true + else + cp -a "${CACHE_TMP}/." "${GOMODCACHE}/" || true + fi + echo "Populate complete" + fi + + - name: Download dependencies + shell: bash + run: go mod download diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5a9275b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +name: CI/CD + +on: + push: + branches: [main, develop] + paths: + - ".github/workflows/ci.yml" + - "Dockerfile" + - ".dockerignore" + - "**/*.go" + - "go.mod" + - "go.sum" + pull_request: + branches: [main, develop] + workflow_dispatch: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go with cache + uses: ./.github/actions/setup-go + with: + go-version: "1.21" + + - name: Run tests + run: go test -v ./... + + - name: Run go vet + run: go vet ./... + + build: + name: Build + runs-on: ubuntu-latest + needs: test + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go with cache + uses: ./.github/actions/setup-go + with: + go-version: "1.21" + + - name: Build + run: go build -v -o bin/feishu-github-tracker ./cmd/feishu-github-tracker + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: feishu-github-tracker-binary + path: bin/feishu-github-tracker + retention-days: 7 + + docker: + name: Build and Push Docker Image + runs-on: ubuntu-latest + needs: test + if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')) + permissions: + contents: read + packages: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set image tag + id: tag + run: | + BRANCH=${GITHUB_REF#refs/heads/} + if [ "${BRANCH}" = "main" ]; then + echo "tag=latest" >> $GITHUB_OUTPUT + elif [ "${BRANCH}" = "develop" ]; then + echo "tag=develop" >> $GITHUB_OUTPUT + fi + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + provenance: false # Disable provenance to avoid unknown/unknown + sbom: false # Disable sbom to avoid unknown/unknown + context: . + push: true + tags: ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.tag }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..92f21ee --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,221 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + version: + description: "Version to release (e.g., v1.0.0)" + required: true + type: string + +jobs: + # 如果是 workflow_dispatch,先创建并推送 tag + create-tag: + name: Create and Push Tag + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + permissions: + contents: write + outputs: + tag: ${{ steps.create_tag.outputs.tag }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Create and push tag + id: create_tag + run: | + TAG="${{ github.event.inputs.version }}" + echo "Creating tag: ${TAG}" + git tag -a "${TAG}" -m "Release ${TAG}" + git push origin "${TAG}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + + test: + name: Test + runs-on: ubuntu-latest + needs: [create-tag] + if: always() && !cancelled() && !failure() + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ needs.create-tag.outputs.tag || github.ref }} + + - name: Setup Go with cache + uses: ./.github/actions/setup-go + with: + go-version: "1.21" + + - name: Run tests + run: go test -v ./... + + - name: Run go vet + run: go vet ./... + + build: + name: Build Binary + runs-on: ubuntu-latest + needs: [create-tag, test] + if: always() && !cancelled() && needs.test.result == 'success' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ needs.create-tag.outputs.tag || github.ref }} + + - name: Setup Go with cache + uses: ./.github/actions/setup-go + with: + go-version: "1.21" + + - name: Get version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=${VERSION}" >> $GITHUB_OUTPUT + + - name: Build + run: go build -v -ldflags "-X main.Version=${{ steps.version.outputs.version }}" -o bin/feishu-github-tracker ./cmd/feishu-github-tracker + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: feishu-github-tracker-binary-${{ steps.version.outputs.version }} + path: bin/feishu-github-tracker + retention-days: 30 + + docker: + name: Build and Push Docker Image + runs-on: ubuntu-latest + needs: [create-tag, test] + if: always() && !cancelled() && needs.test.result == 'success' + permissions: + contents: read + packages: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ needs.create-tag.outputs.tag || github.ref }} + + - name: Get version and tags + id: meta + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + + # 移除 'v' 前缀(如果有)用于 Docker tag + DOCKER_VERSION="${VERSION#v}" + + # 构建 Docker 标签 + TAGS="ghcr.io/${{ github.repository }}:${DOCKER_VERSION}" + TAGS="${TAGS},ghcr.io/${{ github.repository }}:latest" + + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "docker_version=${DOCKER_VERSION}" >> $GITHUB_OUTPUT + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + provenance: false + sbom: false + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + VERSION=${{ steps.meta.outputs.version }} + + create-release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [create-tag, build, docker] + if: always() && !cancelled() && needs.build.result == 'success' && needs.docker.result == 'success' + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ needs.create-tag.outputs.tag || github.ref }} + fetch-depth: 0 + + - name: Get version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=${VERSION}" >> $GITHUB_OUTPUT + + - name: Download binary artifact + uses: actions/download-artifact@v4 + with: + name: feishu-github-tracker-binary-${{ steps.version.outputs.version }} + path: ./release + + - name: Generate release notes + id: notes + run: | + VERSION="${{ steps.version.outputs.version }}" + echo "## Release ${VERSION}" > release_notes.md + echo "" >> release_notes.md + echo "### Docker Image" >> release_notes.md + echo '```bash' >> release_notes.md + echo "docker pull ghcr.io/${{ github.repository }}:${VERSION#v}" >> release_notes.md + echo "docker pull ghcr.io/${{ github.repository }}:latest" >> release_notes.md + echo '```' >> release_notes.md + echo "" >> release_notes.md + echo "### Changes" >> release_notes.md + + # 获取上一个 tag + PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + if [ -n "${PREV_TAG}" ]; then + echo "" >> release_notes.md + git log ${PREV_TAG}..HEAD --pretty=format:"- %s (%h)" >> release_notes.md + fi + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.version.outputs.version }} + name: Release ${{ steps.version.outputs.version }} + body_path: release_notes.md + draft: false + prerelease: false + files: | + release/feishu-github-tracker + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sync-develop.yml b/.github/workflows/sync-develop.yml new file mode 100644 index 0000000..4070bc8 --- /dev/null +++ b/.github/workflows/sync-develop.yml @@ -0,0 +1,131 @@ +name: Sync Develop Branch + +on: + push: + branches: + - main + workflow_dispatch: # 允许手动触发 + +jobs: + sync-develop: + name: Sync develop branch with main + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # 获取完整的 git 历史,以便进行分支操作 + fetch-depth: 0 + # 使用 GitHub Token 进行身份验证 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Fetch all branches + run: | + git fetch origin main + git fetch origin develop + + - name: Check if develop branch exists + id: check-develop + run: | + if git show-ref --verify --quiet refs/remotes/origin/develop; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "✅ develop branch exists" + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "❌ develop branch does not exist" + fi + + - name: Create develop branch if it doesn't exist + if: steps.check-develop.outputs.exists == 'false' + run: | + echo "🔧 Creating develop branch from main..." + git checkout -b develop + git push origin develop + echo "✅ develop branch created and pushed" + + - name: Sync develop with main + if: steps.check-develop.outputs.exists == 'true' + run: | + echo "🔄 Syncing develop branch with main..." + + # 切换到 develop 分支 + git checkout develop + + # 获取当前分支的最新提交 hash + DEVELOP_BEFORE=$(git rev-parse HEAD) + echo "📋 develop branch before sync: $DEVELOP_BEFORE" + + # 获取 main 分支的最新提交 hash + MAIN_COMMIT=$(git rev-parse origin/main) + echo "📋 main branch commit: $MAIN_COMMIT" + + # 检查是否需要同步 + if [ "$DEVELOP_BEFORE" = "$MAIN_COMMIT" ]; then + echo "✅ develop branch is already up to date with main" + exit 0 + fi + + # 尝试快进合并 main 到 develop + echo "🚀 Attempting fast-forward merge..." + if git merge --ff-only origin/main; then + echo "✅ Fast-forward merge successful" + + # 推送更新到远程 develop 分支 + git push origin develop + + DEVELOP_AFTER=$(git rev-parse HEAD) + echo "📋 develop branch after sync: $DEVELOP_AFTER" + echo "🎉 develop branch successfully synced with main" + + else + echo "⚠️ Fast-forward merge failed, there might be conflicts or diverged commits" + echo "📋 This usually happens when develop has commits that are not in main" + echo "🔍 Checking for diverged commits..." + + # 检查 develop 是否有 main 没有的提交 + DIVERGED_COMMITS=$(git rev-list --count origin/main..develop) + echo "📊 Number of commits in develop not in main: $DIVERGED_COMMITS" + + if [ "$DIVERGED_COMMITS" -gt 0 ]; then + echo "⚠️ develop branch has $DIVERGED_COMMITS commits that are not in main" + echo "🔧 This requires manual intervention or a different sync strategy" + echo "📋 Diverged commits:" + git log --oneline origin/main..develop + + echo "💡 Consider creating a pull request to merge these changes to main first" + exit 1 + else + echo "❌ Unexpected merge failure" + exit 1 + fi + fi + + - name: Create summary + if: always() + run: | + echo "## 🔄 Develop Branch Sync Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Trigger**: Push to main branch" >> $GITHUB_STEP_SUMMARY + echo "- **Main commit**: \`$(git rev-parse origin/main)\`" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check-develop.outputs.exists }}" = "true" ]; then + echo "- **Develop branch**: Existed and processed" >> $GITHUB_STEP_SUMMARY + else + echo "- **Develop branch**: Created from main" >> $GITHUB_STEP_SUMMARY + fi + + echo "- **Status**: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ job.status }}" = "success" ]; then + echo "✅ **Result**: develop branch is now in sync with main" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Result**: Sync failed - manual intervention may be required" >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6f72f89..151e78d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,231 @@ go.work.sum # env file .env + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + +bin/ +logs/* +!logs/.gitkeep + +# docker-compose use docker-compose.yml in default, ignore this +docker-compose.yaml + +# OS files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 613d7ad..4297ac6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,120 @@ { "cSpell.words": [ + "abbrev", + "abstra", + "analyzer", + "behavior", + "bref", + "Buildx", + "cacheprep", + "celerybeat", + "codz", "color", + "concl", + "cython", + "delaycompress", "demilestoned", + "dmypy", + "elif", + "endscript", + "envrc", + "extldflags", "feishu", "forkee", + "ghsa", + "GOARCH", + "gobwas", + "golangci", "gollum", + "GOMODCACHE", + "GOOS", + "gopkg", + "healthcheck", + "htmlcov", + "ipynb", + "ipython", "labeled", + "ldflags", + "lname", + "lurl", + "marimo", + "Math", "milestoned", + "mkdocs", + "mnesia", + "myorg", + "mypy", + "myrepo", + "nosetests", + "notifempty", + "okcm", + "okfull", + "okid", + "okph", + "okpt", + "okptag", + "okpv", + "okrepo", + "okrp", + "okuk", + "okurl", + "oneline", + "passthrough", + "pipefail", + "pipenv", + "Pipfile", + "pixi", + "pname", + "postrotate", "prereleased", + "ptag", + "ptype", + "pver", + "pybuilder", + "pycache", + "pyenv", + "pyflow", + "pypa", + "pypackages", + "pypirc", + "pytest", + "pytype", + "rabbitmq", "rerequested", - "unlabeled" - ] + "resp", + "ropeproject", + "rtype", + "rurl", + "scrapy", + "sdist", + "sharedscripts", + "softprops", + "sponsorable", + "Spyder", + "spyderproject", + "spyproject", + "streamlit", + "surl", + "timezone", + "tmap", + "tmpl", + "tname", + "tokk", + "tval", + "tzdata", + "unlabeled", + "venv", + "vname", + "webassets", + "yourdomain", + "zoneinfo" + ], + "markdownlint.config": { + "first-line-h1": false, + "no-duplicate-heading": false + }, + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.patterns": { + "handler.go": "handler_*.go" + } } \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7d0cb61 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,152 @@ +# Contributing to Feishu GitHub Tracker + +感谢您对本项目的关注!我们欢迎各种形式的贡献。 + +## 如何贡献 + +### 报告 Bug + +如果您发现了 bug,请创建一个 Issue 并包含: + +1. 清晰的标题和描述 +2. 复现步骤 +3. 预期行为和实际行为 +4. 环境信息(Go 版本、操作系统等) +5. 相关的日志输出 + +### 提出新功能 + +如果您有新功能的想法: + +1. 先创建一个 Issue 讨论该功能 +2. 说明功能的用途和价值 +3. 如果可能,提供实现思路 + +### 提交代码 + +1. **Fork 项目** + +2. **创建特性分支** + + ```bash + git checkout -b feature/amazing-feature + ``` + +3. **进行修改** + + - 遵循现有的代码风格 + - 添加必要的测试 + - 更新相关文档 + +4. **提交更改** + + ```bash + git add . + git commit -m "feat: add an amazing feature" + ``` + +5. **推送到 Fork** + + ```bash + git push origin feature/amazing-feature + ``` + +6. **创建 Pull Request** + +## 开发指南 + +### 环境准备 + +```bash +# 克隆项目 +git clone https://github.com/hnrobert/feishu-github-tracker.git +cd feishu-github-tracker + +# 安装依赖 +go mod download + +# 运行测试 +go test ./... + +# 构建 +make build +``` + +### 代码规范 + +- 使用 `go fmt` 格式化代码 +- 使用 `go vet` 检查代码 +- 所有导出的函数和类型都需要注释 +- 遵循 Go 标准项目布局 + +### 测试 + +- 为新功能添加单元测试 +- 确保所有测试通过:`go test ./...` +- 测试覆盖率应保持或提高 + +### 提交信息规范 + +使用清晰的提交信息: + +```text +: + + + +