From 723fee99bf543aa54af4c976cd7ca2b803e02056 Mon Sep 17 00:00:00 2001 From: ciat-777 Date: Sun, 15 Mar 2026 13:30:55 +0800 Subject: [PATCH] feat: add issue templates for bug reports, documentation updates, feature requests, and questions --- .github/ISSUE_TEMPLATE/bug_report.yml | 80 +++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/docs.yml | 40 ++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 49 +++++++ .github/ISSUE_TEMPLATE/question.yml | 39 ++++++ .github/labeler.yml | 45 ++++++ .github/workflows/backfill-pr-labels.yml | 155 +++++++++++++++++++++ .github/workflows/labeler.yml | 71 ++++++++++ 8 files changed, 484 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/docs.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/backfill-pr-labels.yml create mode 100644 .github/workflows/labeler.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..26da3fc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,80 @@ +name: Bug report +description: Report a reproducible problem in Metapi +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug. Please include enough detail for someone else to reproduce it. + + - type: checkboxes + id: checks + attributes: + label: Before submitting + options: + - label: I searched existing issues to avoid creating a duplicate + required: true + - label: I tested on the latest release or current main branch + required: true + + - type: dropdown + id: deployment + attributes: + label: Deployment type + description: Where did you reproduce the problem? + options: + - Web app + - Desktop app + - Server only + - Docker deployment + - Other + validations: + required: true + + - type: textarea + id: summary + attributes: + label: What happened? + description: Describe the bug clearly and concisely. + placeholder: Describe the problem you observed. + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + placeholder: | + 1. Go to ... + 2. Configure ... + 3. Send request ... + 4. Observe ... + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + placeholder: Describe what you expected to happen. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs, screenshots, or error output + description: Paste logs or attach screenshots if available. + render: shell + + - type: textarea + id: environment + attributes: + label: Environment + description: Include OS, browser, app version, database type, or other relevant details. + placeholder: | + OS: + Browser: + Metapi version: + Database: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0de19be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Metapi README + url: https://github.com/cita-777/metapi#readme + about: Start with the README for setup, usage, and deployment details. diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml new file mode 100644 index 0000000..3f6989b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs.yml @@ -0,0 +1,40 @@ +name: Documentation update +description: Report missing, incorrect, or unclear documentation +title: "[Docs]: " +labels: ["docs"] +body: + - type: markdown + attributes: + value: | + Use this template when documentation is missing, incorrect, or hard to follow. + + - type: checkboxes + id: checks + attributes: + label: Before submitting + options: + - label: I searched existing issues to avoid creating a duplicate + required: true + + - type: input + id: location + attributes: + label: Documentation location + description: Link the page or file that needs attention. + placeholder: e.g. README.md or docs/getting-started.md + validations: + required: true + + - type: textarea + id: issue + attributes: + label: What is wrong or missing? + placeholder: Describe the missing, inaccurate, or confusing part. + validations: + required: true + + - type: textarea + id: fix + attributes: + label: Suggested improvement + placeholder: Describe how the documentation could be improved. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..e5eddbf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,49 @@ +name: Feature request +description: Suggest a new capability or improvement +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for the suggestion. Keep this issue focused on one feature request. + + - type: checkboxes + id: checks + attributes: + label: Before submitting + options: + - label: I searched existing issues to avoid creating a duplicate + required: true + - label: I confirmed this feature is not already available in the latest release or main branch + required: true + + - type: textarea + id: problem + attributes: + label: Problem or use case + description: What problem are you trying to solve? + placeholder: Explain the workflow or pain point behind this request. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: Describe the change you want to see. + placeholder: Explain the feature or behavior you want added. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Describe any workaround or alternative you considered. + + - type: textarea + id: context + attributes: + label: Additional context + description: Add screenshots, examples, or links if they help explain the request. diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..d9d998a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,39 @@ +name: Question +description: Ask for clarification or usage guidance +title: "[Question]: " +labels: ["question"] +body: + - type: markdown + attributes: + value: | + Use this template for support or clarification questions that are not bug reports or feature requests. + + - type: checkboxes + id: checks + attributes: + label: Before submitting + options: + - label: I searched existing issues and the README for an answer + required: true + + - type: textarea + id: question + attributes: + label: Question + description: What do you need help understanding? + placeholder: Ask one clear question and include the relevant context. + validations: + required: true + + - type: textarea + id: tried + attributes: + label: What have you tried? + description: Include commands, config, or investigation you already attempted. + + - type: textarea + id: environment + attributes: + label: Relevant environment or config + description: Paste config snippets, request examples, or environment details if needed. + render: shell diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..cb17b5c --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,45 @@ +"area: db": + - changed-files: + - any-glob-to-any-file: + - "src/server/db/**" + - "drizzle/**" + +"area: server": + - all: + - changed-files: + - any-glob-to-any-file: + - "src/server/**" + - all-globs-to-all-files: + - "!src/server/db/**" + +"area: web": + - changed-files: + - any-glob-to-any-file: + - "src/web/**" + +"area: desktop": + - changed-files: + - any-glob-to-any-file: + - "src/desktop/**" + - "electron-builder.yml" + - "tsconfig.desktop.json" + +"area: docs": + - changed-files: + - any-glob-to-any-file: + - "docs/**" + - "README.md" + - "README_EN.md" + +"area: scripts": + - changed-files: + - any-glob-to-any-file: + - "scripts/**" + +"area: ci": + - changed-files: + - any-glob-to-any-file: + - ".github/**" + - "docker/**" + - "render.yaml" + - "zeabur-template.yaml" diff --git a/.github/workflows/backfill-pr-labels.yml b/.github/workflows/backfill-pr-labels.yml new file mode 100644 index 0000000..3bc4e52 --- /dev/null +++ b/.github/workflows/backfill-pr-labels.yml @@ -0,0 +1,155 @@ +name: Backfill PR Labels + +on: + workflow_dispatch: + inputs: + state: + description: Pull request state to scan + required: true + default: all + type: choice + options: + - open + - closed + - all + limit: + description: Maximum number of pull requests to inspect + required: true + default: "100" + type: string + +permissions: + contents: read + pull-requests: write + +jobs: + backfill: + name: Backfill missing PR labels + runs-on: ubuntu-latest + steps: + - name: Backfill PR labels + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const requestedState = core.getInput('state'); + const limit = Number.parseInt(core.getInput('limit'), 10); + const sizeLabels = ['size: XS', 'size: S', 'size: M', 'size: L', 'size: XL', 'size: XXL']; + const areaRules = [ + { label: 'area: db', include: ['src/server/db/', 'drizzle/'] }, + { label: 'area: server', include: ['src/server/'], exclude: ['src/server/db/'] }, + { label: 'area: web', include: ['src/web/'] }, + { label: 'area: desktop', include: ['src/desktop/', 'electron-builder.yml', 'tsconfig.desktop.json'] }, + { label: 'area: docs', include: ['docs/', 'README.md', 'README_EN.md'] }, + { label: 'area: scripts', include: ['scripts/'] }, + { label: 'area: ci', include: ['.github/', 'docker/', 'render.yaml', 'zeabur-template.yaml'] }, + ]; + + if (!Number.isFinite(limit) || limit <= 0) { + core.setFailed('The limit input must be a positive integer.'); + return; + } + + function matchesRule(filename, rule) { + const included = rule.include.some((prefix) => filename === prefix || filename.startsWith(prefix)); + if (!included) return false; + if (!rule.exclude) return true; + return !rule.exclude.some((prefix) => filename === prefix || filename.startsWith(prefix)); + } + + function computeAreaLabels(files) { + return areaRules + .filter((rule) => files.some((file) => matchesRule(file.filename, rule))) + .map((rule) => rule.label); + } + + function computeSizeLabel(totalChanges) { + if (totalChanges < 50) return 'size: XS'; + if (totalChanges < 200) return 'size: S'; + if (totalChanges < 500) return 'size: M'; + if (totalChanges < 1000) return 'size: L'; + if (totalChanges < 2000) return 'size: XL'; + return 'size: XXL'; + } + + const state = requestedState === 'all' ? 'all' : requestedState; + const pulls = await github.paginate(github.rest.pulls.list, { + owner, + repo, + state, + per_page: 100, + }); + + let inspected = 0; + let updated = 0; + let skipped = 0; + + for (const pull of pulls) { + if (inspected >= limit) { + break; + } + + inspected += 1; + + const files = await github.paginate(github.rest.pulls.listFiles, { + owner, + repo, + pull_number: pull.number, + per_page: 100, + }); + + const desiredAreaLabels = computeAreaLabels(files); + const desiredSizeLabel = computeSizeLabel(pull.additions + pull.deletions); + const currentLabels = pull.labels.map((label) => label.name); + const missingAreaLabels = desiredAreaLabels.filter((label) => !currentLabels.includes(label)); + const currentSizeLabels = currentLabels.filter((label) => sizeLabels.includes(label)); + const sizeMatches = currentSizeLabels.length === 1 && currentSizeLabels[0] === desiredSizeLabel; + + if (missingAreaLabels.length === 0 && sizeMatches) { + skipped += 1; + continue; + } + + for (const label of currentSizeLabels) { + if (label === desiredSizeLabel) { + continue; + } + + try { + await github.rest.issues.removeLabel({ + owner, + repo, + issue_number: pull.number, + name: label, + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } + + const labelsToAdd = [...missingAreaLabels]; + if (!sizeMatches) { + labelsToAdd.push(desiredSizeLabel); + } + + if (labelsToAdd.length > 0) { + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: pull.number, + labels: labelsToAdd, + }); + } + + updated += 1; + } + + core.summary + .addHeading('Backfill summary') + .addRaw(`Inspected PRs: ${inspected}\n`) + .addRaw(`Updated PRs: ${updated}\n`) + .addRaw(`Skipped PRs: ${skipped}\n`) + .write(); diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..020f8b8 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,71 @@ +name: Label Pull Requests + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + pull-requests: write + +jobs: + apply-labels: + name: Apply area and size labels + runs-on: ubuntu-latest + steps: + - name: Apply area labels + uses: actions/labeler@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + sync-labels: false + + - name: Apply size label + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const pull_number = context.payload.pull_request.number; + const sizeLabels = ['size: XS', 'size: S', 'size: M', 'size: L', 'size: XL', 'size: XXL']; + + const { data: pull } = await github.rest.pulls.get({ + owner, + repo, + pull_number, + }); + + const totalChanges = pull.additions + pull.deletions; + let nextSize = 'size: XXL'; + if (totalChanges < 50) nextSize = 'size: XS'; + else if (totalChanges < 200) nextSize = 'size: S'; + else if (totalChanges < 500) nextSize = 'size: M'; + else if (totalChanges < 1000) nextSize = 'size: L'; + else if (totalChanges < 2000) nextSize = 'size: XL'; + + const currentLabels = pull.labels.map((label) => label.name); + const existingSizeLabels = currentLabels.filter((label) => sizeLabels.includes(label)); + const labelsToRemove = existingSizeLabels.filter((label) => label !== nextSize); + + for (const label of labelsToRemove) { + try { + await github.rest.issues.removeLabel({ + owner, + repo, + issue_number: pull_number, + name: label, + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } + + if (!currentLabels.includes(nextSize)) { + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: pull_number, + labels: [nextSize], + }); + }