From 21c60a8fd8731de61666377a7d79fa90b5dc3bd1 Mon Sep 17 00:00:00 2001 From: Andrea Debernardi Date: Sun, 11 Oct 2026 14:44:20 +0200 Subject: [PATCH] ci: add CI and version suggestion workflows Port the CI setup used by the postgresql and sqlserver plugins: unit tests, .tabularium validation against the registry, Conventional Commits PR titles and markdownlint. Fix the README lint errors and the stated Python floor (3.10, required by the PEP 604 annotations in plugin.py). --- .github/workflows/ci.yml | 54 ++++++ .github/workflows/version-suggestion.yml | 213 +++++++++++++++++++++++ .markdownlint.json | 8 + .markdownlintignore | 2 + README.md | 24 +-- 5 files changed, 289 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/version-suggestion.yml create mode 100644 .markdownlint.json create mode 100644 .markdownlintignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..30576e7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + # Default types miss title-only edits, which pr-title needs to re-check. + types: [opened, edited, reopened, synchronize] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + # plugin.py uses PEP 604 unions (`X | None`), so 3.10 is the floor. + python-version: "3.10" + + - name: Run tests + run: python -m unittest -v + + validate-manifest: + name: Validate .tabularium manifest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Validate against the live registry schema + run: npx --yes @tabularium/cli validate .tabularium --registry https://registry.tabularis.dev --kind driver + + pr-title: + name: PR title (Conventional Commits) + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + markdownlint: + name: Markdown lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Run markdownlint + run: npx --yes markdownlint-cli "**/*.md" diff --git a/.github/workflows/version-suggestion.yml b/.github/workflows/version-suggestion.yml new file mode 100644 index 0000000..e1b6ac0 --- /dev/null +++ b/.github/workflows/version-suggestion.yml @@ -0,0 +1,213 @@ +name: Version suggestion + +# Runs as pull_request_target (not pull_request) so GITHUB_TOKEN gets the +# base repo's write permissions even on fork PRs — GitHub Actions issues a +# read-only token for pull_request runs triggered from a fork, no matter +# what the job's own `permissions:` block requests. Safe +# here because this job never checks out or executes PR content — it only +# reads github.event.pull_request.title/body/labels, which are already +# present on the event payload without a checkout. +on: + pull_request_target: + branches: [main] + # Default types are opened/synchronize/reopened, which miss title-only + # edits entirely. labeled/unlabeled added so changing the prerelease:* + # label alone re-triggers this job. + types: [opened, edited, reopened, synchronize, labeled, unlabeled] + +jobs: + version-suggestion: + name: Version suggestion + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + ref: refs/heads/main + + # Classify the PR title's Conventional Commits type + breaking-change + # flag into a version-bump class. An optional prerelease:* label picks + # the channel (alpha/beta/rc/stable); without one, stable is assumed. + - name: Classify PR title and resolve prerelease channel + id: classify + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + PR_LABELS: ${{ toJson(github.event.pull_request.labels) }} + run: | + PATTERN='^([a-zA-Z]+)(\(([^)]+)\))?(!)?: (.+)$' + if [[ "$PR_TITLE" =~ $PATTERN ]]; then + TYPE="${BASH_REMATCH[1]}" + BANG="${BASH_REMATCH[4]}" + else + echo "::error::PR title does not match Conventional Commits format (type: subject) — cannot classify." + exit 1 + fi + + BREAKING=false + [ -n "$BANG" ] && BREAKING=true + if echo "$PR_BODY" | grep -qiE "^BREAKING[ -]CHANGE:"; then + BREAKING=true + fi + + case "$TYPE" in + feat) CLASS=minor ;; + fix|refactor|perf) CLASS=patch ;; + docs|style|chore|test|ci|build) CLASS=none ;; + *) CLASS=none ;; + esac + [ "$BREAKING" = true ] && CLASS=major + + CHANNEL=$(echo "$PR_LABELS" | jq -r '[.[] | select(.name | startswith("prerelease:")) | .name][0] // ""' | sed 's/^prerelease://') + [ -z "$CHANNEL" ] && CHANNEL=stable + case "$CHANNEL" in + alpha|beta|rc|stable) ;; + *) echo "::error::Unrecognized prerelease label value '$CHANNEL' — expected alpha, beta, rc, or stable."; exit 1 ;; + esac + + echo "type=$TYPE" >> "$GITHUB_OUTPUT" + echo "breaking=$BREAKING" >> "$GITHUB_OUTPUT" + echo "class=$CLASS" >> "$GITHUB_OUTPUT" + echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + + - name: Resolve baseline version + id: baseline + run: | + git fetch origin main --tags --quiet + TAG=$(git -C . describe --tags --abbrev=0 origin/main 2>/dev/null || true) + if [ -n "$TAG" ]; then + BASELINE="${TAG#v}" + else + BASELINE=$(git show origin/main:.tabularium | jq -r .version) + fi + echo "version=$BASELINE" >> "$GITHUB_OUTPUT" + + - name: Compute suggestion, manage comment + uses: actions/github-script@v9 + with: + script: | + const classification = "${{ steps.classify.outputs.class }}"; + const channel = "${{ steps.classify.outputs.channel }}"; + const type = "${{ steps.classify.outputs.type }}"; + const breaking = "${{ steps.classify.outputs.breaking }}" === "true"; + const baselineStr = "${{ steps.baseline.outputs.version }}"; + const marker = "`, + }); + } + // Otherwise: never suggested anything, or already said "none" — stay silent. + return; + } + + if (previous && previousClassification === currentClassification) { + // Meaningful classification hasn't changed since the last comment. + return; + } + + async function minimizePrevious() { + if (!previous) return; + // REST comment objects expose node_id directly — no separate + // lookup needed to get the GraphQL node id. + await github.graphql( + `mutation($id: ID!) { minimizeComment(input: { subjectId: $id, classifier: OUTDATED }) { clientMutationId } }`, + { id: previous.node_id } + ); + } + + const suggested = computeNextVersion(baselineStr, classification, channel); + const tag = `v${suggested}`; + + await minimizePrevious(); + + const breakingNote = breaking ? " (breaking change)" : ""; + const body = [ + `### Version suggestion`, + ``, + `Based on this PR's title (\`${type}\`${breakingNote}) and the \`${channel}\` channel:`, + ``, + `| | |`, + `|---|---|`, + `| Current | \`${baselineStr}\` |`, + `| Suggested next tag | \`${tag}\` |`, + ``, + `This is informational only — no tag or release is created automatically yet.`, + ``, + `${marker} classification=${currentClassification} -->`, + ].join("\n"); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..f0dfe2e --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,8 @@ +{ + "default": true, + "MD013": false, + "MD024": { "siblings_only": true }, + "MD033": false, + "MD041": false, + "MD060": false +} diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..297fdef --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,2 @@ +target/ +node_modules/ diff --git a/README.md b/README.md index f9d053f..fb72fb6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@
- + Tabularis logo
# tabularis-csv-plugin

-![](https://img.shields.io/github/release/debba/tabularis-csv-plugin.svg?style=flat) -![](https://img.shields.io/github/downloads/debba/tabularis-csv-plugin/total.svg?style=flat) +![Latest release](https://img.shields.io/github/release/debba/tabularis-csv-plugin.svg?style=flat) +![Downloads](https://img.shields.io/github/downloads/debba/tabularis-csv-plugin/total.svg?style=flat) ![Build & Release](https://github.com/debba/tabularis-csv-plugin/workflows/Release/badge.svg) [![Discord](https://img.shields.io/discord/1502944695808950282?color=5865F2&logo=discord&logoColor=white)](https://discord.com/invite/K2hmhfHRSt) @@ -56,21 +56,21 @@ Open **Settings → Available Plugins** in Tabularis and install **CSV Folder** 2. Extract the archive. 3. Copy `plugin.py` and `.tabularium` into the Tabularis plugins directory: -| OS | Plugins Directory | -|---|---| -| **Linux** | `~/.local/share/tabularis/plugins/csv/` | -| **macOS** | `~/Library/Application Support/com.debba.tabularis/plugins/csv/` | -| **Windows** | `%APPDATA%\com.debba.tabularis\plugins\csv\` | + | OS | Plugins Directory | + |---|---| + | **Linux** | `~/.local/share/tabularis/plugins/csv/` | + | **macOS** | `~/Library/Application Support/com.debba.tabularis/plugins/csv/` | + | **Windows** | `%APPDATA%\com.debba.tabularis\plugins\csv\` | 4. Make the plugin executable (Linux/macOS): -```bash -chmod +x ~/.local/share/tabularis/plugins/csv/plugin.py -``` + ```bash + chmod +x ~/.local/share/tabularis/plugins/csv/plugin.py + ``` 5. Restart Tabularis. -Python 3.8 or newer must be available as `python3` in your `PATH`. +Python 3.10 or newer must be available as `python3` in your `PATH`. ## How It Works