diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..8adfb96 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,16 @@ +# Default owners for all files +* @boloagegit + +# Backend (Java source) +/src/main/java/ @boloagegit + +# Configuration +/config/ @boloagegit +/src/main/resources/ @boloagegit + +# CI/CD +/.github/ @boloagegit + +# Docker +/Dockerfile @boloagegit +/docker-compose.yml @boloagegit diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml new file mode 100644 index 0000000..e2cfb39 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -0,0 +1,124 @@ +name: Bug Report +description: File a bug report. +title: "[Bug]: " +labels: + - Bug +body: + - type: markdown + attributes: + value: | + ## Bug Report + + Thanks for taking the time to fill out this bug report! + + Please fill out the following sections to help us understand the issue you are facing. + + - type: dropdown + id: installation-method + attributes: + label: Installation Method + description: | + How are you running Echo Mock Server? + options: + - Docker + - Local (java -jar) + - Gradle (./gradlew bootRun) + validations: + required: true + + - type: textarea + id: problem + validations: + required: true + attributes: + label: The Problem + description: | + Describe the issue you are experiencing. Tell us what you were trying to do and what happened. + placeholder: Provide a detailed description of the issue. + + - type: markdown + attributes: + value: | + ## Environment + + - type: input + id: version + validations: + required: true + attributes: + label: Echo Version + placeholder: e.g., 2025.05.01 + description: What version of Echo Mock Server has the issue? + + - type: input + id: java-version + attributes: + label: Java Version + placeholder: e.g., 17.0.11 + description: What Java version are you using? + + - type: input + id: os + attributes: + label: Operating System + placeholder: e.g., macOS 15, Ubuntu 24.04, Windows 11 + + - type: markdown + attributes: + value: | + ## Steps to Reproduce + + - type: textarea + id: steps + validations: + required: true + attributes: + label: Steps to Reproduce + description: | + Provide the steps to reproduce the issue. + placeholder: | + 1. Start the server with ... + 2. Send a request to ... + 3. Observe ... + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What did you expect to happen? + + - type: textarea + id: actual + attributes: + label: Actual Behavior + description: What actually happened? + + - type: markdown + attributes: + value: | + ## Logs + + - type: textarea + id: logs + attributes: + label: Relevant Log Output + description: | + Provide any log output that might help diagnose the issue. + render: txt + + - type: textarea + id: additional-info + attributes: + label: Additional Information + description: | + Any additional context, screenshots, or configuration that might help. + + - type: checkboxes + id: terms + attributes: + label: No Duplicate Issue + description: | + Please confirm that you have searched for similar issues. + options: + - label: I have verified that there are no existing issues related to my problem. + required: true diff --git a/.github/ISSUE_TEMPLATE/2-feature.yml b/.github/ISSUE_TEMPLATE/2-feature.yml new file mode 100644 index 0000000..d8f9824 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature.yml @@ -0,0 +1,64 @@ +name: Feature Request +description: Submit a new feature request. +title: "[Feature Request]: " +labels: + - enhancement +body: + - type: markdown + attributes: + value: | + ## Feature Request + + Thank you for taking the time to suggest a new feature! + + Please fill out the following sections to help us understand your idea. + + - type: textarea + id: feature-description + validations: + required: true + attributes: + label: Feature Description + description: | + Describe the feature you would like to see. What should it do and what problem does it solve? + placeholder: Provide a detailed description of the desired feature. + + - type: textarea + id: use-case + attributes: + label: Use Case + description: | + Why is this feature needed? Describe the scenario where this would be useful. + placeholder: Describe why this feature is important. + + - type: textarea + id: implementation + attributes: + label: Suggested Implementation + description: | + If you have ideas about how this could be implemented, describe them here. This is optional. + placeholder: Describe how this feature might be implemented. + + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: | + Any alternative solutions or workarounds you've considered. + + - type: textarea + id: additional-info + attributes: + label: Additional Information + description: | + Any additional context, mockups, or references that support your request. + + - type: checkboxes + id: search-confirmation + attributes: + label: No Duplicate Feature Request + description: | + Please confirm that you have searched for similar feature requests. + options: + - label: I have verified that there are no existing feature requests similar to mine. + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 461d00f..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Bug Report -about: Report a bug to help us improve -title: '[Bug] ' -labels: bug -assignees: '' ---- - -## Description - -A clear description of the bug. - -## Steps to Reproduce - -1. -2. -3. - -## Expected Behavior - -What you expected to happen. - -## Actual Behavior - -What actually happened. - -## Environment - -- Echo version: -- Java version: -- OS: -- Browser (if UI related): - -## Logs / Screenshots - -Attach relevant logs or screenshots if possible. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ea39251 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: πŸ“– Documentation + url: https://github.com/boloagegit/echo-mock-server/blob/main/README.md + about: Read the documentation before opening an issue diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 80a1ef5..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Feature Request -about: Suggest a new feature or improvement -title: '[Feature] ' -labels: enhancement -assignees: '' ---- - -## Description - -A clear description of the feature you'd like. - -## Use Case - -Why is this feature needed? What problem does it solve? - -## Proposed Solution - -How you think it could be implemented (optional). - -## Alternatives Considered - -Any alternative solutions or workarounds you've considered. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e5f94fa..5c36908 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,14 @@ -## What does this PR do? +# Description of Changes -Brief description of the changes. + -Closes # +--- ## Type of Change @@ -12,10 +16,27 @@ Closes # - [ ] New feature - [ ] Refactoring - [ ] Documentation -- [ ] Other: +- [ ] CI/CD or build +- [ ] Other: ## Checklist +### General + +- [ ] I have read the [Contribution Guidelines](https://github.com/boloagegit/echo-mock-server/blob/main/CONTRIBUTING.md) +- [ ] I have performed a self-review of my own code +- [ ] My changes generate no new warnings + +### Testing + - [ ] Tests pass (`./gradlew test`) - [ ] New features include unit tests -- [ ] Code follows project style guidelines +- [ ] I have tested my changes locally + +### Documentation (if applicable) + +- [ ] I have updated relevant documentation (README, CONTRIBUTING, etc.) + +### UI Changes (if applicable) + +- [ ] Screenshots or videos demonstrating the changes are attached diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d44d9e8..d161a33 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,9 +5,18 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 5 + rebase-strategy: "auto" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 5 + rebase-strategy: "auto" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 3 + rebase-strategy: "auto" diff --git a/.github/labeler-config.yml b/.github/labeler-config.yml new file mode 100644 index 0000000..2bd9876 --- /dev/null +++ b/.github/labeler-config.yml @@ -0,0 +1,76 @@ +version: 1 +labels: + # --- Conventional Commit title matching --- + - label: "Bugfix" + title: '^fix(\([^)]*\))?:|^fix:.*' + + - label: "enhancement" + title: '^feat(\([^)]*\))?:|^feat:.*' + + - label: "build" + title: '^build(\([^)]*\))?:|^build:.*' + + - label: "chore" + title: '^chore(\([^)]*\))?:|^chore:.*' + + - label: "ci" + title: '^ci(\([^)]*\))?:|^ci:.*' + + - label: "perf" + title: '^perf(\([^)]*\))?:|^perf:.*' + + - label: "refactor" + title: '^refactor(\([^)]*\))?:|^refactor:.*' + + - label: "revert" + title: '^revert(\([^)]*\))?:|^revert:.*' + + - label: "style" + title: '^style(\([^)]*\))?:|^style:.*' + + - label: "Documentation" + title: '^docs(\([^)]*\))?:|^docs:.*' + + - label: "dependencies" + title: '^deps(\([^)]*\))?:|^deps:.*' + + # --- File path matching --- + - label: "Java" + files: + - 'src/main/java/.*.java' + + - label: "Back End" + files: + - 'src/main/java/com/echo/controller/.*' + - 'src/main/java/com/echo/service/.*' + - 'src/main/java/com/echo/config/.*' + - 'src/main/resources/.*' + + - label: "JMS" + files: + - 'src/main/java/com/echo/jms/.*' + + - label: "Security" + files: + - 'src/main/java/com/echo/config/Security.*' + + - label: "Docker" + files: + - 'Dockerfile' + - 'docker-compose.yml' + + - label: "Documentation" + files: + - '.*.md' + + - label: "ci" + files: + - '.github/.*' + + - label: "Gradle" + files: + - 'gradle/.*' + - 'gradlew' + - 'gradlew.bat' + - 'build.gradle' + - 'settings.gradle' diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..434e890 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,145 @@ +# Labels are automatically synced by the manage-label workflow. +# See: .github/workflows/manage-label.yml + +# --- Type --- +- name: "Bug" + description: "Something isn't working" + color: "EB9CA6" + +- name: "Bugfix" + description: "Pull requests that fix bugs" + color: "FF9E1F" + +- name: "enhancement" + description: "New feature or request" + color: "A0EEEE" + +- name: "question" + description: "Further information is requested" + color: "D97EE5" + +- name: "duplicate" + description: "This issue or pull request already exists" + color: "CDD1D5" + +- name: "invalid" + description: "This doesn't seem right" + color: "E5E566" + +- name: "wontfix" + description: "This will not be worked on" + color: "FFFFFF" + +# --- Area --- +- name: "Back End" + description: "Issues or pull requests related to back-end / Java code" + color: "20CE6C" + +- name: "API" + description: "API-related issues or pull requests" + color: "FFFF00" + +- name: "Docker" + description: "Pull requests that update Docker code" + color: "1FCEFF" + +- name: "Security" + description: "Security-related issues or pull requests" + color: "000000" + +- name: "JMS" + description: "JMS messaging related issues" + color: "8B5CF6" + +- name: "Documentation" + description: "Improvements or additions to documentation" + color: "35ABFF" + +# --- Process --- +- name: "good first issue" + description: "Good for newcomers" + color: "C1B8FF" + +- name: "help wanted" + description: "Extra attention is needed" + color: "00E6C4" + +- name: "more-info-needed" + description: "More information is needed from the reporter" + color: "00E4F8" + +- name: "Stale" + description: "Issues that have become inactive" + color: "000000" + +# --- Priority --- +- name: "Priority: Critical" + description: "Highest priority β€” must fix immediately" + color: "000000" + +- name: "Priority: High" + description: "High priority" + color: "FF0000" + +- name: "Priority: Medium" + description: "Medium priority" + color: "FFFF00" + +- name: "Priority: Low" + description: "Low priority" + color: "00FF00" + +# --- CI/Build --- +- name: "dependencies" + description: "Pull requests that update a dependency file" + color: "5AA8FC" + +- name: "github-actions" + description: "Pull requests that update GitHub Actions code" + color: "999999" + +- name: "Gradle" + description: "Pull requests that update Gradle code" + color: "FF9E1F" + +- name: "Java" + description: "Pull requests that update Java code" + color: "FF9E1F" + +- name: "build" + description: "Changes that affect the build system or external dependencies" + color: "1E90FF" + +- name: "ci" + description: "Changes to CI configuration files and scripts" + color: "4682B4" + +# --- Conventional Commits --- +- name: "chore" + description: "Routine tasks or maintenance" + color: "FFD700" + +- name: "perf" + description: "Changes that improve performance" + color: "FF69B4" + +- name: "refactor" + description: "Code changes that neither fix a bug nor add a feature" + color: "9932CC" + +- name: "revert" + description: "Reverts a previous commit" + color: "DC143C" + +- name: "style" + description: "Changes that do not affect the meaning of the code (formatting, etc.)" + color: "FFA500" + +# --- Release --- +- name: "break-change" + description: "This PR introduces a breaking change" + color: "FF0000" + +- name: "ignore-for-release" + description: "Exclude from release notes" + color: "EDEDED" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..84dd4cc --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,44 @@ +changelog: + exclude: + labels: + - ignore-for-release + + categories: + - title: Breaking Changes + labels: + - break-change + + - title: Bug Fixes + labels: + - Bugfix + - Bug + + - title: Enhancements + labels: + - enhancement + - Back End + - API + - JMS + + - title: Minor Enhancements + labels: + - chore + - style + - refactor + - perf + + - title: Docker Updates + labels: + - Docker + + - title: Documentation + labels: + - Documentation + + - title: Dependencies + labels: + - dependencies + + - title: Other Changes + labels: + - "*" diff --git a/.github/workflows/auto-labeler.yml b/.github/workflows/auto-labeler.yml new file mode 100644 index 0000000..906c6ea --- /dev/null +++ b/.github/workflows/auto-labeler.yml @@ -0,0 +1,27 @@ +name: Auto Label PRs + +on: + pull_request_target: + types: [opened, synchronize] + branches: + - main + - master + +permissions: + contents: read + +jobs: + labeler: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - uses: srvaroa/labeler@v1 + with: + config_path: .github/labeler-config.yml + use_local_config: false + fail_on_error: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c623a8..a0a8ae0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,21 +2,35 @@ name: CI on: push: - branches: [ main, master ] + branches: [main, master] pull_request: - branches: [ main, master ] + branches: [main, master] + +# Cancel in-progress jobs when a new commit is pushed to the same branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read jobs: build: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + jdk-version: [17, 21] steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Set up JDK 17 + - name: Set up JDK ${{ matrix.jdk-version }} uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '${{ matrix.jdk-version }}' distribution: 'temurin' - name: Setup Gradle @@ -29,12 +43,54 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-report + name: test-report-jdk${{ matrix.jdk-version }} path: build/reports/tests/test/ + retention-days: 14 - name: Upload Coverage Report if: always() uses: actions/upload-artifact@v4 with: - name: coverage-report + name: coverage-report-jdk${{ matrix.jdk-version }} path: build/reports/jacoco/test/html/ + retention-days: 14 + + - name: Upload SpotBugs Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: spotbugs-report-jdk${{ matrix.jdk-version }} + path: build/reports/spotbugs/ + retention-days: 14 + + - name: Upload JAR + if: success() && matrix.jdk-version == 17 + uses: actions/upload-artifact@v4 + with: + name: echo-server-jar + path: build/libs/echo-server-*.jar + retention-days: 14 + + docker-build-test: + if: github.event_name == 'pull_request' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build JAR + run: ./gradlew bootJar + + - name: Build Docker image (test only) + run: docker build -t echo-mock-server:test . diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..1d6ecd4 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,19 @@ +name: Dependency Review + +on: + pull_request: + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Dependency Review + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: high diff --git a/.github/workflows/manage-label.yml b/.github/workflows/manage-label.yml new file mode 100644 index 0000000..ad2d92d --- /dev/null +++ b/.github/workflows/manage-label.yml @@ -0,0 +1,26 @@ +name: Manage labels + +on: + schedule: + - cron: "30 20 * * *" + workflow_dispatch: + +permissions: + contents: read + +jobs: + labeler: + name: Sync labels + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Check out the repository + uses: actions/checkout@v4 + + - name: Run Labeler + uses: crazy-max/ghaction-github-labeler@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + yaml-file: .github/labels.yml + skip-delete: true diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml new file mode 100644 index 0000000..31fd5c5 --- /dev/null +++ b/.github/workflows/push-docker.yml @@ -0,0 +1,75 @@ +name: Push Docker Image to GHCR + +on: + push: + branches: [main, master] + release: + types: [created] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + packages: write + +jobs: + push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build JAR + run: ./gradlew bootJar + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Convert repository owner to lowercase + id: repoowner + run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT + + - name: Get version from Gradle + id: version + run: | + VERSION=$(./gradlew properties -q | grep '^version:' | awk '{print $2}') + if [ -z "$VERSION" ]; then + echo "Failed to extract version from Gradle" + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Generate Docker tags + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/echo-mock-server + tags: | + type=raw,value=${{ steps.version.outputs.version }},enable=${{ github.event_name == 'release' }} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event_name == 'release' }} + type=sha,prefix=,format=short + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2f69814 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Release + +on: + release: + types: [created] + +permissions: + contents: write + +jobs: + build-and-upload: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build JAR + run: ./gradlew bootJar + + - name: Get version from tag + id: version + run: | + TAG="${GITHUB_REF#refs/tags/}" + echo "tag=$TAG" >> $GITHUB_OUTPUT + + - name: Rename JAR + run: | + mkdir -p dist + cp build/libs/echo-server-*.jar dist/echo-server-${{ steps.version.outputs.tag }}.jar + + - name: Upload JAR to Release + uses: softprops/action-gh-release@v2 + with: + files: dist/echo-server-*.jar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..ce96ce9 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,34 @@ +name: Close stale issues + +on: + schedule: + - cron: "30 0 * * *" + workflow_dispatch: + +permissions: + contents: read + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Mark stale issues + uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 30 + days-before-close: 7 + stale-issue-message: > + This issue has been automatically marked as stale because it has had no recent activity. + It will be closed if no further activity occurs. Thank you for your contributions. + close-issue-message: > + This issue has been automatically closed due to inactivity after being marked as stale. + Please reopen if you need further assistance. + stale-issue-label: "Stale" + remove-stale-when-updated: true + only-issue-labels: "more-info-needed" + days-before-pr-stale: -1 + days-before-pr-close: -1 diff --git a/Dockerfile b/Dockerfile index c35baf7..fd957d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:17-jre +FROM eclipse-temurin:25-jre LABEL maintainer="Echo Mock Server" LABEL description="Enterprise Mock Server for HTTP and JMS" diff --git a/README.md b/README.md index 7aeb06f..d1deea5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Echo Mock Server +[![CI](https://github.com/boloagegit/echo-mock-server/actions/workflows/ci.yml/badge.svg)](https://github.com/boloagegit/echo-mock-server/actions/workflows/ci.yml) +[![Docker](https://github.com/boloagegit/echo-mock-server/actions/workflows/push-docker.yml/badge.svg)](https://github.com/boloagegit/echo-mock-server/actions/workflows/push-docker.yml) ![Java 17](https://img.shields.io/badge/Java-17-orange) ![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-green) ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) [δΈ­ζ–‡η‰ˆ README](README_zh-TW.md)