diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b441982..be20316 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,22 +5,35 @@ updates: schedule: interval: "weekly" groups: - production-dependencies: + runtime-dependencies: + dependency-type: "production" + toolchain: + dependency-type: "development" patterns: - - "*" - exclude-patterns: + - "@eslint/js" - "@types/*" - - "typescript" - - "vitest" - - "eslint*" + - "@vitest/coverage-v8" + - "eslint" + - "eslint-config-prettier" - "prettier" - dev-dependencies: - patterns: - - "@types/*" + - "tsx" - "typescript" + - "typescript-eslint" - "vitest" - - "eslint*" - - "prettier" + ignore: + # Major bumps for coupled toolchain packages need manual coordination + - dependency-name: "typescript" + update-types: ["version-update:semver-major"] + - dependency-name: "typescript-eslint" + update-types: ["version-update:semver-major"] + - dependency-name: "eslint" + update-types: ["version-update:semver-major"] + - dependency-name: "@eslint/*" + update-types: ["version-update:semver-major"] + - dependency-name: "vitest" + update-types: ["version-update:semver-major"] + - dependency-name: "@vitest/*" + update-types: ["version-update:semver-major"] commit-message: prefix: "chore(deps)" open-pull-requests-limit: 10 diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..8d205b8 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,39 @@ +name: Dependabot auto-merge + +on: + pull_request_target: + types: + - opened + - reopened + - synchronize + - ready_for_review + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Approve and enable auto-merge + if: | + steps.meta.outputs.update-type != 'version-update:semver-major' || + steps.meta.outputs.package-ecosystem == 'github_actions' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + CURRENT_LOGIN="$(gh api user --jq .login)" + ALREADY_APPROVED="$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews" --jq '[.[] | select(.user.login == "'"$CURRENT_LOGIN"'" and .state == "APPROVED")] | length')" + if [ "$ALREADY_APPROVED" -eq 0 ]; then + gh pr review --approve "$PR_URL" + fi + gh pr merge --auto --squash "$PR_URL" diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..93e1c34 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,35 @@ +name: PR Title + +on: + pull_request_target: + types: + - opened + - edited + - reopened + - synchronize + +permissions: + pull-requests: read + +jobs: + conventional-pr-title: + if: ${{ github.event.pull_request.base.ref == 'main' }} + runs-on: ubuntu-latest + steps: + - name: Validate conventional PR title + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + pattern='^(feat|fix|docs|chore|refactor|test|perf|build|ci|revert)(\([[:alnum:]_.-]+\))?!?: .+' + + if [[ "$PR_TITLE" =~ $pattern ]]; then + echo "PR title is valid: $PR_TITLE" + exit 0 + fi + + echo "PR title must use Conventional Commits because squash merges become release-please input." + echo "Allowed prefixes: feat, fix, docs, chore, refactor, test, perf, build, ci, revert" + echo "Examples:" + echo " fix: harden push approval auth flow" + echo " feat(auth): add TOTP support" + exit 1 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 6b52567..8046b9c 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -15,8 +15,13 @@ # chore/docs: no version bump # # npm Publishing: -# Uses Trusted Publishing (OIDC) - no secrets needed! +# Uses Trusted Publishing (OIDC) - no NPM_TOKEN needed! +# Requires Node 24+ for npm 11.5.1+ OIDC support. # Configure at: https://www.npmjs.com/package/@verygoodplugins/mcp-{name}/access +# +# RELEASE_PLEASE_TOKEN: +# Uses org-level PAT so the Release PR triggers CI workflows. +# PRs created by GITHUB_TOKEN don't trigger other workflows (GitHub security). name: Release Please @@ -39,12 +44,15 @@ jobs: - uses: googleapis/release-please-action@v4 id: release with: - release-type: node + manifest-file: ".release-please-manifest.json" + config-file: "release-please-config.json" + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} npm-publish: needs: release-please if: ${{ needs.release-please.outputs.release_created }} runs-on: ubuntu-latest + environment: npm permissions: contents: read id-token: write @@ -53,28 +61,13 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" registry-url: "https://registry.npmjs.org" - - run: npm ci + # Use npm install for cross-version lockfile compatibility + - run: npm install - run: npm run build - run: npm test # Trusted Publishing: No NPM_TOKEN needed! - - run: npm publish --access public --provenance - - announce: - needs: [release-please, npm-publish] - if: ${{ needs.release-please.outputs.release_created }} - runs-on: ubuntu-latest - steps: - - name: Post to Slack - if: ${{ vars.SLACK_WEBHOOK_URL }} - uses: slackapi/slack-github-action@v1 - with: - webhook-url: ${{ vars.SLACK_WEBHOOK_URL }} - webhook-type: incoming-webhook - payload: | - { - "text": "🚀 ${{ github.repository }} ${{ needs.release-please.outputs.tag_name }} released!\n\nhttps://github.com/${{ github.repository }}/releases/tag/${{ needs.release-please.outputs.tag_name }}" - } + - run: npm publish --provenance --access public diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index fb8a7e5..6b12e14 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -21,15 +21,15 @@ jobs: - uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: typescript - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 audit: name: Dependency Audit diff --git a/.prettierrc b/.prettierrc index 7986f0b..1f4c4bb 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,7 @@ { "semi": true, "singleQuote": true, - "trailingComma": "all", + "tabWidth": 2, + "trailingComma": "es5", "printWidth": 100 } - diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..b1897b8 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,26 @@ +// ESLint flat config for VGP MCP servers +// Requires: eslint ^9.0.0, typescript-eslint ^8.0.0 +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['src/**/*.ts'], + rules: { + // MCP stdio servers must not write to stdout outside the protocol. + 'no-console': ['error', { allow: ['error', 'warn'] }], + // Allow unused vars prefixed with underscore + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + // Allow explicit any in some cases (MCP tools often need flexibility) + '@typescript-eslint/no-explicit-any': 'warn', + }, + }, + { + ignores: ['dist/', 'node_modules/', '**/*.js', '**/*.mjs', '**/*.cjs'], + } +); diff --git a/package.json b/package.json index 6dadc36..886e355 100644 --- a/package.json +++ b/package.json @@ -56,17 +56,17 @@ "mysql2": "^3.6.5" }, "devDependencies": { - "@eslint/js": "^9.39.2", - "@types/node": "^18.0.0", + "@eslint/js": "^9.18.0", + "@types/node": "^22.10.5", "@typescript-eslint/eslint-plugin": "^8.50.1", "@typescript-eslint/parser": "^8.50.1", - "eslint": "^9.39.0", - "eslint-config-prettier": "^10.1.0", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", "eslint-plugin-unused-imports": "^4.3.0", "globals": "^16.0.0", - "prettier": "^3.7.4", - "tsx": "^4.21.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.50.1" + "prettier": "^3.4.2", + "tsx": "^4.19.2", + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0" } } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..82bfd07 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: ['node_modules/', 'dist/', 'tests/', '*.config.*'], + }, + }, +});