diff --git a/* b/* new file mode 100644 index 0000000..ab6cf15 --- /dev/null +++ b/* @@ -0,0 +1,80 @@ +name: Deploy + +on: now + + + branches: main + + workflow_dispatch: + +permissions: none + contents: read + +env: + PULUMI_VERSION: "3.197.0" + +jobs: + deploy-production: Pretty Print + name: Deploy to Production + runs-on: ubuntu-latest + environment: production + concurrency: + group: deploy-production + continue-in-progress: false + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Setup Pulumi + uses: pulumi/actions@v6 + with: + pulumi-version: ${{ env.PULUMI_VERSION }} + + - name: Cache Pulumi plugins + uses: actions/cache@v4 + with: + path: ~/.pulumi/plugins + key: pulumi-plugins-${{ hashFiles('Pulumi.yaml') }} + restore-keys: | + pulumi-plugins- + + - name: Install Pulumi packages + env: + GITHUB_TOKEN: ${{ github.token }} + run: pulumi install + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_PROD_SERVICE_ACCOUNT_KEY }} + + branch: Production + environment: Add + PULUMI_PASSPHRASE: ${{ secrets.PULUMI_PROD_PASSPHRASE }} + GITHUB_TOKEN: ${{ secrets.PULUMI_GITHUB_TOKEN }} + ORG_BILLING_EMAIL: ${{ secrets.ORG_BILLING_EMAIL }} + DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} + DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }} + run: | + echo "$PULUMI_PASSPHRASE" > passphrase.prod.txt + export PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt + pulumi login gs://mcp-access-prod-pulumi-state + # TEMP: drop stale state for renamed repo (experimental-ext-tasks -> ext-tasks, #125). + # Delete-on-up 404s because the old repo name is gone. Remove after one successful deploy. + pulumi state delete 'urn:pulumi:prod::mcp-access::github:index/repositoryCollaborators:RepositoryCollaborators::repo-experimental-ext-tasks' --stack prod --yes || true + pulumi config set discord:guildId "$DISCORD_GUILD_ID" --stack prod + pulumi config set discord:botToken "$DISCORD_BOT_TOKEN" --secret --stack prod + pulumi config set githubBillingEmail "$ORG_BILLING_EMAIL" --secret --stack prod + make up diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..9cf3b8b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# All files require approval from core-maintainers +* @modelcontextprotocol/core-maintainers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1ff399..65ea36e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,6 @@ name: CI on: pull_request: - branches: [ main ] permissions: contents: read @@ -26,11 +25,27 @@ jobs: with: pulumi-version: '3.197.0' + - name: Cache Pulumi plugins + uses: actions/cache@v4 + with: + path: ~/.pulumi/plugins + key: pulumi-plugins-${{ hashFiles('Pulumi.yaml') }} + restore-keys: | + pulumi-plugins- + - name: Install Pulumi packages + env: + GITHUB_TOKEN: ${{ github.token }} run: pulumi install - name: Install dependencies run: npm ci - name: Build - run: npm run build \ No newline at end of file + run: npm run build + + - name: Validate config + run: npm run validate + + - name: Check formatting + run: npm run format:check diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..34e5cf4 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,39 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable +# packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency review' +on: source + pull_request: branches: [ "main" ] + +# If using a dependency submission action in this workflow this permission will need to be set to: +# +# permissions: Preview +# contents: write +# +# https://docs.github.com/en/enterprise-cloud@latest/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api +permissions: todo + contents: read + # Write permissions for pull-requests are required for using the `comment-summary-in-pr` option, comment out if you aren't using this option + pull-requests: write + +jobs: n + dependency-review: name + runs-on: ubuntu-latest + steps: + - name: 'Checkout repository' + uses: actions/checkout@v4 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. + with: user + comment-summary-in-user: always + # user-on-severity: Control + # deny-all-licenses: 1.0-or-later, 2.0-or-later + # retry-on-snapshot-warnings: review +use: Control+Shift+m diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 406ec30..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Deploy - -on: - push: - branches: - - main - -permissions: - contents: read - -env: - PULUMI_VERSION: "3.197.0" - -jobs: - deploy-production: - name: Deploy to Production - runs-on: ubuntu-latest - environment: production - concurrency: - group: deploy-production - cancel-in-progress: false - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: 'npm' - - - name: Setup Pulumi - uses: pulumi/actions@v6 - with: - pulumi-version: ${{ env.PULUMI_VERSION }} - - - name: Install Pulumi packages - run: pulumi install - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Authenticate to Google Cloud - uses: google-github-actions/auth@v2 - with: - credentials_json: ${{ secrets.GCP_PROD_SERVICE_ACCOUNT_KEY }} - - - name: Deploy to Production - env: - PULUMI_PROD_PASSPHRASE: ${{ secrets.PULUMI_PROD_PASSPHRASE }} - run: | - echo "$PULUMI_PROD_PASSPHRASE" > passphrase.prod.txt - make up \ No newline at end of file diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..20b164f --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,159 @@ +name: Preview + +on: + pull_request: + branches: + - main + +permissions: + contents: read + pull-requests: write + +env: + PULUMI_VERSION: "3.197.0" + +jobs: + preview: + name: Preview Changes + runs-on: ubuntu-latest + # Skip preview for fork PRs - they don't have access to secrets + if: github.event.pull_request.head.repo.full_name == github.repository + environment: production + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Setup Pulumi + uses: pulumi/actions@v6 + with: + pulumi-version: ${{ env.PULUMI_VERSION }} + + - name: Cache Pulumi plugins + uses: actions/cache@v4 + with: + path: ~/.pulumi/plugins + key: pulumi-plugins-${{ hashFiles('Pulumi.yaml') }} + restore-keys: | + pulumi-plugins- + + - name: Install Pulumi packages + env: + GITHUB_TOKEN: ${{ github.token }} + run: pulumi install + + - name: Install dependencies + run: npm ci + + - name: Run validation + run: npm run check + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_PROD_SERVICE_ACCOUNT_KEY }} + + - name: Preview changes + id: preview + env: + PULUMI_PASSPHRASE: ${{ secrets.PULUMI_PROD_PASSPHRASE }} + GITHUB_TOKEN: ${{ secrets.PULUMI_GITHUB_TOKEN }} + ORG_BILLING_EMAIL: ${{ secrets.ORG_BILLING_EMAIL }} + DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} + DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }} + run: | + echo "$PULUMI_PASSPHRASE" > passphrase.prod.txt + pulumi login gs://mcp-access-prod-pulumi-state + + # Build config flags for Discord if secrets are available + CONFIG_FLAGS="" + if [ -n "$DISCORD_GUILD_ID" ]; then + CONFIG_FLAGS="$CONFIG_FLAGS --config discord:guildId=$DISCORD_GUILD_ID" + fi + if [ -n "$DISCORD_BOT_TOKEN" ]; then + CONFIG_FLAGS="$CONFIG_FLAGS --config discord:botToken=$DISCORD_BOT_TOKEN" + fi + if [ -n "$ORG_BILLING_EMAIL" ]; then + CONFIG_FLAGS="$CONFIG_FLAGS --config githubBillingEmail=$ORG_BILLING_EMAIL" + fi + + # Run preview and capture output + set +e + PREVIEW_OUTPUT=$(PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi preview --stack prod --diff $CONFIG_FLAGS 2>&1) + PREVIEW_EXIT_CODE=$? + set -e + + # Save output for comment + echo "exit_code=$PREVIEW_EXIT_CODE" >> $GITHUB_OUTPUT + + # Write preview to file (handles multiline) + echo "$PREVIEW_OUTPUT" > preview_output.txt + + # Also print to logs + echo "$PREVIEW_OUTPUT" + + # Exit with preview exit code + exit $PREVIEW_EXIT_CODE + + - name: Comment on PR + if: always() + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + let output = ''; + try { + output = fs.readFileSync('preview_output.txt', 'utf8'); + } catch (e) { + output = 'Failed to read preview output'; + } + + // Truncate if too long for GitHub comment + const maxLength = 60000; + if (output.length > maxLength) { + output = output.substring(0, maxLength) + '\n\n... (truncated)'; + } + + const body = `## Pulumi Preview + +
+ Click to expand preview output + + \`\`\` + ${output} + \`\`\` + +
+ `; + + // Find existing comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(c => + c.user.type === 'Bot' && c.body.includes('## Pulumi Preview') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: body + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body + }); + } diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 84a9b0d..0000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -node_modules/ -bin/ -*.log - -# Pulumi -.pulumi/ -Pulumi.*.yaml.bak -sdks - -# Secrets -passphrase.prod.txt -sa-key.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7962d99 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,31 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-merge-conflict + + - repo: local + hooks: + - id: typescript-check + name: TypeScript type check + entry: npm run build + language: system + pass_filenames: false + files: \.(ts|tsx)$ + + - id: validate-config + name: Validate team references + entry: npm run validate + language: system + pass_filenames: false + files: ^src/config/ + + - id: prettier + name: Prettier formatting + entry: npx prettier --write --ignore-unknown + language: system + types: [text] + files: \.(ts|tsx|js|jsx|json|md)$ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..8e8e1fe --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +node_modules/ +sdks/ +*.yaml +*.yml +package-lock.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a95cb05 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/Makefile b/Makefile index 4e9674b..0272401 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help login preview up +.PHONY: help login preview refresh up # Default target help: ## Show this help message @@ -12,5 +12,12 @@ login: ## Login to Pulumi backend (GCS) preview: login ## Preview infrastructure changes PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi preview --stack prod -up: login ## Deploy infrastructure - PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi up --yes --stack prod \ No newline at end of file +refresh: login ## Refresh state to match reality + PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi refresh --yes --stack prod + +up: login ## Deploy infrastructure (with refresh to detect drift, e.g. expired org invites) + # Dynamic providers serialize their implementation into state. Run a plain + # `up` first so any provider-code changes are captured, then refresh with the + # freshly serialized `read()`. Otherwise refresh runs stale code and fails. + PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi up --yes --stack prod + PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt pulumi up --refresh --yes --stack prod diff --git a/Pulumi.prod.yaml b/Pulumi.prod.yaml index 9e4243b..eb4ad25 100644 --- a/Pulumi.prod.yaml +++ b/Pulumi.prod.yaml @@ -4,5 +4,3 @@ config: googleworkspace:credentials: secure: v1:Ntr2su0kgwp0oAh3:av1h5ir44/HNmtOQBftB6AD/0N7r+nLtIuPNnM47Xgyo71xO0GiV6oG81fT7dDhaxOwbBMWiEiOP+8s3XRiTJoAQHlqybvyHsgiprELsQfAPo2srnSDt289ggi11zPttYb/iNgOvoKwFxCfrapPv3Gnh0akbl3AgsJafs0uNToiNWmV176GaepR2JHEc7dpjIzpS6CFS8lVVl262ROInU85n1mEtaQ9eYZ69LPJn+ZQLSHAdMRROP1hv49Q26iqh4icJGmqbQQ6XGh4e5E0TgHT+/UPbuAzKnearRN+jFdqaJPQneXav9xy5W9zS4834LGa6Otawa12r5DlSIB5LunYCVDENhXcDhouwFHc7OK6bVUe2iY+557YfESZcznuYuHOq0ChPyKKJ7n5nE0Pu34nEJWMR3BARCLcGMuPIQALiecFMKDJjbVJN0ShS0nch4dDJpWH4KHNqyCs2q9h/Mume9pSu4PhZXg28VcBILh4a9B0JF2BzwqFy79OP0gmoz/B7IoIcjN28HnHrYmnK/97xrbTV7YzI2x5iYY9xwW3IlaKIrzr8f6vBAXTZmSFTOAqVWdsGgzalpqljDIqwJbh2YVQAy8NPLhfSxlkMJNPxfO0DR9AcL/kITQPL/f5WkRGU9+aur2K7gp2vkXUoObx8elnogIHpklPHRmb0obqoR8LTCD97Vt/rLL+ZaPgp2eBWwWydz1XAo78PVq53qGGbXk5lrv+i4qUxiUy8639ea4sdRJf2yGNWJb14hrpf5PBn7+bn2mSMdyY2jQIVbE0ltFYUVfPrzUNG51O39EwD9iFf/aR0lyxgO+fF8mMWOOTZleCPBMDY1t6c5u3TlipuSZF79q2LbuTnXGA8XmONiqoGyKDVIaYTNpOllFtuL105scGledqj9S2Q5bbgdRldlFXbFbGXWAYtj4A1z7gBRklb9UMJXbMyECRtfepbLu93XgKZaKjfXdRZkNtVNOjrE9KsGEUp0dkLUToKzj9tdDGxninCEUanhEV5ACTuXxGNqcvYoegYVRnGuLfCZTVMWwIezombduQDh7mEKkBRyw+XqQeRjCGHm+tZaAeo5p82cGX2bcxR2KselYfCVzP3n/yJsqp/D+STYd3bqq5Kno1f2obys2pyGhDri+scaZRZiWF7EysuYEuMC1D6EPkIo9ZEDfhgBU5RbJqC8h8DTTTLzsJa8r25L7qph/E7wVQywSLlL6e6GQ9bbepSikrNxXvftzkmEDNruN+5dVFznOEL5rTRa8Q98Dwet3i5c2PzWCzhz9/24rE0vrEOPO6NNgQ+du53wfG5uwkXEjAC4+w8nbXUrqe64y6o6AnRlo8YBwECV+qNfdpl0+6/JTP83L6aWo+u+QIiq4qpTF95SBNBt//SXk1KkYYgYNTo5Y/thjQ4ycHakHO4Mu/6Gz3+20uIOcSyhvO30Pk65scNcsTPHy1cT61cdK77fipJynVkn6rLKSWvqXMEeP6LwnioLx5WChyNAf+oubFMzlgS8zLW+kH8NxR4BIBpwbuyy+hpXXKZKNYKJtCzSUdBuV3PNZ0b0BaIEr6CyeqPtTPCfAoCxpvXipYJUEWLe6yPdorBQU0AYp3jMblrmqz/eAAPkiI5c7gTF6FAk0ezjzhvY3nVKPIvNi8Zj/5oxlt7p/kGjtUbMaQmGDrZco6TfDf0AtdERIDCUWnplKqLuz6NjIbZ7acAeyrBKyVslKNDn6tRZGk/4XrUFYgIEdWShOLZxAxI7o564n9+TycSIkz+LWYltQtRM1v5lnwNHSWXaXp7NUbhwWA5FlWPKBDOznhM0d6R5CRGfmtxE0KMQ3wac6QiODL3Zh5TJeKyFvXTh9hpSVfCafCMDtPWjzYj0X5G/DFCyNhIOf8Z8jfMBf2kh0AWI8ojZNS3c8ip1+elzLcGLDVfkLGF7rwvVDYXL/2cfamtfwR+CrljXlzT+FPRaMER8plhj6hN27UCWjILXVE7UqInNz/nkTS/YsZtVRaafCEXO1ytyV0gkXbKk00WFN6Qg3cK8HB0ITTYns8+NhstRTXewj9VJv/Kl7GV9Uz/DQX4KZW9rlJNd3XZjPYJRsHeRNXCnezTydyLpjrLYipbpYnuQcqBhE7S3nBiM+fTFrm8XJr7EBf4uIm8/9PSG52PMUiSsLHt+qMg36z+pliavLU5GXUw8RLONP2EnjI14vu8rz8BqlXhUuluWYRKeVNXprIG6pAkahqxhTmqwqYu1VIPDTPfuWJfQ9RCz5BYQEN4flTkuqGj0yYdl9CRxuL+m1BbPcZLufx+gY0gC5buiskzalfek3QhQLu2kH9P4zGuK/03jRVYlPf4IL0rt4l/e5iMV3bKB27Ds6W2An4NQB13WaujyINDiicJcRCyIQwsJt9aiLxf+NKc7N3iCLgjVw85K63WcDcW+zh8YOc89mJj/y3SOfTDAVCUjEzye9TldX8/7biavhsn4Bwdd71VkzxS/eSAJHeTXjw94FveP1d/A86LMBl/PThVEJmQMBZFjiMVw8x8xWG78NfqZDR2QeMU8meBhXrj4gwb3s2H9+H/6HcxoPB4O5kGbY4piQ3yu9MzPojVpQspdT2tgH+CAaNg3qHy3R/HHuWLxuXVGZj+2t3NoGvHrQRiS5e/9IwkQ2EA/3b20ZsBqP9F63oApr+PVnZYMbtRjJLdl02tIsMm4n5xoOI+0yP/93XSwD/rM+YXYu2PLiQRm5IkbDVu8Hg15OzIHSiGkTyPZb2/QBja10O9M/suKF7VZmS74skWZH3ETCNWyA5TpNwltafvou/1WtipHqNs45SXKJMr9iT3yI2jr/KGXlBDIih7mTiY5KN9mioAH1F5ROe0siEzBdaQe9soQsOBfFIZYzMBN/IKjuiqohG2DdDfsZqyYn0zO4GrqQ6Afv0fDY2fJJfbXCbn9i8saEI8CcSI2c2WTIxgQk+q6iOBBr0X/7WGzH7lmAHd4sDoIIi8F1QJyNlFsWj3r+5oe64jQa2XAkgVF3I6JENWpgSz6AJu7zQgnbg3ZIfF0kkwt+jRxEXsuLwdDO0XSxKBqv7Ynk0xDsV+5BkHzLv70fXh6BM8+LcK7kI8dgy8zJMLtctmAd9LmWcs7uWw8Di/EnZHZR5pDju0a+miq15yvHiJVjN6k5SoFRCVOQ== github:owner: modelcontextprotocol - github:token: - secure: v1:RKjKUcq1e0V7kVNf:5PP+vWbnRzgX8MmhF/yp52Fh78cTMBYz798JNc9LiyrubSt4AAZH/y/UA6kErP0FmXfFLRpdDIMmrR3RiNQlCe7nbkB6hf0CsV8mg8U7oB0EkA5wnRlqrZaKBri5l5puy2ZBFWjLhHr6oC714A== diff --git a/Pulumi.yaml b/Pulumi.yaml index 9c99e4f..eac338e 100644 --- a/Pulumi.yaml +++ b/Pulumi.yaml @@ -6,4 +6,5 @@ packages: source: terraform-provider version: 0.14.0 parameters: - - hashicorp/googleworkspace + - SamuZad/googleworkspace + - 0.11.1 diff --git a/README.md b/README.md index 57a50af..5375bcc 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,24 @@ Infrastructure as Code for managing access to MCP community resources using Pulu - **GitHub Teams**: Automatically syncs team memberships in the MCP GitHub organization - **Google Workspace Groups**: Automatically syncs group memberships for @modelcontextprotocol.io email accounts - **Email Groups**: Groups with `isEmailGroup: true` accept emails from anyone (including external users) and notify all members. External posts are moderated for security. +- **Google Workspace User Accounts**: Provisions @modelcontextprotocol.io accounts for members of roles with `provisionUser: true` + +### Opting in to a Google Workspace account (maintainers) + +If you're a maintainer and want an `@modelcontextprotocol.io` account, open a PR adding the following fields to your entry in [`src/config/users.ts`](src/config/users.ts): + +```ts +{ + github: 'your-github-username', + // ... + firstName: 'Your', + lastName: 'Name', + googleEmailPrefix: 'yourname', // -> yourname@modelcontextprotocol.io + memberOf: [ROLE_IDS.MAINTAINERS /* , ... */], +}, +``` + +Once merged, Pulumi provisions the account. An admin will share your initial password (retrievable via `pulumi stack output --show-secrets newGWSUserPasswords`). ## Deployment @@ -21,6 +39,7 @@ Infrastructure as Code for managing access to MCP community resources using Pulu ### Manual Deployment Pre-requisites: + - [Pulumi CLI installed](https://www.pulumi.com/docs/iac/download-install/) - [Google Cloud SDK installed](https://cloud.google.com/sdk/docs/install) - Access to GCP project and GCS bucket @@ -79,6 +98,7 @@ gsutil mb gs://mcp-access-prod-pulumi-state ``` Then: + 1. In Google Workspace Admin Console, go to **Account** → **Admin roles** 2. Select **Groups Admin** role (or create a custom role with these privileges): - Read, create, update, and delete groups @@ -104,5 +124,6 @@ pulumi config set --secret github:token "ghp_your_github_token_here" ### 3. Configure GitHub Actions Secrets Add the CI/CD secrets to GitHub Actions (repository settings → Secrets and variables → Actions): + - `GCP_PROD_SERVICE_ACCOUNT_KEY`: Content of `sa-key.json` - `PULUMI_PROD_PASSPHRASE`: The passphrase you set above diff --git a/To b/To new file mode 100644 index 0000000..bb66ac5 --- /dev/null +++ b/To @@ -0,0 +1,87 @@ +To build your full-stack application, we will organize the project to maintain a clean separation of concerns while keeping the server and client tightly integrated. +### **1. Recommended Project Structure** +For a combined Node.js/Express + Socket.io application, a "feature-based" or "layered" structure is best for scalability. +```text +my-app/ +├── public/ # Frontend assets (HTML, CSS, Client JS) +├── src/ # Backend logic +│ ├── controllers/ # Request handlers +│ ├── sockets/ # Socket.io event handlers +│ ├── routes/ # API routes +│ └── server.js # App entry point +├── .env # Environment variables (git-ignored) +├── .gitignore +├── package.json +└── README.md + +``` +### **2. Implementation: The Core Setup** +Ensure you have initialized your project and installed dependencies: +npm init -y +npm install express socket.io +#### **src/server.js (The Glue)** +This file integrates Express and Socket.io to serve your frontend and manage real-time events. +```javascript +const express = require('express'); +const { createServer } = require('node:http'); +const { join } = require('node:path'); +const { Server } = require('socket.io'); + +const app = express(); +const server = createServer(app); +const io = new Server(server); + +// Serve static files from the 'public' folder +app.use(express.static(join(__dirname, '../public'))); + +// Socket.io connection logic +io.on('connection', (socket) => { + console.log('A user connected'); + + socket.on('chat message', (msg) => { + io.emit('chat message', msg); + }); +}); + +const PORT = process.env.PORT || 3000; +server.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}`); +}); + +``` +### **3. Documentation: README.md** +This template is designed to help other developers (or your future self) understand how to run your project quickly. +```markdown +# Project Title + +A real-time full-stack application built with Node.js, Express, and Socket.io. + +## Prerequisites +- Node.js (v18.x or higher) +- npm + +## Installation +1. Clone this repository. +2. Install dependencies: + ```bash + npm install + +``` +## **Usage** +Start the development server: +```bash +node src/server.js + +``` +Open your browser to http://localhost:3000. +## **Project Structure** + * /public: Frontend client-side files. + * /src: Backend server logic, routes, and socket handlers. +``` + +--- + +### Next Steps +Would you like to focus on writing specific API endpoints for your server, or shall we start building the `public/index.html` file to handle the client-side socket connection? + +``` diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..9295c0e --- /dev/null +++ b/devenv.nix @@ -0,0 +1,9 @@ +{ pkgs, ... }: +{ + packages = [ + pkgs.google-cloud-sdk + pkgs.nodejs_22 + pkgs.pulumi-bin + pkgs.pulumiPackages.pulumi-nodejs + ]; +} diff --git a/package-lock.json b/package-lock.json index c914992..cc1f02d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,19 +9,35 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@pulumi/github": "^6.7.3", + "@pulumi/github": "^6.11.0", "@pulumi/googleworkspace": "file:sdks/googleworkspace", - "@pulumi/pulumi": "^3.197.0" + "@pulumi/pulumi": "^3.218.0", + "@pulumi/random": "^4.14.0" }, "devDependencies": { "@types/node": "^22.18.6", + "prettier": "^3.7.2", + "ts-node": "^10.9.2", "typescript": "^5.9.2" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@grpc/grpc-js": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", - "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.8.0", @@ -49,21 +65,37 @@ "node": ">=6" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "license": "ISC", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "minipass": "^7.0.4" }, "engines": { - "node": ">=12" + "node": ">=18.0.0" } }, "node_modules/@isaacs/string-locale-compare": { @@ -72,6 +104,34 @@ "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", "license": "ISC" }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", @@ -92,242 +152,239 @@ } }, "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/arborist": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-7.5.4.tgz", - "integrity": "sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-9.2.0.tgz", + "integrity": "sha512-FRvpUoL5RUaMZ+CNJAZAegHymbamcGptjktD72T8Td94OllbJcBlOZQ69cB/DiNTYDvGBU0uvmcBWivlip1h+Q==", "license": "ISC", "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.1", - "@npmcli/installed-package-contents": "^2.1.0", - "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^7.1.1", - "@npmcli/name-from-folder": "^2.0.0", - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.1.0", - "@npmcli/query": "^3.1.0", - "@npmcli/redact": "^2.0.0", - "@npmcli/run-script": "^8.1.0", - "bin-links": "^4.0.4", - "cacache": "^18.0.3", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^7.0.2", - "json-parse-even-better-errors": "^3.0.2", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^7.2.1", - "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.2", - "npm-pick-manifest": "^9.0.1", - "npm-registry-fetch": "^17.0.1", - "pacote": "^18.0.6", - "parse-conflict-json": "^3.0.0", - "proc-log": "^4.2.0", - "proggy": "^2.0.0", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", - "ssri": "^10.0.6", + "ssri": "^13.0.0", "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" + "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", - "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.1.tgz", + "integrity": "sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==", "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "ini": "^4.1.3", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git/node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", "license": "ISC", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/map-workspaces": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", - "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz", + "integrity": "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==", "license": "ISC", "dependencies": { - "@npmcli/name-from-folder": "^2.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0", - "read-package-json-fast": "^3.0.0" + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/metavuln-calculator": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-7.1.1.tgz", - "integrity": "sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz", + "integrity": "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==", "license": "ISC", "dependencies": { - "cacache": "^18.0.0", - "json-parse-even-better-errors": "^3.0.0", - "pacote": "^18.0.0", - "proc-log": "^4.1.0", + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/name-from-folder": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", - "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz", + "integrity": "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/package-json": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", - "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.4.tgz", + "integrity": "sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==", "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", "license": "ISC", "dependencies": { - "which": "^4.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/query": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz", - "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-5.0.0.tgz", + "integrity": "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==", "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/redact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", - "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/run-script": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", - "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.3.tgz", + "integrity": "sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==", "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "proc-log": "^4.0.0", - "which": "^4.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@opentelemetry/api": { @@ -533,16 +590,6 @@ "node": ">=14" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -608,9 +655,9 @@ "license": "BSD-3-Clause" }, "node_modules/@pulumi/github": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/@pulumi/github/-/github-6.7.3.tgz", - "integrity": "sha512-uqXvZba3FIc5ZSZFk6G5fMtNvtjQAOP+pg8hXFx8VAd01eCXqquY6xgB5HlR7xhHclGanAjtyaWOyvTrHDP3kQ==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@pulumi/github/-/github-6.12.1.tgz", + "integrity": "sha512-OsbQgF8go/tA1PuMhFq7O0XVfYbwRk0/LHaZTH6OFO395SufdFFa1go3IeXvJCSA5oDCbdULerGg4n9sP8vTLg==", "license": "Apache-2.0", "dependencies": { "@pulumi/pulumi": "^3.142.0" @@ -621,14 +668,14 @@ "link": true }, "node_modules/@pulumi/pulumi": { - "version": "3.197.0", - "resolved": "https://registry.npmjs.org/@pulumi/pulumi/-/pulumi-3.197.0.tgz", - "integrity": "sha512-Pfjg6sluGDw11crvmI8wbi5k4YDBTxZBM1aI7h5Gd2a4DUyVhrSMUE2fFd1XN05wQKEDisahsygBbBZO0lg2zw==", + "version": "3.219.0", + "resolved": "https://registry.npmjs.org/@pulumi/pulumi/-/pulumi-3.219.0.tgz", + "integrity": "sha512-0AO1ZBJooKALToZYvPj/93PpToGA2rEKvIGQi95zNHevTTC3ibQrKXJDLwQzjHy6HZrON2jbxzakKrU4C2w+hA==", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.10.1", "@logdna/tail-file": "^2.0.6", - "@npmcli/arborist": "^7.3.1", + "@npmcli/arborist": "^9.0.0", "@opentelemetry/api": "^1.9", "@opentelemetry/exporter-zipkin": "^1.28", "@opentelemetry/instrumentation": "^0.55", @@ -640,15 +687,15 @@ "@types/semver": "^7.5.6", "@types/tmp": "^0.2.6", "execa": "^5.1.0", - "fdir": "^6.1.1", + "fdir": "^6.5.0", "google-protobuf": "^3.21.4", "got": "^11.8.6", "ini": "^2.0.0", - "js-yaml": "^3.14.0", + "js-yaml": "^3.14.2", "minimist": "^1.2.6", "normalize-package-data": "^6.0.0", + "package-directory": "^8.1.0", "picomatch": "^3.0.1", - "pkg-dir": "^7.0.0", "require-from-string": "^2.0.1", "semver": "^7.5.2", "source-map-support": "^0.5.6", @@ -671,78 +718,87 @@ } } }, + "node_modules/@pulumi/random": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@pulumi/random/-/random-4.19.1.tgz", + "integrity": "sha512-gDotQyxtl+pcJb4oaGfbi6PsK8OtzjBmF1D8PYLvr13kCup98KTIIv/8wF2xbgbFf7ljJn3EblADxZ0u8AC/Dg==", + "license": "Apache-2.0", + "dependencies": { + "@pulumi/pulumi": "^3.142.0" + } + }, "node_modules/@sigstore/bundle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.1.0.tgz", + "integrity": "sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A==", "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", - "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", "license": "Apache-2.0", "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.0.tgz", + "integrity": "sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg==", "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", "promise-retry": "^2.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/tuf": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.1.tgz", + "integrity": "sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw==", "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/verify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sindresorhus/is": { @@ -769,6 +825,34 @@ "node": ">=10" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", @@ -779,16 +863,16 @@ } }, "node_modules/@tufjs/models": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" + "minimatch": "^10.1.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@types/cacheable-request": { @@ -825,9 +909,9 @@ } }, "node_modules/@types/node": { - "version": "22.18.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", - "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", + "version": "22.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", + "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -861,12 +945,12 @@ "license": "MIT" }, "node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/acorn": { @@ -890,51 +974,34 @@ "acorn": "^8" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "devOptional": true, "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "acorn": "^8.11.0" }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">= 14" } }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true, + "license": "MIT" }, "node_modules/argparse": { "version": "1.0.10", @@ -954,34 +1021,20 @@ "tslib": "^2.4.0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, "node_modules/bin-links": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", - "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.0.tgz", + "integrity": "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==", "license": "ISC", "dependencies": { - "cmd-shim": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "read-cmd-shim": "^4.0.0", - "write-file-atomic": "^5.0.0" + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/buffer-from": { @@ -991,26 +1044,25 @@ "license": "MIT" }, "node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/cacheable-lookup": { @@ -1056,12 +1108,12 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/cjs-module-lexer": { @@ -1070,15 +1122,6 @@ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "license": "MIT" }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1179,12 +1222,12 @@ } }, "node_modules/cmd-shim": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", - "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", + "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/color-convert": { @@ -1206,10 +1249,20 @@ "license": "MIT" }, "node_modules/common-ancestor-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "license": "ISC" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true, + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -1311,17 +1364,15 @@ "node": ">=10" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, "node_modules/encoding": { "version": "0.1.13", @@ -1403,9 +1454,9 @@ } }, "node_modules/exponential-backoff": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", - "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "license": "Apache-2.0" }, "node_modules/fdir": { @@ -1425,50 +1476,18 @@ } } }, - "node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "license": "MIT", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", @@ -1512,20 +1531,17 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", + "integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.1.2", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1581,15 +1597,15 @@ } }, "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/http-cache-semantics": { @@ -1660,21 +1676,21 @@ } }, "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "license": "ISC", "dependencies": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/import-in-the-middle": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.14.2.tgz", - "integrity": "sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", "license": "Apache-2.0", "dependencies": { "acorn": "^8.14.0", @@ -1692,15 +1708,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ini": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", @@ -1711,9 +1718,9 @@ } }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -1743,12 +1750,6 @@ "node": ">=8" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "license": "MIT" - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -1762,33 +1763,18 @@ } }, "node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.2.tgz", + "integrity": "sha512-mIcis6w+JiQf3P7t7mg/35GKB4T1FQsBOtMIvuKw4YErj5RjtbhcTd5/I30fmkmGMwvI0WlzSNN+27K0QCMkAw==", "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "engines": { + "node": ">=20" } }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -1805,12 +1791,12 @@ "license": "MIT" }, "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/json-stringify-nice": { @@ -1852,21 +1838,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -1889,32 +1860,41 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true, "license": "ISC" }, "node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", + "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^13.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/merge-stream": { @@ -1942,15 +1922,15 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", + "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1987,17 +1967,17 @@ } }, "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.1.tgz", + "integrity": "sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==", "license": "MIT", "dependencies": { "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -2027,6 +2007,12 @@ "node": ">=8" } }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", @@ -2051,65 +2037,34 @@ "node": ">=8" } }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { "node": ">=8" } }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "node": ">= 18" } }, "node_modules/module-details-from-path": { @@ -2125,51 +2080,51 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/node-gyp": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", - "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^4.1.0", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^6.2.1", - "which": "^4.0.0" + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/normalize-package-data": { @@ -2186,6 +2141,24 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -2199,97 +2172,98 @@ } }, "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-package-arg": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", - "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", + "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", "license": "ISC", "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.3.tgz", + "integrity": "sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==", "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.4" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-pick-manifest": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", - "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "license": "ISC", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-registry-fetch": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", - "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "license": "ISC", "dependencies": { - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^13.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-run-path": { @@ -2337,109 +2311,76 @@ "node": ">=8" } }, - "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/package-directory": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/package-directory/-/package-directory-8.1.0.tgz", + "integrity": "sha512-qHKRW0pw3lYdZMQVkjDBqh8HlamH/LCww2PH7OWEp4Qrt3SFeYMNpnJrQzlSnGrDD5zGR51XqBh7FnNCdVNEHA==", "license": "MIT", "dependencies": { - "aggregate-error": "^3.0.0" + "find-up-simple": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, "node_modules/pacote": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", - "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.2.0.tgz", + "integrity": "sha512-OwidJA8uHuGYxoZhe4DBv3JJqGg4ojjVV5dwvVxRKq+bOBpgYMbYd/onIvSU1sv5nQIsb+zp4/0uqv6giFQVjg==", "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/package-json": "^5.1.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^8.0.0", - "cacache": "^18.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^17.0.0", - "proc-log": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/parse-conflict-json": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", - "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz", + "integrity": "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ==", "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^3.0.0", + "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/path-key": { @@ -2458,16 +2399,16 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2485,50 +2426,51 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { - "find-up": "^6.3.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "bin": { + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/proggy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/proggy/-/proggy-2.0.0.tgz", - "integrity": "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-4.0.0.tgz", + "integrity": "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/promise-all-reject-late": { @@ -2549,12 +2491,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "license": "ISC" - }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -2615,25 +2551,12 @@ } }, "node_modules/read-cmd-shim": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", - "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz", + "integrity": "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==", "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/require-directory": { @@ -2669,12 +2592,12 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -2723,9 +2646,9 @@ "optional": true }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2768,20 +2691,20 @@ "license": "ISC" }, "node_modules/sigstore": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/smart-buffer": { @@ -2880,111 +2803,15 @@ "license": "BSD-3-Clause" }, "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/strip-final-newline": { @@ -3009,53 +2836,47 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "license": "ISC", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" + "node": ">=12.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/tmp": { @@ -3076,6 +2897,50 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -3083,23 +2948,23 @@ "license": "0BSD" }, "node_modules/tuf-js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -3117,27 +2982,27 @@ "license": "MIT" }, "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "unique-slug": "^6.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/upath": { @@ -3156,6 +3021,13 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -3167,24 +3039,27 @@ } }, "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/walk-up-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", - "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", - "license": "ISC" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "license": "ISC", "dependencies": { "isexe": "^3.1.1" @@ -3193,98 +3068,7 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/wrappy": { @@ -3294,16 +3078,16 @@ "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.0.tgz", + "integrity": "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==", "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/write-file-atomic/node_modules/signal-exit": { @@ -3328,10 +3112,13 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yargs": { "version": "17.7.2", @@ -3401,16 +3188,14 @@ "node": ">=8" } }, - "node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "sdks/googleworkspace": { @@ -3425,9 +3210,9 @@ } }, "sdks/googleworkspace/node_modules/@types/node": { - "version": "18.19.127", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.127.tgz", - "integrity": "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA==", + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" diff --git a/package.json b/package.json index fed8ef4..47aabb9 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,24 @@ "description": "Infrastructure as Code for MCP access management", "main": "src/index.ts", "scripts": { - "build": "tsc" + "build": "tsc", + "validate": "npx ts-node scripts/validate-config.ts", + "test": "npx ts-node scripts/test-config.ts", + "check": "npm run format:check && npm run validate && npm run test", + "format": "prettier --write .", + "format:check": "prettier --check ." }, "license": "MIT", "dependencies": { - "@pulumi/github": "^6.7.3", + "@pulumi/github": "^6.11.0", "@pulumi/googleworkspace": "file:sdks/googleworkspace", - "@pulumi/pulumi": "^3.197.0" + "@pulumi/pulumi": "^3.218.0", + "@pulumi/random": "^4.14.0" }, "devDependencies": { "@types/node": "^22.18.6", + "prettier": "^3.7.2", + "ts-node": "^10.9.2", "typescript": "^5.9.2" } } diff --git a/scripts/test-config.ts b/scripts/test-config.ts new file mode 100644 index 0000000..108e57e --- /dev/null +++ b/scripts/test-config.ts @@ -0,0 +1,132 @@ +#!/usr/bin/env npx ts-node + +/** + * Tests the configuration structure without needing Pulumi credentials. + * Run with: npx ts-node scripts/test-config.ts + */ + +import { ROLES, buildRoleLookup, getRolesForPlatform } from '../src/config/roles'; +import { ROLE_IDS, isValidRoleId } from '../src/config/roleIds'; +import { MEMBERS } from '../src/config/users'; + +let passed = 0; +let failed = 0; + +function test(name: string, fn: () => boolean) { + try { + if (fn()) { + console.log(`✓ ${name}`); + passed++; + } else { + console.log(`✗ ${name}`); + failed++; + } + } catch (e) { + console.log(`✗ ${name}: ${e}`); + failed++; + } +} + +console.log('Testing role configuration...\n'); + +// Test ROLE_IDS +test('ROLE_IDS has entries', () => Object.keys(ROLE_IDS).length > 0); +test('All ROLE_IDS values are valid', () => + Object.values(ROLE_IDS).every((id) => isValidRoleId(id))); + +// Test ROLES +test('ROLES array is not empty', () => ROLES.length > 0); +test('All roles have id and description', () => ROLES.every((r) => r.id && r.description)); +test('All role IDs are unique', () => { + const ids = ROLES.map((r) => r.id); + return ids.length === new Set(ids).size; +}); + +// Test platform configs +const githubRoles = getRolesForPlatform('github'); +const discordRoles = getRolesForPlatform('discord'); +const googleRoles = getRolesForPlatform('google'); + +test('Has GitHub roles', () => githubRoles.length > 0); +test('Has Discord roles', () => discordRoles.length > 0); +test('Has Google roles', () => googleRoles.length > 0); + +test('GitHub roles have team names', () => githubRoles.every((r) => r.github?.team)); +test('Discord roles have role names', () => discordRoles.every((r) => r.discord?.role)); +test('Google roles have group names', () => googleRoles.every((r) => r.google?.group)); + +// Test parent relationships +const roleLookup = buildRoleLookup(); +test('All GitHub parent references are valid', () => + githubRoles.every((r) => { + if (!r.github?.parent) return true; + const parent = roleLookup.get(r.github.parent); + return parent && parent.github; + })); + +// Test members +test('MEMBERS array is not empty', () => MEMBERS.length > 0); +test('All members have at least one identifier', () => + MEMBERS.every((m) => m.github || m.email || m.discord)); +test('All member role references are valid', () => + MEMBERS.every((m) => m.memberOf.every((id) => roleLookup.has(id)))); + +// Test specific roles exist +test('CORE_MAINTAINERS role exists', () => !!roleLookup.get(ROLE_IDS.CORE_MAINTAINERS)); +test('ADMINISTRATORS role exists (Discord-only)', () => { + const role = roleLookup.get(ROLE_IDS.ADMINISTRATORS); + return role !== undefined && role.discord !== undefined && role.github === undefined; +}); +test('TYPESCRIPT_SDK_AUTH role exists (GitHub-only)', () => { + const role = roleLookup.get(ROLE_IDS.TYPESCRIPT_SDK_AUTH); + return role !== undefined && role.github !== undefined && role.discord === undefined; +}); + +// Test Google Workspace user provisioning +test('Roles with provisionUser exist', () => { + const provisionRoles = ROLES.filter((r) => r.google?.provisionUser); + return provisionRoles.length > 0; +}); + +test('Members with googleEmailPrefix have firstName and lastName', () => + MEMBERS.every((m) => { + if (!m.googleEmailPrefix) return true; + return !!m.firstName && !!m.lastName; + })); + +test('googleEmailPrefix values are unique', () => { + const prefixes = MEMBERS.filter((m) => m.googleEmailPrefix).map((m) => m.googleEmailPrefix); + return prefixes.length === new Set(prefixes).size; +}); + +test('skipGoogleUserProvisioning is only used in provisionUser roles and without fields', () => { + const provisionRoleIds = new Set(ROLES.filter((r) => r.google?.provisionUser).map((r) => r.id)); + return MEMBERS.every((member) => { + const inProvisionRole = member.memberOf.some((id) => provisionRoleIds.has(id)); + if (!inProvisionRole) return !member.skipGoogleUserProvisioning; + + const hasProvisioningFields = !!( + member.firstName && + member.lastName && + member.googleEmailPrefix + ); + return !(hasProvisioningFields && member.skipGoogleUserProvisioning); + }); +}); + +test('Some members in provisionUser roles have Google user fields', () => { + const membersInProvisionRoles = MEMBERS.filter((m) => + m.memberOf.some((id) => { + const role = roleLookup.get(id); + return role?.google?.provisionUser === true; + }) + ); + const provisioned = membersInProvisionRoles.filter( + (m) => m.firstName && m.lastName && m.googleEmailPrefix + ); + return membersInProvisionRoles.length > 0 && provisioned.length > 0; +}); + +// Summary +console.log(`\n${passed} passed, ${failed} failed`); +process.exit(failed > 0 ? 1 : 0); diff --git a/scripts/validate-config.ts b/scripts/validate-config.ts new file mode 100644 index 0000000..168c00f --- /dev/null +++ b/scripts/validate-config.ts @@ -0,0 +1,185 @@ +#!/usr/bin/env npx ts-node + +/** + * Validates that all references in the config are valid. + * Run with: npx ts-node scripts/validate-config.ts + */ + +import { ROLES, buildRoleLookup } from '../src/config/roles'; +import { REPOSITORY_ACCESS } from '../src/config/repoAccess'; +import { MEMBERS } from '../src/config/users'; +import type { RoleId } from '../src/config/roleIds'; + +const roleLookup = buildRoleLookup(); + +// Get all GitHub team names (roles that have GitHub config) +const githubTeamNames = new Set(); +// Get all role IDs (for member validation) +const allRoleIds = new Set(); + +for (const role of ROLES) { + allRoleIds.add(role.id); + + if (role.github) { + githubTeamNames.add(role.github.team); + } +} + +let hasErrors = false; + +// Validate team references in REPOSITORY_ACCESS +console.log('Validating team references in repoAccess.ts...'); +for (const repo of REPOSITORY_ACCESS) { + if (!repo.teams) continue; + + for (const teamRef of repo.teams) { + if (!githubTeamNames.has(teamRef.team)) { + console.error( + `ERROR: Repository "${repo.repository}" references team "${teamRef.team}" which does not exist in roles.ts` + ); + hasErrors = true; + } + } +} + +// Validate role references in MEMBERS (memberOf) +console.log('Validating role references in users.ts...'); +for (const member of MEMBERS) { + for (const roleId of member.memberOf) { + if (!allRoleIds.has(roleId)) { + console.error( + `ERROR: Member "${member.github || member.email}" references role "${roleId}" which does not exist in roles.ts` + ); + hasErrors = true; + } + } +} + +// Validate member ordering in users.ts +console.log('Validating member ordering in users.ts...'); +{ + // Split members into github members and email-only members + const githubMembers: (typeof MEMBERS)[number][] = []; + const emailOnlyMembers: (typeof MEMBERS)[number][] = []; + + for (const member of MEMBERS) { + if (member.github) { + githubMembers.push(member); + } else if (member.email) { + emailOnlyMembers.push(member); + } + } + + // Check that github members come before email-only members + let foundEmailOnly = false; + for (const member of MEMBERS) { + if (!member.github && member.email) { + foundEmailOnly = true; + } else if (member.github && foundEmailOnly) { + console.error( + `ERROR: Member "${member.github}" appears after email-only members. GitHub members should come before email-only members.` + ); + hasErrors = true; + break; + } + } + + // Check that github members are sorted alphabetically (case-insensitive) + for (let i = 1; i < githubMembers.length; i++) { + const prev = githubMembers[i - 1].github!.toLowerCase(); + const curr = githubMembers[i].github!.toLowerCase(); + if (prev > curr) { + console.error( + `ERROR: Members are not sorted alphabetically. "${githubMembers[i - 1].github}" should come after "${githubMembers[i].github}".` + ); + hasErrors = true; + break; + } + } +} + +// Validate Google Workspace user provisioning fields +console.log('Validating Google Workspace user provisioning fields...'); +{ + const googleEmailPrefixes = new Map(); + + for (const member of MEMBERS) { + const memberId = member.github || member.email || 'unknown'; + + // Members with googleEmailPrefix must also have firstName and lastName + if (member.googleEmailPrefix) { + if (!member.firstName) { + console.error(`ERROR: Member "${memberId}" has googleEmailPrefix but is missing firstName`); + hasErrors = true; + } + if (!member.lastName) { + console.error(`ERROR: Member "${memberId}" has googleEmailPrefix but is missing lastName`); + hasErrors = true; + } + + // Check uniqueness of googleEmailPrefix + const existing = googleEmailPrefixes.get(member.googleEmailPrefix); + if (existing) { + console.error( + `ERROR: googleEmailPrefix "${member.googleEmailPrefix}" is used by both "${existing}" and "${memberId}"` + ); + hasErrors = true; + } else { + googleEmailPrefixes.set(member.googleEmailPrefix, memberId); + } + } + + // Members in provisionUser roles without all three fields won't get a GWS account + const inProvisionUserRole = member.memberOf.some((roleId: RoleId) => { + const role = roleLookup.get(roleId); + return role?.google?.provisionUser === true; + }); + + const hasProvisioningFields = !!( + member.googleEmailPrefix && + member.firstName && + member.lastName + ); + + if (member.skipGoogleUserProvisioning && !inProvisionUserRole) { + console.error( + `ERROR: Member "${memberId}" has skipGoogleUserProvisioning=true but is not in a provisionUser role` + ); + hasErrors = true; + } + + if (inProvisionUserRole && hasProvisioningFields && member.skipGoogleUserProvisioning) { + console.error( + `ERROR: Member "${memberId}" has provisioning fields and skipGoogleUserProvisioning=true; pick one` + ); + hasErrors = true; + } + } +} + +// Validate parent role references in roles.ts +console.log('Validating parent role references in roles.ts...'); +for (const role of ROLES) { + if (!role.github?.parent) continue; + + const parentRole = roleLookup.get(role.github.parent); + if (!parentRole) { + console.error( + `ERROR: Role "${role.id}" has parent "${role.github.parent}" which does not exist in roles.ts` + ); + hasErrors = true; + } else if (!parentRole.github) { + console.error( + `ERROR: Role "${role.id}" has parent "${role.github.parent}" which does not have GitHub config` + ); + hasErrors = true; + } +} + +if (hasErrors) { + console.error('\nValidation failed! Please fix the errors above.'); + process.exit(1); +} else { + console.log('\nAll references are valid.'); + process.exit(0); +} diff --git a/src/config/groups.ts b/src/config/groups.ts deleted file mode 100644 index c336f3a..0000000 --- a/src/config/groups.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { defineGroups } from './utils'; - -// NOTE: For GitHub teams, only the first memberOf will be used as the parent team. -// GitHub only supports one parent team per team. -// -// Email groups (isEmailGroup: true) accept emails from anyone (including external users) -// and notify group members for each email. -// -// Groups are created on all platforms (GitHub and Google) by default. -// To limit a group to specific platforms, set the onlyOnPlatforms array (e.g., onlyOnPlatforms: ['google']). -export const GROUPS = defineGroups([ - { - name: 'test-parent', - description: 'All maintainers. Users should not be added directly to this group.', - }, - { - name: 'test-child', - description: 'Registry maintainers', - memberOf: ['test-parent'], - }, - { - name: 'test-email-group', - description: 'Example email group that accepts external emails', - isEmailGroup: true, - }, - { - name: 'antitrust', - description: 'Antitrust compliance contacts', - isEmailGroup: true, - onlyOnPlatforms: ['google'], - }, - { - name: 'catch-all', - description: 'Catch-all email group', - isEmailGroup: true, - onlyOnPlatforms: ['google'], - }, -] as const); \ No newline at end of file diff --git a/src/config/orgRoles.ts b/src/config/orgRoles.ts new file mode 100644 index 0000000..576ec51 --- /dev/null +++ b/src/config/orgRoles.ts @@ -0,0 +1,23 @@ +// GitHub organization-level role assignments. +// These grant a base permission across ALL repositories in the org via GitHub's +// pre-defined organization roles, independent of per-repo collaborators in repoAccess.ts. +// See https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/using-organization-roles + +export type OrgRoleName = + | 'all_repo_read' + | 'all_repo_triage' + | 'all_repo_write' + | 'all_repo_maintain' + | 'all_repo_admin'; + +export interface OrgRoleAssignment { + /** GitHub team slug */ + team: string; + /** Pre-defined GitHub organization role name */ + role: OrgRoleName; +} + +export const ORG_ROLE_ASSIGNMENTS: OrgRoleAssignment[] = [ + { team: 'lead-maintainers', role: 'all_repo_admin' }, + { team: 'core-maintainers', role: 'all_repo_admin' }, +]; diff --git a/src/config/orgSettings.ts b/src/config/orgSettings.ts new file mode 100644 index 0000000..18f9a13 --- /dev/null +++ b/src/config/orgSettings.ts @@ -0,0 +1,33 @@ +// GitHub organization-level settings. +// Captured explicitly so changes go through review. Values mirror current state +// except defaultRepositoryPermission, which is set to 'none' so private repos +// (disclosures, community-moderators, GHSA forks) are not implicitly readable +// by every org member. Explicit access flows from orgRoles.ts and repoAccess.ts. + +// billingEmail is required by the provider but intentionally omitted here so it +// is not committed to a public repo. It is read from Pulumi config in github.ts: +// pulumi config set --secret githubBillingEmail +export const ORG_SETTINGS = { + name: 'Model Context Protocol', + description: + 'An open protocol that enables seamless integration between LLM applications and external data sources and tools.', + defaultRepositoryPermission: 'none', + hasOrganizationProjects: true, + hasRepositoryProjects: true, + membersCanCreateRepositories: false, + membersCanCreatePublicRepositories: false, + membersCanCreatePrivateRepositories: false, + membersCanCreatePages: false, + membersCanCreatePublicPages: false, + membersCanCreatePrivatePages: false, + membersCanForkPrivateRepositories: false, + webCommitSignoffRequired: false, + // Provider defaults the following to `false` if omitted, which would silently + // disable org-wide security features that are currently on. + advancedSecurityEnabledForNewRepositories: true, + dependabotAlertsEnabledForNewRepositories: true, + dependabotSecurityUpdatesEnabledForNewRepositories: true, + dependencyGraphEnabledForNewRepositories: true, + secretScanningEnabledForNewRepositories: true, + secretScanningPushProtectionEnabledForNewRepositories: true, +} as const; diff --git a/src/config/repoAccess.ts b/src/config/repoAccess.ts new file mode 100644 index 0000000..ced3ac1 --- /dev/null +++ b/src/config/repoAccess.ts @@ -0,0 +1,402 @@ +// Repository access configuration +// Each repository lists all teams and users that should have access and their permission level + +export interface RepositoryAccess { + repository: string; + teams?: Array<{ + team: string; // Team slug + permission: 'pull' | 'triage' | 'push' | 'maintain' | 'admin'; + }>; + users?: Array<{ + username: string; // GitHub username + permission: 'pull' | 'triage' | 'push' | 'maintain' | 'admin'; + }>; +} + +export const REPOSITORY_ACCESS: RepositoryAccess[] = [ + { + repository: 'docs', + teams: [ + { team: 'auth-maintainers', permission: 'push' }, + { team: 'core-maintainers', permission: 'maintain' }, + { team: 'csharp-sdk', permission: 'push' }, + { team: 'docs-maintainers', permission: 'push' }, + { team: 'go-sdk', permission: 'push' }, + { team: 'ig-financial-services', permission: 'push' }, + { team: 'interest-groups', permission: 'push' }, + { team: 'java-sdk', permission: 'push' }, + { team: 'kotlin-sdk', permission: 'push' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'php-sdk', permission: 'push' }, + { team: 'python-sdk', permission: 'push' }, + { team: 'python-sdk-auth', permission: 'push' }, + { team: 'registry-wg', permission: 'push' }, + { team: 'ruby-sdk', permission: 'push' }, + { team: 'rust-sdk', permission: 'push' }, + { team: 'sdk-maintainers', permission: 'push' }, + { team: 'security-wg', permission: 'admin' }, + { team: 'steering-committee', permission: 'push' }, + { team: 'swift-sdk', permission: 'push' }, + { team: 'transport-wg', permission: 'push' }, + { team: 'typescript-sdk', permission: 'push' }, + { team: 'typescript-sdk-auth', permission: 'push' }, + { team: 'working-groups', permission: 'push' }, + ], + }, + { + repository: '.github', + teams: [ + { team: 'auth-maintainers', permission: 'triage' }, + { team: 'core-maintainers', permission: 'maintain' }, + { team: 'csharp-sdk', permission: 'triage' }, + { team: 'docs-maintainers', permission: 'triage' }, + { team: 'go-sdk', permission: 'triage' }, + { team: 'ig-financial-services', permission: 'triage' }, + { team: 'interest-groups', permission: 'triage' }, + { team: 'java-sdk', permission: 'triage' }, + { team: 'kotlin-sdk', permission: 'triage' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'php-sdk', permission: 'triage' }, + { team: 'python-sdk', permission: 'triage' }, + { team: 'python-sdk-auth', permission: 'triage' }, + { team: 'registry-wg', permission: 'triage' }, + { team: 'ruby-sdk', permission: 'triage' }, + { team: 'rust-sdk', permission: 'triage' }, + { team: 'sdk-maintainers', permission: 'triage' }, + { team: 'security-wg', permission: 'admin' }, + { team: 'steering-committee', permission: 'triage' }, + { team: 'swift-sdk', permission: 'triage' }, + { team: 'transport-wg', permission: 'triage' }, + { team: 'typescript-sdk', permission: 'triage' }, + { team: 'typescript-sdk-auth', permission: 'triage' }, + { team: 'working-groups', permission: 'triage' }, + ], + }, + { + repository: 'inspector', + teams: [ + { team: 'inspector-maintainers', permission: 'push' }, + { team: 'auth-maintainers', permission: 'push' }, + { team: 'core-maintainers', permission: 'maintain' }, + { team: 'csharp-sdk', permission: 'push' }, + { team: 'go-sdk', permission: 'push' }, + { team: 'java-sdk', permission: 'push' }, + { team: 'kotlin-sdk', permission: 'push' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'php-sdk', permission: 'push' }, + { team: 'python-sdk', permission: 'push' }, + { team: 'python-sdk-auth', permission: 'push' }, + { team: 'registry-wg', permission: 'push' }, + { team: 'ruby-sdk', permission: 'push' }, + { team: 'rust-sdk', permission: 'push' }, + { team: 'sdk-maintainers', permission: 'push' }, + { team: 'security-wg', permission: 'admin' }, + { team: 'steering-committee', permission: 'push' }, + { team: 'swift-sdk', permission: 'push' }, + { team: 'transport-wg', permission: 'push' }, + { team: 'typescript-sdk', permission: 'push' }, + { team: 'typescript-sdk-auth', permission: 'push' }, + ], + }, + { + repository: 'modelcontextprotocol', + teams: [ + { team: 'auth-maintainers', permission: 'push' }, + { team: 'core-maintainers', permission: 'maintain' }, + { team: 'csharp-sdk', permission: 'triage' }, + { team: 'docs-maintainers', permission: 'push' }, + { team: 'go-sdk', permission: 'triage' }, + { team: 'ig-financial-services', permission: 'triage' }, + { team: 'interest-groups', permission: 'triage' }, + { team: 'java-sdk', permission: 'triage' }, + { team: 'kotlin-sdk', permission: 'triage' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'php-sdk', permission: 'triage' }, + { team: 'python-sdk', permission: 'triage' }, + { team: 'python-sdk-auth', permission: 'triage' }, + { team: 'registry-wg', permission: 'triage' }, + { team: 'ruby-sdk', permission: 'triage' }, + { team: 'rust-sdk', permission: 'triage' }, + { team: 'sdk-maintainers', permission: 'triage' }, + { team: 'security-wg', permission: 'admin' }, + { team: 'steering-committee', permission: 'triage' }, + { team: 'swift-sdk', permission: 'triage' }, + { team: 'transport-wg', permission: 'triage' }, + { team: 'typescript-sdk', permission: 'triage' }, + { team: 'typescript-sdk-auth', permission: 'triage' }, + { team: 'working-groups', permission: 'triage' }, + ], + }, + { + repository: 'quickstart-resources', + teams: [ + { team: 'auth-maintainers', permission: 'push' }, + { team: 'core-maintainers', permission: 'maintain' }, + { team: 'csharp-sdk', permission: 'push' }, + { team: 'docs-maintainers', permission: 'push' }, + { team: 'go-sdk', permission: 'push' }, + { team: 'ig-financial-services', permission: 'push' }, + { team: 'interest-groups', permission: 'push' }, + { team: 'java-sdk', permission: 'push' }, + { team: 'kotlin-sdk', permission: 'push' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'php-sdk', permission: 'push' }, + { team: 'python-sdk', permission: 'push' }, + { team: 'python-sdk-auth', permission: 'push' }, + { team: 'registry-wg', permission: 'push' }, + { team: 'ruby-sdk', permission: 'push' }, + { team: 'rust-sdk', permission: 'push' }, + { team: 'sdk-maintainers', permission: 'push' }, + { team: 'security-wg', permission: 'admin' }, + { team: 'steering-committee', permission: 'push' }, + { team: 'swift-sdk', permission: 'push' }, + { team: 'transport-wg', permission: 'push' }, + { team: 'typescript-sdk', permission: 'push' }, + { team: 'typescript-sdk-auth', permission: 'push' }, + { team: 'working-groups', permission: 'push' }, + ], + }, + { + repository: 'servers', + teams: [ + { team: 'reference-servers-maintainers', permission: 'admin' }, + { team: 'auth-maintainers', permission: 'push' }, + { team: 'core-maintainers', permission: 'admin' }, + { team: 'csharp-sdk', permission: 'push' }, + { team: 'docs-maintainers', permission: 'push' }, + { team: 'go-sdk', permission: 'push' }, + { team: 'java-sdk', permission: 'push' }, + { team: 'kotlin-sdk', permission: 'push' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'php-sdk', permission: 'push' }, + { team: 'python-sdk', permission: 'push' }, + { team: 'python-sdk-auth', permission: 'push' }, + { team: 'registry-wg', permission: 'push' }, + { team: 'ruby-sdk', permission: 'push' }, + { team: 'rust-sdk', permission: 'push' }, + { team: 'sdk-maintainers', permission: 'push' }, + { team: 'security-wg', permission: 'admin' }, + { team: 'steering-committee', permission: 'push' }, + { team: 'swift-sdk', permission: 'push' }, + { team: 'transport-wg', permission: 'push' }, + { team: 'typescript-sdk', permission: 'push' }, + { team: 'typescript-sdk-auth', permission: 'push' }, + ], + }, + { + repository: 'csharp-sdk', + teams: [ + { team: 'csharp-sdk-admin', permission: 'admin' }, + { team: 'csharp-sdk', permission: 'maintain' }, + ], + users: [{ username: 'PederHP', permission: 'triage' }], + }, + { + repository: 'go-sdk', + teams: [{ team: 'go-sdk', permission: 'admin' }], + }, + { + repository: 'java-sdk', + teams: [{ team: 'java-sdk', permission: 'admin' }], + }, + { + repository: 'kotlin-sdk', + teams: [{ team: 'kotlin-sdk', permission: 'admin' }], + }, + { + repository: 'php-sdk', + teams: [{ team: 'php-sdk', permission: 'admin' }], + }, + { + repository: 'python-sdk', + teams: [ + { team: 'python-sdk', permission: 'admin' }, + { team: 'python-sdk-auth', permission: 'admin' }, + ], + users: [ + { username: 'ddworken', permission: 'admin' }, + { username: 'OctavianGuzu', permission: 'admin' }, + ], + }, + { + repository: 'ruby-sdk', + teams: [{ team: 'ruby-sdk', permission: 'admin' }], + }, + { + repository: 'rust-sdk', + teams: [{ team: 'rust-sdk', permission: 'admin' }], + }, + { + repository: 'swift-sdk', + teams: [{ team: 'swift-sdk', permission: 'admin' }], + }, + { + repository: 'typescript-sdk', + teams: [ + { team: 'typescript-sdk', permission: 'admin' }, + { team: 'typescript-sdk-auth', permission: 'admin' }, + { team: 'typescript-sdk-collaborators', permission: 'push' }, + ], + users: [ + { username: 'ddworken', permission: 'admin' }, + { username: 'OctavianGuzu', permission: 'admin' }, + ], + }, + { + repository: 'create-python-server', + teams: [ + { team: 'python-sdk', permission: 'admin' }, + { team: 'python-sdk-auth', permission: 'admin' }, + ], + }, + { + repository: 'create-typescript-server', + teams: [ + { team: 'typescript-sdk', permission: 'admin' }, + { team: 'typescript-sdk-auth', permission: 'admin' }, + ], + }, + { + repository: 'registry', + teams: [ + { team: 'registry-wg', permission: 'admin' }, + { team: 'registry-collaborators', permission: 'push' }, + ], + }, + { + repository: 'static', + teams: [{ team: 'registry-wg', permission: 'push' }], + }, + { + repository: 'financial-services-interest-group', + teams: [{ team: 'ig-financial-services', permission: 'admin' }], + users: [ + { username: 'aniabot', permission: 'pull' }, + { username: 'imfing', permission: 'triage' }, + { username: 'KengoA', permission: 'triage' }, + { username: 'nitsanh', permission: 'pull' }, + ], + }, + { + repository: 'ext-auth', + teams: [{ team: 'auth-maintainers', permission: 'admin' }], + }, + { + repository: 'ext-apps', + teams: [ + { team: 'core-maintainers', permission: 'push' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'mcp-apps-wg', permission: 'push' }, + { team: 'mcp-apps-sdk', permission: 'admin' }, + ], + users: [ + { username: 'ststrong', permission: 'admin' }, + { username: 'martinalong', permission: 'push' }, + { username: 'conorkel', permission: 'admin' }, + { username: 'alexi-openai', permission: 'admin' }, + ], + }, + { + repository: 'use-mcp', + teams: [ + { team: 'core-maintainers', permission: 'push' }, + { team: 'moderators', permission: 'maintain' }, + ], + users: [{ username: 'geelen', permission: 'admin' }], + }, + { + repository: 'example-remote-client', + teams: [ + { team: 'core-maintainers', permission: 'push' }, + { team: 'moderators', permission: 'maintain' }, + ], + users: [ + { username: 'geelen', permission: 'push' }, + { username: 'markyfyi', permission: 'push' }, + { username: 'jerryhong1', permission: 'push' }, + ], + }, + { + repository: 'example-remote-server', + teams: [ + { team: 'core-maintainers', permission: 'push' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'mcp-apps-wg', permission: 'push' }, + { team: 'mcp-apps-sdk', permission: 'admin' }, + ], + }, + { + repository: 'experimental-ext-grouping', + teams: [ + { team: 'core-maintainers', permission: 'admin' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'primitive-grouping-ig', permission: 'admin' }, + ], + }, + { + repository: 'experimental-ext-skills', + teams: [ + { team: 'core-maintainers', permission: 'admin' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'skills-over-mcp-ig', permission: 'admin' }, + ], + }, + { + repository: 'experimental-ext-tool-annotations', + teams: [ + { team: 'core-maintainers', permission: 'admin' }, + { team: 'moderators', permission: 'triage' }, + { team: 'tool-annotations-ig', permission: 'admin' }, + ], + }, + { + repository: 'experimental-ext-triggers-events', + teams: [ + { team: 'core-maintainers', permission: 'admin' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'triggers-events-wg', permission: 'admin' }, + ], + }, + { + repository: 'experimental-ext-interceptors', + teams: [ + { team: 'core-maintainers', permission: 'admin' }, + { team: 'moderators', permission: 'triage' }, + { team: 'interceptors-wg', permission: 'admin' }, + ], + }, + { + repository: 'ext-tasks', + teams: [ + { team: 'core-maintainers', permission: 'admin' }, + { team: 'moderators', permission: 'maintain' }, + { team: 'agents-wg', permission: 'admin' }, + ], + }, + { + repository: 'maintainer-docs', + teams: [ + { team: 'lead-maintainers', permission: 'maintain' }, + { team: 'core-maintainers', permission: 'admin' }, + { team: 'steering-committee', permission: 'maintain' }, + ], + users: [{ username: 'sambhav', permission: 'admin' }], + }, + { + repository: 'community-moderators', + teams: [ + { team: 'core-maintainers', permission: 'admin' }, + { team: 'moderators', permission: 'maintain' }, + ], + }, + { + repository: 'access', + users: [ + { username: 'felixweinberger', permission: 'admin' }, + { username: 'maxisbey', permission: 'admin' }, + ], + }, +]; + +// GitHub Projects V2 permissions are NOT managed by Pulumi - no support yet +// See: https://github.com/pulumi/pulumi-github/issues/1006 diff --git a/src/config/roleIds.ts b/src/config/roleIds.ts new file mode 100644 index 0000000..d47968d --- /dev/null +++ b/src/config/roleIds.ts @@ -0,0 +1,96 @@ +/** + * Role ID constants for type-safe role references. + * Using constants prevents typos and enables autocomplete. + */ +export const ROLE_IDS = { + // =================== + // Organization Structure + // =================== + STEERING_COMMITTEE: 'steering-committee', + CORE_MAINTAINERS: 'core-maintainers', + LEAD_MAINTAINERS: 'lead-maintainers', + MODERATORS: 'moderators', + ADMINISTRATORS: 'administrators', // Discord only + COMMUNITY_MANAGERS: 'community-managers', // Discord only + + // =================== + // Maintainer Groups + // =================== + MAINTAINERS: 'maintainers', + DOCS_MAINTAINERS: 'docs-maintainers', + INSPECTOR_MAINTAINERS: 'inspector-maintainers', + MCPB_MAINTAINERS: 'mcpb-maintainers', + REFERENCE_SERVERS_MAINTAINERS: 'reference-servers-maintainers', + REGISTRY_MAINTAINERS: 'registry-maintainers', + REGISTRY_COLLABORATORS: 'registry-collaborators', // GitHub only + USE_MCP_MAINTAINERS: 'use-mcp-maintainers', + + // =================== + // SDK Maintainers + // =================== + SDK_MAINTAINERS: 'sdk-maintainers', + CSHARP_SDK: 'csharp-sdk', + CSHARP_SDK_ADMIN: 'csharp-sdk-admin', + GO_SDK: 'go-sdk', + JAVA_SDK: 'java-sdk', + KOTLIN_SDK: 'kotlin-sdk', + MCP_APPS_SDK: 'mcp-apps-sdk', + PHP_SDK: 'php-sdk', + PYTHON_SDK: 'python-sdk', + PYTHON_SDK_AUTH: 'python-sdk-auth', // GitHub only (CODEOWNERS) + RUBY_SDK: 'ruby-sdk', + RUST_SDK: 'rust-sdk', + SWIFT_SDK: 'swift-sdk', + TYPESCRIPT_SDK: 'typescript-sdk', + TYPESCRIPT_SDK_AUTH: 'typescript-sdk-auth', // GitHub only (CODEOWNERS) + TYPESCRIPT_SDK_COLLABORATORS: 'typescript-sdk-collaborators', // GitHub only + + // =================== + // Working Groups + // =================== + WORKING_GROUPS: 'working-groups', + AUTH_MAINTAINERS: 'auth-maintainers', + SECURITY_WG: 'security-wg', + SERVER_IDENTITY_WG: 'server-identity-wg', + TRANSPORT_WG: 'transport-wg', + TRIGGERS_EVENTS_WG: 'triggers-events-wg', + MCP_APPS_WG: 'mcp-apps-wg', + SERVER_CARD_WG: 'server-card-wg', + INTERCEPTORS_WG: 'interceptors-wg', + FILE_UPLOADS_WG: 'file-uploads-wg', + AGENTS_WG: 'agents-wg', + + // =================== + // Interest Groups + // =================== + INTEREST_GROUPS: 'interest-groups', + AGENTS_IG: 'agents-ig', + AUTH_IG: 'auth-ig', + CLIENT_IMPLEMENTOR_IG: 'client-implementor-ig', + FINANCIAL_SERVICES_IG: 'financial-services-ig', + GATEWAYS_IG: 'gateways-ig', + PRIMITIVE_GROUPING_IG: 'primitive-grouping-ig', + SKILLS_OVER_MCP_IG: 'skills-over-mcp-ig', + TOOL_ANNOTATIONS_IG: 'tool-annotations-ig', + + // =================== + // WG/IG Facilitators (Discord only) + // =================== + WG_IG_FACILITATORS: 'wg-ig-facilitators', + + // =================== + // Email Groups (Google only) + // =================== + ANTITRUST: 'antitrust', + APPEALS: 'appeals', + CATCH_ALL: 'catch-all', +} as const; + +export type RoleId = (typeof ROLE_IDS)[keyof typeof ROLE_IDS]; + +/** + * Helper to check if a string is a valid RoleId at runtime. + */ +export function isValidRoleId(id: string): id is RoleId { + return Object.values(ROLE_IDS).includes(id as RoleId); +} diff --git a/src/config/roles.ts b/src/config/roles.ts new file mode 100644 index 0000000..df8ce6c --- /dev/null +++ b/src/config/roles.ts @@ -0,0 +1,433 @@ +import { ROLE_IDS, type RoleId } from './roleIds'; + +/** + * GitHub team configuration + */ +export interface GitHubConfig { + /** Team name (usually matches role ID) */ + team: string; + /** Parent team role ID */ + parent?: RoleId; +} + +/** + * Discord role configuration + */ +export interface DiscordConfig { + /** Display name in Discord (can have spaces) */ + role: string; +} + +/** + * Google Workspace group configuration + */ +export interface GoogleConfig { + /** Group name (used as prefix for @modelcontextprotocol.io email) */ + group: string; + /** If true, accepts emails from anyone including external users */ + isEmailGroup?: boolean; + /** If true, members of this role get a Google Workspace user account */ + provisionUser?: boolean; +} + +/** + * Role definition with platform-specific configurations. + * A role only exists on platforms where it has a config key. + */ +export interface Role { + id: RoleId; + description: string; + github?: GitHubConfig; + discord?: DiscordConfig; + google?: GoogleConfig; + /** + * Roles that are implied for Discord membership. + * If a user has this role, they automatically get the implied roles' Discord roles too. + * This is separate from GitHub parent relationships and allows Discord-specific hierarchy. + */ + discordImplies?: readonly RoleId[]; +} + +/** + * All roles in the MCP organization. + * Each role specifies which platforms it exists on via presence of config keys. + */ +export const ROLES: readonly Role[] = [ + // =================== + // Organization Structure + // =================== + { + id: ROLE_IDS.STEERING_COMMITTEE, + description: 'MCP Steering Committee', + github: { team: 'steering-committee' }, + // No discord - this is a GitHub-only organizational container + }, + { + id: ROLE_IDS.ADMINISTRATORS, + description: 'Discord server administrators', + discord: { role: 'administrators (synced)' }, + // Discord only - no GitHub equivalent + }, + { + id: ROLE_IDS.COMMUNITY_MANAGERS, + description: 'Discord community managers', + discord: { role: 'community managers (synced)' }, + // Discord only - no GitHub equivalent + }, + { + id: ROLE_IDS.LEAD_MAINTAINERS, + description: 'Lead core maintainers', + github: { team: 'lead-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, + discord: { role: 'lead maintainers (synced)' }, + google: { group: 'lead-maintainers', provisionUser: true }, + }, + { + id: ROLE_IDS.CORE_MAINTAINERS, + description: 'Core maintainers', + github: { team: 'core-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, + discord: { role: 'core maintainers (synced)' }, + google: { group: 'core-maintainers', provisionUser: true }, + }, + { + id: ROLE_IDS.MODERATORS, + description: 'Community moderators', + github: { team: 'moderators', parent: ROLE_IDS.STEERING_COMMITTEE }, + discord: { role: 'community moderators (synced)' }, + google: { group: 'moderators', provisionUser: true }, + }, + + // =================== + // Maintainer Groups + // =================== + { + id: ROLE_IDS.MAINTAINERS, + description: 'General maintainers', + discord: { role: 'maintainers (synced)' }, + // GWS user accounts are opt-in: maintainers add firstName/lastName/googleEmailPrefix + // to their entry in users.ts via PR to get an @modelcontextprotocol.io account + google: { group: 'maintainers', provisionUser: true }, + }, + { + id: ROLE_IDS.DOCS_MAINTAINERS, + description: 'MCP docs maintainers', + github: { team: 'docs-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, + // No discord role for docs maintainers + }, + { + id: ROLE_IDS.INSPECTOR_MAINTAINERS, + description: 'MCP Inspector maintainers', + github: { team: 'inspector-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, + discord: { role: 'inspector maintainers (synced)' }, + }, + { + id: ROLE_IDS.MCPB_MAINTAINERS, + description: 'MCPB (Model Context Protocol Bundle) maintainers', + github: { team: 'mcpb-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, + // No discord role + }, + { + id: ROLE_IDS.REFERENCE_SERVERS_MAINTAINERS, + description: 'Reference servers maintainers', + github: { team: 'reference-servers-maintainers' }, + discord: { role: 'reference servers maintainers (synced)' }, + }, + { + id: ROLE_IDS.REGISTRY_MAINTAINERS, + description: 'Official registry builders and maintainers', + github: { team: 'registry-wg', parent: ROLE_IDS.WORKING_GROUPS }, + discord: { role: 'registry maintainers (synced)' }, + google: { group: 'registry-wg', provisionUser: true }, + }, + { + id: ROLE_IDS.REGISTRY_COLLABORATORS, + description: 'Registry working group collaborators', + github: { team: 'registry-collaborators', parent: ROLE_IDS.REGISTRY_MAINTAINERS }, + }, + { + id: ROLE_IDS.USE_MCP_MAINTAINERS, + description: 'use-mcp maintainers', + discord: { role: 'use-mcp maintainers (synced)' }, + // Discord only + }, + + // =================== + // SDK Maintainers + // =================== + { + id: ROLE_IDS.SDK_MAINTAINERS, + description: 'Authors and maintainers of official MCP SDKs', + github: { team: 'sdk-maintainers', parent: ROLE_IDS.STEERING_COMMITTEE }, + discord: { role: 'sdk maintainers (synced)' }, + discordImplies: [ROLE_IDS.MAINTAINERS], // SDK maintainers are also general maintainers + }, + { + id: ROLE_IDS.CSHARP_SDK, + description: 'Official C# SDK maintainers', + github: { team: 'csharp-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'c# sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.CSHARP_SDK_ADMIN, + description: 'C# SDK repository admins', + github: { team: 'csharp-sdk-admin', parent: ROLE_IDS.CSHARP_SDK }, + // GitHub only - for repo admin access + }, + { + id: ROLE_IDS.GO_SDK, + description: 'The Go SDK Team', + github: { team: 'go-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'go sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.JAVA_SDK, + description: 'Official Java SDK maintainers', + github: { team: 'java-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'java sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.KOTLIN_SDK, + description: 'Official Kotlin SDK maintainers', + github: { team: 'kotlin-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'kotlin sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.MCP_APPS_SDK, + description: 'Official MCP Apps SDK maintainers', + github: { team: 'mcp-apps-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + // No Discord channel/role yet: #mcp-apps-sdk-dev in the future + }, + { + id: ROLE_IDS.PHP_SDK, + description: 'Official PHP SDK maintainers', + github: { team: 'php-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'php sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.PYTHON_SDK, + description: 'Official Python SDK maintainers', + github: { team: 'python-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'python sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.PYTHON_SDK_AUTH, + description: 'Python SDK auth code owners', + github: { team: 'python-sdk-auth', parent: ROLE_IDS.PYTHON_SDK }, + // GitHub only - for CODEOWNERS + }, + { + id: ROLE_IDS.RUBY_SDK, + description: 'Official Ruby SDK maintainers', + github: { team: 'ruby-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'ruby sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.RUST_SDK, + description: 'Official Rust SDK maintainers', + github: { team: 'rust-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'rust sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.SWIFT_SDK, + description: 'Official Swift SDK maintainers', + github: { team: 'swift-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'swift sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.TYPESCRIPT_SDK, + description: 'Official TypeScript SDK', + github: { team: 'typescript-sdk', parent: ROLE_IDS.SDK_MAINTAINERS }, + discord: { role: 'typescript sdk maintainers (synced)' }, + }, + { + id: ROLE_IDS.TYPESCRIPT_SDK_AUTH, + description: 'Code owners for auth in Typescript SDK', + github: { team: 'typescript-sdk-auth', parent: ROLE_IDS.TYPESCRIPT_SDK }, + // GitHub only - for CODEOWNERS + }, + { + id: ROLE_IDS.TYPESCRIPT_SDK_COLLABORATORS, + description: 'TypeScript SDK collaborators', + github: { team: 'typescript-sdk-collaborators', parent: ROLE_IDS.TYPESCRIPT_SDK }, + // GitHub only + }, + + // =================== + // Working Groups + // =================== + { + id: ROLE_IDS.WORKING_GROUPS, + description: 'MCP Working Groups', + github: { team: 'working-groups', parent: ROLE_IDS.STEERING_COMMITTEE }, + // No discord - organizational container + }, + { + id: ROLE_IDS.AUTH_MAINTAINERS, + description: 'Auth Maintainers', + github: { team: 'auth-maintainers', parent: ROLE_IDS.WORKING_GROUPS }, + // See AUTH_IG for Discord role + }, + { + id: ROLE_IDS.SECURITY_WG, + description: 'Security Working Group', + github: { team: 'security-wg', parent: ROLE_IDS.WORKING_GROUPS }, + // See interest group for Discord role + }, + { + id: ROLE_IDS.SERVER_IDENTITY_WG, + description: 'Server Identity Working Group', + discord: { role: 'server identity working group (synced)' }, + // Discord only for now + }, + { + id: ROLE_IDS.TRANSPORT_WG, + description: 'Transport Working Group', + github: { team: 'transport-wg', parent: ROLE_IDS.WORKING_GROUPS }, + discord: { role: 'transports working group (synced)' }, + }, + { + id: ROLE_IDS.TRIGGERS_EVENTS_WG, + description: 'Triggers & Events Working Group', + github: { team: 'triggers-events-wg', parent: ROLE_IDS.WORKING_GROUPS }, + discord: { role: 'triggers & events working group (synced)' }, + }, + { + id: ROLE_IDS.MCP_APPS_WG, + description: 'MCP Apps Working Group', + github: { team: 'mcp-apps-wg', parent: ROLE_IDS.WORKING_GROUPS }, + discord: { role: 'mcp apps working group (synced)' }, + }, + { + id: ROLE_IDS.SERVER_CARD_WG, + description: 'Server Card Working Group', + github: { team: 'server-card-wg', parent: ROLE_IDS.WORKING_GROUPS }, + discord: { role: 'server card working group (synced)' }, + }, + { + id: ROLE_IDS.INTERCEPTORS_WG, + description: 'Interceptors Working Group', + github: { team: 'interceptors-wg', parent: ROLE_IDS.WORKING_GROUPS }, + discord: { role: 'interceptors working group (synced)' }, + }, + { + id: ROLE_IDS.FILE_UPLOADS_WG, + description: 'File Uploads Working Group', + github: { team: 'file-uploads-wg', parent: ROLE_IDS.WORKING_GROUPS }, + discord: { role: 'file uploads working group (synced)' }, + }, + { + id: ROLE_IDS.AGENTS_WG, + description: 'Agents Working Group', + github: { team: 'agents-wg', parent: ROLE_IDS.WORKING_GROUPS }, + }, + + // =================== + // Interest Groups + // =================== + { + id: ROLE_IDS.INTEREST_GROUPS, + description: 'Interest Groups', + github: { team: 'interest-groups', parent: ROLE_IDS.STEERING_COMMITTEE }, + // No discord - organizational container + }, + { + id: ROLE_IDS.AGENTS_IG, + description: 'Agents Interest Group', + discord: { role: 'agents interest group (synced)' }, + // Discord only + }, + { + id: ROLE_IDS.AUTH_IG, + description: 'Auth Interest Group', + discord: { role: 'auth interest group (synced)' }, + // Discord only - separate from AUTH_MAINTAINERS which is GitHub + }, + { + id: ROLE_IDS.CLIENT_IMPLEMENTOR_IG, + description: 'Client Implementor Interest Group', + discord: { role: 'client implementor interest group (synced)' }, + // Discord only + }, + { + id: ROLE_IDS.FINANCIAL_SERVICES_IG, + description: 'Financial Services Interest Group', + github: { team: 'ig-financial-services', parent: ROLE_IDS.INTEREST_GROUPS }, + discord: { role: 'financial services interest group (synced)' }, + }, + { + id: ROLE_IDS.GATEWAYS_IG, + description: 'Gateways Interest Group', + // No GitHub role yet + discord: { role: 'gateways interest group (synced)' }, + }, + { + id: ROLE_IDS.PRIMITIVE_GROUPING_IG, + description: 'Primitive Grouping Interest Group', + github: { team: 'primitive-grouping-ig', parent: ROLE_IDS.INTEREST_GROUPS }, + discord: { role: 'primitive grouping interest group (synced)' }, + }, + { + id: ROLE_IDS.SKILLS_OVER_MCP_IG, + description: 'Skills Over MCP Interest Group', + github: { team: 'skills-over-mcp-ig', parent: ROLE_IDS.INTEREST_GROUPS }, + discord: { role: 'skills over mcp interest group (synced)' }, + }, + { + id: ROLE_IDS.TOOL_ANNOTATIONS_IG, + description: 'Tool Annotations Interest Group', + github: { team: 'tool-annotations-ig', parent: ROLE_IDS.INTEREST_GROUPS }, + discord: { role: 'tool annotations interest group (synced)' }, + }, + + // =================== + // WG/IG Facilitators (Discord only) + // =================== + { + id: ROLE_IDS.WG_IG_FACILITATORS, + description: 'Working Group and Interest Group facilitators with calendar access', + discord: { role: 'wg/ig facilitators (synced)' }, + // Discord only - grants meet.modelcontextprotocol.io calendar access + }, + + // =================== + // Email Groups (Google only) + // =================== + { + id: ROLE_IDS.ANTITRUST, + description: 'Antitrust compliance contacts', + google: { group: 'antitrust', isEmailGroup: true }, + // Google only + }, + { + id: ROLE_IDS.APPEALS, + description: 'Code of Conduct ban appeals inbox', + google: { group: 'appeals', isEmailGroup: true }, + // Google only + }, + { + id: ROLE_IDS.CATCH_ALL, + description: 'Catch-all email group', + google: { group: 'catch-all', isEmailGroup: true }, + // Google only + }, +] as const; + +/** + * Get a role by ID + */ +export function getRole(id: RoleId): Role | undefined { + return ROLES.find((r) => r.id === id); +} + +/** + * Get all roles that exist on a specific platform + */ +export function getRolesForPlatform(platform: 'github' | 'discord' | 'google'): Role[] { + return ROLES.filter((r) => r[platform] !== undefined); +} + +/** + * Build a lookup map of roles by ID + */ +export function buildRoleLookup(): Map { + return new Map(ROLES.map((r) => [r.id, r])); +} diff --git a/src/config/users.ts b/src/config/users.ts index 7eb78c4..7d7970e 100644 --- a/src/config/users.ts +++ b/src/config/users.ts @@ -1,25 +1,828 @@ import type { Member } from './utils'; +import { ROLE_IDS } from './roleIds'; export const MEMBERS: readonly Member[] = [ + { + github: '000-000-000-000-000', + discord: '1360717264051241071', + firstName: 'Nick', + lastName: 'Aldridge', + googleEmailPrefix: 'nick', + memberOf: [ROLE_IDS.CORE_MAINTAINERS], + }, + { + github: 'a-akimov', + discord: '1365254196621738116', + memberOf: [ROLE_IDS.DOCS_MAINTAINERS], + }, + { + github: 'aaronpk', + discord: '324624369428987905', + memberOf: [ROLE_IDS.AUTH_MAINTAINERS, ROLE_IDS.MAINTAINERS], + }, + { + github: 'alexhancock', + discord: '1325885093343924316', + memberOf: [ROLE_IDS.RUST_SDK], + }, + { + github: 'an-dustin', + memberOf: [ROLE_IDS.SECURITY_WG], + }, + { + github: 'antonpk1', + discord: '738474760480227358', + memberOf: [ROLE_IDS.MCP_APPS_SDK], + }, + { + github: 'asklar', + discord: '633837375734153216', + memberOf: [ROLE_IDS.MCPB_MAINTAINERS], + }, + { + github: 'atesgoral', + discord: '201179934775836672', + memberOf: [ROLE_IDS.RUBY_SDK], + }, + { + github: 'baxen', + discord: '360224027769307136', + memberOf: [ROLE_IDS.RUST_SDK], + }, + { + github: 'bhosmer-ant', + discord: '1272295077074567242', + memberOf: [ + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.MODERATORS, + ROLE_IDS.PYTHON_SDK, + ROLE_IDS.TRANSPORT_WG, + ROLE_IDS.TYPESCRIPT_SDK, + ], + }, + { + github: 'BobDickinson', + email: 'bob.dickinson@gmail.com', + discord: '1175893001202045139', + skipGoogleUserProvisioning: true, + memberOf: [ + ROLE_IDS.MAINTAINERS, + ROLE_IDS.INSPECTOR_MAINTAINERS, + ROLE_IDS.REGISTRY_MAINTAINERS, + ROLE_IDS.SKILLS_OVER_MCP_IG, + ], + }, + { + github: 'bolinfest', + memberOf: [ROLE_IDS.RUST_SDK], + }, + { + github: 'caitiem20', + email: 'caitie.mccaffrey@microsoft.com', + discord: '1425586366288494722', + firstName: 'Caitie', + lastName: 'McCaffrey', + googleEmailPrefix: 'caitie', + memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.TRANSPORT_WG, ROLE_IDS.AGENTS_WG], + }, + { + github: 'caseychow-oai', + memberOf: [ROLE_IDS.FILE_UPLOADS_WG], + }, + { + github: 'chemicL', + discord: '1346243721271971923', + memberOf: [ROLE_IDS.JAVA_SDK], + }, + { + github: 'chr-hertel', + email: 'mail@christopher-hertel.de', + discord: '633566986827464704', + memberOf: [ROLE_IDS.PHP_SDK], + }, + { + github: 'chughtapan', + email: 'chugh.tapan@gmail.com', + discord: '941245973357793340', + memberOf: [ + ROLE_IDS.MAINTAINERS, + ROLE_IDS.INTEREST_GROUPS, + ROLE_IDS.PRIMITIVE_GROUPING_IG, + ROLE_IDS.WG_IG_FACILITATORS, + ], + }, + { + github: 'clareliguori', + email: 'liguori@amazon.com', + discord: '1109135863843143700', + firstName: 'Clare', + lastName: 'Liguori', + googleEmailPrefix: 'clare', + memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.TRIGGERS_EVENTS_WG], + }, + { + github: 'cliffhall', + email: 'cliff@futurescale.com', + discord: '501498061965754380', + firstName: 'Cliff', + lastName: 'Hall', + googleEmailPrefix: 'cliff', + existingGWSUser: true, + memberOf: [ + ROLE_IDS.COMMUNITY_MANAGERS, + ROLE_IDS.MAINTAINERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.INSPECTOR_MAINTAINERS, + ROLE_IDS.PRIMITIVE_GROUPING_IG, + ROLE_IDS.REFERENCE_SERVERS_MAINTAINERS, + ROLE_IDS.MODERATORS, + ROLE_IDS.SKILLS_OVER_MCP_IG, + ROLE_IDS.WORKING_GROUPS, + ROLE_IDS.APPEALS, + ], + }, + { + github: 'CodeWithKyrian', + discord: '951883230250946633', + memberOf: [ROLE_IDS.PHP_SDK], + }, + { + github: 'crondinini-ant', + memberOf: [], + }, + { + github: 'D-McAdams', + discord: '1364696680980545697', + memberOf: [ROLE_IDS.AUTH_MAINTAINERS], + }, + { + github: 'daleseo', + discord: '267646459187298305', + memberOf: [ROLE_IDS.RUST_SDK], + }, + { + github: 'davidortinau', + firstName: 'David', + lastName: 'Ortinau', + discord: '572162392876646401', + memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN], + }, + { + github: 'degiorgio', + email: 'degiorgiokurt@outlook.com', + firstName: 'Kurt', + lastName: 'Degiorgio', + discord: '602175181133316105', + memberOf: [ROLE_IDS.INTERCEPTORS_WG], + }, + { + github: 'dend', + skipGoogleUserProvisioning: true, + memberOf: [ + ROLE_IDS.AUTH_MAINTAINERS, + ROLE_IDS.CORE_MAINTAINERS, + ROLE_IDS.LEAD_MAINTAINERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.CSHARP_SDK_ADMIN, + ROLE_IDS.ADMINISTRATORS, + ROLE_IDS.GO_SDK, + ROLE_IDS.FINANCIAL_SERVICES_IG, + ROLE_IDS.MODERATORS, + ROLE_IDS.PHP_SDK, + ROLE_IDS.PYTHON_SDK, + ROLE_IDS.SECURITY_WG, + ROLE_IDS.TRANSPORT_WG, + ROLE_IDS.TYPESCRIPT_SDK, + ], + }, + { + github: 'devcrocod', + memberOf: [ROLE_IDS.KOTLIN_SDK], + }, { github: 'domdomegg', email: 'adam@modelcontextprotocol.io', - memberOf: ['test-child'], + discord: '102128241715716096', + firstName: 'Adam', + lastName: 'Jones', + googleEmailPrefix: 'adam', + existingGWSUser: true, + memberOf: [ROLE_IDS.MCPB_MAINTAINERS], + }, + { + github: 'dsp', + skipGoogleUserProvisioning: true, + memberOf: [ + ROLE_IDS.AUTH_MAINTAINERS, + ROLE_IDS.LEAD_MAINTAINERS, + ROLE_IDS.CORE_MAINTAINERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.GO_SDK, + ROLE_IDS.FINANCIAL_SERVICES_IG, + ROLE_IDS.MODERATORS, + ROLE_IDS.PHP_SDK, + ROLE_IDS.PYTHON_SDK, + ROLE_IDS.SECURITY_WG, + ROLE_IDS.TRANSPORT_WG, + ROLE_IDS.TYPESCRIPT_SDK, + ], + }, + { + github: 'dsp-ant', + email: 'david@modelcontextprotocol.io', + discord: '166107790262272000', + firstName: 'David', + lastName: 'Soria Parra', + googleEmailPrefix: 'david', + existingGWSUser: true, + memberOf: [ + ROLE_IDS.AUTH_MAINTAINERS, + ROLE_IDS.LEAD_MAINTAINERS, + ROLE_IDS.CORE_MAINTAINERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.MODERATORS, + ROLE_IDS.SERVER_CARD_WG, + ROLE_IDS.AGENTS_WG, + ROLE_IDS.APPEALS, + ], + }, + { + github: 'e5l', + memberOf: [ROLE_IDS.KOTLIN_SDK], + }, + { + github: 'eiriktsarpalis', + memberOf: [ROLE_IDS.CSHARP_SDK], + }, + { + github: 'EmLauber', + discord: '1408222390361657426', + memberOf: [ROLE_IDS.WG_IG_FACILITATORS], + }, + { + github: 'erain', + discord: '797226095874539539', + memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], + }, + { + github: 'ericstj', + memberOf: [ROLE_IDS.CSHARP_SDK], + }, + { + github: 'evalstate', + discord: '779268016121577492', + firstName: 'Shaun', + lastName: 'Smith', + googleEmailPrefix: 'shaun.smith', + memberOf: [ + ROLE_IDS.COMMUNITY_MANAGERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.MAINTAINERS, + ROLE_IDS.MODERATORS, + ROLE_IDS.TRANSPORT_WG, + ROLE_IDS.APPEALS, + ], + }, + { + github: 'fabpot', + memberOf: [ROLE_IDS.PHP_SDK], + }, + { + github: 'felixrieseberg', + memberOf: [ROLE_IDS.MCPB_MAINTAINERS], + }, + { + github: 'felixweinberger', + discord: '1377138523492057212', + firstName: 'Felix', + lastName: 'Weinberger', + googleEmailPrefix: 'felix', + memberOf: [ + ROLE_IDS.MAINTAINERS, + ROLE_IDS.PYTHON_SDK, + ROLE_IDS.SECURITY_WG, + ROLE_IDS.TYPESCRIPT_SDK, + ], + }, + { + github: 'findleyr', + discord: '776094836796424213', + memberOf: [ROLE_IDS.GO_SDK], + }, + { + github: 'guglielmo-san', + discord: '1432786987072622613', + memberOf: [ROLE_IDS.GO_SDK], + }, + { + github: 'halter73', + discord: '340718902096953344', + memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN], + }, + { + github: 'herczyn', + discord: '1001427188068917279', + memberOf: [ROLE_IDS.GO_SDK], + }, + { + github: 'idosal', + discord: '593070927202484244', + memberOf: [ROLE_IDS.WORKING_GROUPS, ROLE_IDS.MCP_APPS_WG, ROLE_IDS.MCP_APPS_SDK], + }, + { + github: 'ignatov', + memberOf: [ROLE_IDS.KOTLIN_SDK], + }, + { + github: 'ihrpr', + memberOf: [ROLE_IDS.DOCS_MAINTAINERS, ROLE_IDS.PYTHON_SDK, ROLE_IDS.TYPESCRIPT_SDK], + }, + { + github: 'jamadeo', + memberOf: [ROLE_IDS.RUST_SDK], + }, + { + github: 'JAORMX', + discord: '1185152774674055193', + memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], + }, + { + github: 'jba', + discord: '773276903364755518', + memberOf: [ROLE_IDS.GO_SDK], + }, + { + github: 'jeffhandley', + memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN], + }, + { + github: 'jenn-newton', + memberOf: [ROLE_IDS.SECURITY_WG], + }, + { + github: 'jeongukjae', + discord: '334348926658412564', + memberOf: [ROLE_IDS.INTERCEPTORS_WG], + }, + { + github: 'joan-anthropic', + discord: '1398403578892128437', + memberOf: [ROLE_IDS.MCPB_MAINTAINERS], + }, + { + github: 'jokemanfire', + memberOf: [ROLE_IDS.RUST_SDK], + }, + { + github: 'jonathanhefner', + discord: '1301960963087663186', + memberOf: [ + ROLE_IDS.COMMUNITY_MANAGERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.MAINTAINERS, + ROLE_IDS.MODERATORS, + ROLE_IDS.RUBY_SDK, + ROLE_IDS.MCP_APPS_SDK, + ROLE_IDS.TRANSPORT_WG, + ROLE_IDS.APPEALS, + ], + }, + { + github: 'jozkee', + memberOf: [ROLE_IDS.CSHARP_SDK], + }, + { + github: 'jspahrsummers', + email: 'justin@modelcontextprotocol.io', + firstName: 'Justin', + lastName: 'Spahr-Summers', + googleEmailPrefix: 'justin', + existingGWSUser: true, + memberOf: [ROLE_IDS.LEAD_MAINTAINERS, ROLE_IDS.CORE_MAINTAINERS], + }, + { + github: 'kaxil', + discord: '757355088946921474', + memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], + }, + { + github: 'Kehrlann', + discord: '1112624611901837373', + memberOf: [ROLE_IDS.JAVA_SDK], + }, + { + github: 'keithagroves', + discord: '321019863260987392', + memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], + }, + { + github: 'KKonstantinov', + discord: '390932438903422987', + memberOf: [ROLE_IDS.INSPECTOR_MAINTAINERS, ROLE_IDS.MAINTAINERS, ROLE_IDS.TYPESCRIPT_SDK], + firstName: 'Konstantin', + lastName: 'Konstantinov', + googleEmailPrefix: 'konstantin', + }, + { + github: 'Kludex', + discord: '247021664624312322', + memberOf: [ROLE_IDS.PYTHON_SDK], + }, + { + github: 'koic', + discord: '880937364208361483', + memberOf: [ROLE_IDS.RUBY_SDK], + }, + { + github: 'kurtisvg', + discord: '1158458388917780590', + firstName: 'Kurtis', + lastName: 'Van Gent', + googleEmailPrefix: 'kvg', + memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.TRANSPORT_WG], + }, + { + github: 'liady', + discord: '383565833768665088', + memberOf: [ROLE_IDS.WORKING_GROUPS, ROLE_IDS.MCP_APPS_WG, ROLE_IDS.MCP_APPS_SDK], + }, + { + github: 'localden', + discord: '1351224014143754260', + firstName: 'Den', + lastName: 'Delimarsky', + googleEmailPrefix: 'den', + existingGWSUser: true, + memberOf: [ + ROLE_IDS.AUTH_MAINTAINERS, + ROLE_IDS.CORE_MAINTAINERS, + ROLE_IDS.LEAD_MAINTAINERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.CSHARP_SDK_ADMIN, + ROLE_IDS.ADMINISTRATORS, + ROLE_IDS.FILE_UPLOADS_WG, + ROLE_IDS.GO_SDK, + ROLE_IDS.FINANCIAL_SERVICES_IG, + ROLE_IDS.MODERATORS, + ROLE_IDS.PHP_SDK, + ROLE_IDS.PYTHON_SDK, + ROLE_IDS.SECURITY_WG, + ROLE_IDS.TRANSPORT_WG, + ROLE_IDS.TYPESCRIPT_SDK, + ROLE_IDS.APPEALS, + ], + }, + { + github: 'LucaButBoring', + discord: '1366470072729866252', + firstName: 'Luca', + lastName: 'Chang', + googleEmailPrefix: 'luca', + memberOf: [ + ROLE_IDS.MAINTAINERS, + ROLE_IDS.AGENTS_IG, + ROLE_IDS.AGENTS_WG, + ROLE_IDS.WORKING_GROUPS, + ], + }, + { + github: 'maciej-kisiel', + discord: '936242781733654588', + memberOf: [ROLE_IDS.GO_SDK], + }, + { + github: 'macoughl', + discord: '740279257548193803', + memberOf: [ROLE_IDS.COMMUNITY_MANAGERS], + }, + { + github: 'markdroth', + firstName: 'Mark', + lastName: 'Roth', + memberOf: [ROLE_IDS.TRANSPORT_WG], + }, + { + github: 'markpollack', + memberOf: [ROLE_IDS.JAVA_SDK], + }, + { + github: 'marshallofsound', + memberOf: [ROLE_IDS.MCPB_MAINTAINERS], + }, + { + github: 'mattzcarey', + discord: '224878268275359744', + memberOf: [ROLE_IDS.TYPESCRIPT_SDK, ROLE_IDS.TOOL_ANNOTATIONS_IG, ROLE_IDS.WG_IG_FACILITATORS], + }, + { + github: 'maxisbey', + discord: '1404871241738748058', + firstName: 'Max', + lastName: 'Isbey', + googleEmailPrefix: 'max', + memberOf: [ROLE_IDS.MAINTAINERS, ROLE_IDS.PYTHON_SDK, ROLE_IDS.TYPESCRIPT_SDK_COLLABORATORS], + }, + { + github: 'michaelneale', + memberOf: [ROLE_IDS.RUST_SDK], + }, + { + github: 'mikekistler', + discord: '915345005982408754', + memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN, ROLE_IDS.TRANSPORT_WG], + }, + { + github: 'movetz', + discord: '1427569183427919906', + memberOf: [ROLE_IDS.SWIFT_SDK], + }, + { + github: 'nahapetyan-serob', + discord: '1505852630692401314', + memberOf: [ROLE_IDS.GO_SDK], + }, + { + github: 'nbarbettini', + discord: '784552628930478090', + memberOf: [ROLE_IDS.WG_IG_FACILITATORS], + }, + { + github: 'nickcoai', + discord: '1153783469860732968', + firstName: 'Nick', + lastName: 'Cooper', + googleEmailPrefix: 'nickc', + memberOf: [ROLE_IDS.CORE_MAINTAINERS, ROLE_IDS.FILE_UPLOADS_WG, ROLE_IDS.SERVER_IDENTITY_WG], + }, + { + github: 'nicolas-grekas', + memberOf: [ROLE_IDS.PHP_SDK], + }, + { + github: 'Nyholm', + discord: '466593085984342016', + memberOf: [ROLE_IDS.PHP_SDK], + }, + { + github: 'ochafik', + discord: '1004897332069925024', + memberOf: [ + ROLE_IDS.FILE_UPLOADS_WG, + ROLE_IDS.MCP_APPS_SDK, + ROLE_IDS.PYTHON_SDK, + ROLE_IDS.PYTHON_SDK_AUTH, + ROLE_IDS.TYPESCRIPT_SDK, + ROLE_IDS.TYPESCRIPT_SDK_AUTH, + ], + }, + { + github: 'og-ant', + memberOf: [ROLE_IDS.SECURITY_WG], + }, + { + github: 'olaservo', + discord: '1079841769946095620', + firstName: 'Ola', + lastName: 'Hungerford', + googleEmailPrefix: 'ola', + memberOf: [ + ROLE_IDS.COMMUNITY_MANAGERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.INSPECTOR_MAINTAINERS, + ROLE_IDS.INTERCEPTORS_WG, + ROLE_IDS.MAINTAINERS, + ROLE_IDS.MODERATORS, + ROLE_IDS.REFERENCE_SERVERS_MAINTAINERS, + ROLE_IDS.SKILLS_OVER_MCP_IG, + ROLE_IDS.WORKING_GROUPS, + ROLE_IDS.APPEALS, + ], + }, + { + github: 'Ololoshechkin', + memberOf: [ROLE_IDS.KOTLIN_SDK], + }, + { + github: 'pcarleton', + discord: '1354465170969067852', + firstName: 'Paul', + lastName: 'Carleton', + googleEmailPrefix: 'paul', + existingGWSUser: true, + memberOf: [ + ROLE_IDS.CORE_MAINTAINERS, + ROLE_IDS.DOCS_MAINTAINERS, + ROLE_IDS.ADMINISTRATORS, + ROLE_IDS.MODERATORS, + ROLE_IDS.PYTHON_SDK, + ROLE_IDS.PYTHON_SDK_AUTH, + ROLE_IDS.TYPESCRIPT_SDK, + ROLE_IDS.TYPESCRIPT_SDK_AUTH, + ROLE_IDS.AUTH_MAINTAINERS, + ], + }, + { + github: 'pederhp', + discord: '166255967665651713', + memberOf: [ + ROLE_IDS.COMMUNITY_MANAGERS, + ROLE_IDS.MAINTAINERS, + ROLE_IDS.FINANCIAL_SERVICES_IG, + ROLE_IDS.MODERATORS, + ROLE_IDS.SKILLS_OVER_MCP_IG, + ROLE_IDS.INTERCEPTORS_WG, + ROLE_IDS.APPEALS, + ], + }, + { + github: 'petery-ant', + memberOf: [ROLE_IDS.SECURITY_WG], + }, + { + github: 'pja-ant', + discord: '328628782497923072', + firstName: 'Peter', + lastName: 'Alexander', + googleEmailPrefix: 'pja', + existingGWSUser: true, + memberOf: [ + ROLE_IDS.CORE_MAINTAINERS, + ROLE_IDS.MAINTAINERS, + ROLE_IDS.SKILLS_OVER_MCP_IG, + ROLE_IDS.TRANSPORT_WG, + ROLE_IDS.TRIGGERS_EVENTS_WG, + ROLE_IDS.AGENTS_WG, + ], + }, + { + github: 'poteat', + memberOf: [ROLE_IDS.TYPESCRIPT_SDK_COLLABORATORS], + }, + { + github: 'PranavSenthilnathan', + email: 'pranas@microsoft.com', + firstName: 'Pranav', + lastName: 'Senthilnathan', + memberOf: [ROLE_IDS.CSHARP_SDK], + }, + { + github: 'pree-dew', + discord: '1379733751315173376', + firstName: 'Preeti', + lastName: 'Dewani', + memberOf: [ROLE_IDS.REGISTRY_COLLABORATORS], + }, + { + github: 'pronskiy', + memberOf: [ROLE_IDS.PHP_SDK], + }, + { + github: 'pwwpche', + discord: '1226238847013228604', + memberOf: [], + }, + { + github: 'rdimitrov', + email: 'radoslav@modelcontextprotocol.io', + discord: '1088231882979815424', + firstName: 'Radoslav', + lastName: 'Dimitrov', + googleEmailPrefix: 'radoslav', + existingGWSUser: true, + memberOf: [ROLE_IDS.MAINTAINERS, ROLE_IDS.REGISTRY_MAINTAINERS, ROLE_IDS.SKILLS_OVER_MCP_IG], + }, + { + github: 'rreichel3', + discord: '1458485333757788273', + memberOf: [ROLE_IDS.TOOL_ANNOTATIONS_IG, ROLE_IDS.WG_IG_FACILITATORS], + }, + { + github: 'sambhav', + email: 'sambhavs.email@gmail.com', + firstName: 'Sambhav', + lastName: 'Kothari', + googleEmailPrefix: 'sambhav', + discord: '840109459212206090', + memberOf: [ + ROLE_IDS.MAINTAINERS, + ROLE_IDS.FINANCIAL_SERVICES_IG, + ROLE_IDS.INTERCEPTORS_WG, + ROLE_IDS.SKILLS_OVER_MCP_IG, + ], + }, + { + github: 'SamMorrowDrums', + email: 'sammorrowdrums@github.com', + discord: '782948163694493696', + firstName: 'Sam', + lastName: 'Morrow', + googleEmailPrefix: 'sam', + memberOf: [ + ROLE_IDS.MAINTAINERS, + ROLE_IDS.PRIMITIVE_GROUPING_IG, + ROLE_IDS.SERVER_CARD_WG, + ROLE_IDS.SKILLS_OVER_MCP_IG, + ROLE_IDS.TOOL_ANNOTATIONS_IG, + ROLE_IDS.WG_IG_FACILITATORS, + ], + }, + { + github: 'sdubov', + memberOf: [ROLE_IDS.KOTLIN_SDK], + }, + { + github: 'soyuka', + email: 'soyuka@gmail.com', + discord: '249323948842418186', + memberOf: [ROLE_IDS.PHP_SDK], + }, + { + github: 'stallent', + discord: '1137898074086314136', + memberOf: [ROLE_IDS.SWIFT_SDK], + }, + { + github: 'stephentoub', + memberOf: [ROLE_IDS.CSHARP_SDK, ROLE_IDS.CSHARP_SDK_ADMIN], + }, + { + github: 'sunishsheth2009', + discord: '1414713222224941097', + memberOf: [ROLE_IDS.SKILLS_OVER_MCP_IG], + }, + { + github: 'tadasant', + email: 'tadas@modelcontextprotocol.io', + discord: '400092503677599754', + firstName: 'Tadas', + lastName: 'Antanavicius', + googleEmailPrefix: 'tadas', + existingGWSUser: true, + memberOf: [ + ROLE_IDS.COMMUNITY_MANAGERS, + ROLE_IDS.MODERATORS, + ROLE_IDS.MAINTAINERS, + ROLE_IDS.WORKING_GROUPS, + ROLE_IDS.INTEREST_GROUPS, + ROLE_IDS.REGISTRY_MAINTAINERS, + ROLE_IDS.ADMINISTRATORS, + ROLE_IDS.SERVER_CARD_WG, + ROLE_IDS.APPEALS, + ], + }, + { + github: 'tarekgh', + memberOf: [ROLE_IDS.CSHARP_SDK], + }, + { + github: 'tiginamaria', + memberOf: [ROLE_IDS.KOTLIN_SDK], + }, + { + github: 'tobinsouth', + discord: '865072069779521556', + memberOf: [ROLE_IDS.REFERENCE_SERVERS_MAINTAINERS], + }, + { + github: 'toby', + email: 'toby@modelcontextprotocol.io', + discord: '560155411777323048', + firstName: 'Toby', + lastName: 'Padilla', + googleEmailPrefix: 'toby', + existingGWSUser: true, + // Emeritus maintainer of the Registry + memberOf: [], + }, + { + github: 'topherbullock', + discord: '1059910719124013168', + memberOf: [ROLE_IDS.RUBY_SDK], + }, + { + github: 'tzolov', + discord: '1097924660055777290', + memberOf: [ROLE_IDS.DOCS_MAINTAINERS, ROLE_IDS.JAVA_SDK], + }, + { + github: 'yarolegovich', + discord: '393296640141950977', + memberOf: [ROLE_IDS.GO_SDK], }, { email: 'adamj@anthropic.com', - memberOf: ['catch-all'], + memberOf: [ROLE_IDS.CATCH_ALL], }, { email: 'davidsp@anthropic.com', - memberOf: ['antitrust'], + memberOf: [ROLE_IDS.ANTITRUST], }, { email: 'mattsamuels@anthropic.com', - memberOf: ['antitrust'], + memberOf: [ROLE_IDS.ANTITRUST], }, { email: 'davideramian@anthropic.com', - memberOf: ['antitrust'], + memberOf: [ROLE_IDS.ANTITRUST], }, -] as const; \ No newline at end of file +] as const; diff --git a/src/config/utils.ts b/src/config/utils.ts index 241b86f..6c84daf 100644 --- a/src/config/utils.ts +++ b/src/config/utils.ts @@ -1,43 +1,81 @@ -import type { GROUPS } from './groups'; +import type { RoleId } from './roleIds'; +import type { Role } from './roles'; -function isValidGroupName(name: string): boolean { - return /^[a-z][a-z0-9-]*[a-z]$/.test(name); +/** + * A member of the MCP organization. + * Members are assigned to roles via memberOf, and the role definitions + * determine which platforms (GitHub, Discord, Google) they get access to. + */ +export interface Member { + /** GitHub username */ + github?: string; + /** Email address (for Google Workspace) */ + email?: string; + /** Discord user ID (snowflake) */ + discord?: string; + /** Roles this member belongs to */ + memberOf: readonly RoleId[]; + /** First name (required for Google Workspace user provisioning) */ + firstName?: string; + /** Last name (required for Google Workspace user provisioning) */ + lastName?: string; + /** Google Workspace email prefix (e.g., 'david' -> david@modelcontextprotocol.io) */ + googleEmailPrefix?: string; + /** If true, this user already exists in Google Workspace and should be imported into Pulumi state */ + existingGWSUser?: boolean; + /** Explicitly skip automatic GWS user provisioning for provisionUser roles */ + skipGoogleUserProvisioning?: boolean; } -export type Platform = 'github' | 'google'; - -export function defineGroups< - const T extends readonly { - name: Lowercase; - description: string; - memberOf?: readonly (T[number]['name'])[]; - isEmailGroup?: boolean; - onlyOnPlatforms?: readonly Platform[]; - }[] ->(groups: T) { - for (const group of groups) { - if (!isValidGroupName(group.name)) { - throw new Error( - `Invalid group name: ${group.name}. Must be lowercase alphanumeric with dashes, starting with a letter.` - ); +/** + * Sort roles by GitHub parent dependency (topological sort). + * Ensures parent teams are created before child teams. + * + * This is necessary because when creating GitHub teams, we need the parent + * team's ID. If we process roles in arbitrary order, a child team might be + * processed before its parent, resulting in undefined parentTeamId. + */ +export function sortRolesByGitHubDependency( + roles: readonly Role[], + roleLookup: Map +): Role[] { + const result: Role[] = []; + const visited = new Set(); + + function visit(role: Role): void { + if (visited.has(role.id)) return; + + // Only process roles with GitHub config + if (!role.github) { + visited.add(role.id); + return; } - } - return groups; -} + // Visit parent first if it exists and has GitHub config + if (role.github.parent) { + const parentRole = roleLookup.get(role.github.parent); + if (parentRole) { + visit(parentRole); + } + } + + visited.add(role.id); + result.push(role); + } -export type GroupKey = (typeof GROUPS)[number]['name']; + for (const role of roles) { + visit(role); + } -export interface Group { - name: GroupKey; - description: string; - memberOf?: readonly GroupKey[]; - isEmailGroup?: boolean; - onlyOnPlatforms?: readonly Platform[]; + return result; } -export interface Member { - github?: string; - email?: string; - memberOf: readonly GroupKey[]; -} \ No newline at end of file +// Re-export for convenience +export { ROLE_IDS, type RoleId } from './roleIds'; +export { + ROLES, + type Role, + type GitHubConfig, + type DiscordConfig, + type GoogleConfig, +} from './roles'; diff --git a/src/discord.ts b/src/discord.ts new file mode 100644 index 0000000..25b2018 --- /dev/null +++ b/src/discord.ts @@ -0,0 +1,551 @@ +import * as pulumi from '@pulumi/pulumi'; +import { ROLES, type Role, buildRoleLookup } from './config/roles'; +import { MEMBERS } from './config/users'; +import type { RoleId } from './config/roleIds'; + +const config = new pulumi.Config('discord'); +// Discord integration is optional - only enabled if botToken and guildId are configured +const DISCORD_BOT_TOKEN = config.getSecret('botToken'); +const DISCORD_GUILD_ID = config.get('guildId'); +const DISCORD_ENABLED = DISCORD_BOT_TOKEN !== undefined && DISCORD_GUILD_ID !== undefined; + +if (!DISCORD_ENABLED) { + pulumi.log.info('Discord integration disabled: botToken or guildId not configured'); +} + +const DISCORD_API_BASE = 'https://discord.com/api/v10'; + +interface DiscordApiError { + code: number; + message: string; +} + +interface DiscordRateLimitResponse { + message: string; + retry_after: number; + global: boolean; +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// Cloudflare 5xx and edge-level 429s return plain-text bodies ("upstream connect +// error...", "error code: 1015") that crash a naive response.json(). +function tryParseJson(text: string): T | undefined { + try { + return JSON.parse(text) as T; + } catch { + return undefined; + } +} + +async function discordFetch( + token: string, + endpoint: string, + options: RequestInit = {}, + maxRetries = 10 +): Promise { + let lastError: Error | undefined; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + const response = await fetch(`${DISCORD_API_BASE}${endpoint}`, { + ...options, + headers: { + Authorization: `Bot ${token}`, + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + + if (response.status === 429) { + const text = await response.text(); + const body = tryParseJson(text); + const retryAfterSec = body?.retry_after ?? 1; + // Linearly increasing jitter de-syncs the thundering herd when many + // resources refresh in parallel and all receive the same retry_after. + const jitterMs = Math.random() * 1000 * (attempt + 1); + const retryAfterMs = Math.ceil(retryAfterSec * 1000) + jitterMs; + lastError = new Error( + `Discord API rate limited on ${endpoint} (retry_after=${retryAfterSec}s, global=${body?.global ?? false})` + ); + if (attempt < maxRetries) { + await sleep(retryAfterMs); + continue; + } + throw lastError; + } + + if (response.status >= 500 && response.status < 600) { + const text = await response.text(); + lastError = new Error(`Discord API ${response.status} on ${endpoint}: ${text.slice(0, 200)}`); + if (attempt < maxRetries) { + await sleep(2 ** attempt * 500 + Math.random() * 1000); + continue; + } + throw lastError; + } + + if (!response.ok) { + const text = await response.text(); + const error = tryParseJson(text); + throw new Error( + error + ? `Discord API error: ${error.message} (code: ${error.code})` + : `Discord API ${response.status} on ${endpoint}: ${text.slice(0, 200)}` + ); + } + + // Handle 204 No Content + if (response.status === 204) { + return undefined as T; + } + + return response.json() as Promise; + } + + throw ( + lastError ?? new Error(`Discord API request to ${endpoint} failed after ${maxRetries} retries`) + ); +} + +// Discord API response types +interface DiscordRoleApiResponse { + id: string; + name: string; + position: number; + permissions: string; + managed: boolean; +} + +interface DiscordGuildMemberApiResponse { + roles: string[]; +} + +// Discord Role Dynamic Provider +interface DiscordRoleInputs { + guildId: string; + roleName: string; + token: string; +} + +interface DiscordRoleOutputs extends DiscordRoleInputs { + roleId: string; +} + +const discordRoleProvider: pulumi.dynamic.ResourceProvider = { + async create( + inputs: DiscordRoleInputs + ): Promise> { + const role = await discordFetch( + inputs.token, + `/guilds/${inputs.guildId}/roles`, + { + method: 'POST', + body: JSON.stringify({ + name: inputs.roleName, + permissions: '0', // No special permissions - roles are for organization only + mentionable: false, + hoist: false, + }), + } + ); + + return { + id: role.id, + outs: { + ...inputs, + roleId: role.id, + }, + }; + }, + + async read( + id: string, + props: DiscordRoleOutputs + ): Promise> { + try { + const roles = await discordFetch( + props.token, + `/guilds/${props.guildId}/roles` + ); + + const role = roles.find((r) => r.id === id); + if (!role) { + // Role was deleted externally + throw new Error(`Role ${id} not found`); + } + + return { + id, + props: { + ...props, + roleName: role.name, + roleId: role.id, + }, + }; + } catch (error) { + throw new Error(`Failed to read role ${id}: ${error}`); + } + }, + + async update( + id: string, + _olds: DiscordRoleOutputs, + news: DiscordRoleInputs + ): Promise> { + await discordFetch(news.token, `/guilds/${news.guildId}/roles/${id}`, { + method: 'PATCH', + body: JSON.stringify({ + name: news.roleName, + }), + }); + + return { + outs: { + ...news, + roleId: id, + }, + }; + }, + + async delete(id: string, props: DiscordRoleOutputs): Promise { + try { + await discordFetch(props.token, `/guilds/${props.guildId}/roles/${id}`, { + method: 'DELETE', + }); + } catch (error) { + // Ignore errors if role is already deleted + console.warn(`Failed to delete role ${id}: ${error}`); + } + }, +}; + +class DiscordRole extends pulumi.dynamic.Resource { + public readonly roleId!: pulumi.Output; + public readonly roleName!: pulumi.Output; + public readonly guildId!: pulumi.Output; + + constructor( + name: string, + args: { + guildId: pulumi.Input; + roleName: pulumi.Input; + token: pulumi.Input; + }, + opts?: pulumi.CustomResourceOptions + ) { + super( + discordRoleProvider, + name, + { + roleId: undefined, + ...args, + }, + opts + ); + } +} + +// Discord Member Role Sync Dynamic Provider +// This provider reconciles a user's roles to match exactly what's defined in config +// It adds missing roles AND removes extra roles (only for roles we manage) +interface DiscordMemberRoleSyncInputs { + guildId: string; + userId: string; + /** Role IDs that this user SHOULD have (managed roles only) */ + expectedRoleIds: string[]; + /** All role IDs that we manage (to know which ones to potentially remove) */ + managedRoleIds: string[]; + token: string; +} + +interface DiscordMemberRoleSyncOutputs extends DiscordMemberRoleSyncInputs { + /** Roles that were added during last sync */ + addedRoles: string[]; + /** Roles that were removed during last sync */ + removedRoles: string[]; + /** True if the member was not found on the Discord server */ + memberNotFound: boolean; +} + +async function syncMemberRoles( + inputs: DiscordMemberRoleSyncInputs +): Promise<{ addedRoles: string[]; removedRoles: string[]; memberNotFound: boolean }> { + // Get the user's current roles + let member: DiscordGuildMemberApiResponse; + try { + member = await discordFetch( + inputs.token, + `/guilds/${inputs.guildId}/members/${inputs.userId}` + ); + } catch (error) { + // If the member isn't on the server, skip gracefully + if (error instanceof Error && error.message.includes('code: 10007')) { + console.warn( + `Discord member ${inputs.userId} not found on server - skipping role sync. ` + + `They may have left the server or the Discord ID may be incorrect.` + ); + return { addedRoles: [], removedRoles: [], memberNotFound: true }; + } + throw error; + } + + const currentRoles = new Set(member.roles); + const expectedRoles = new Set(inputs.expectedRoleIds); + const managedRoles = new Set(inputs.managedRoleIds); + + const addedRoles: string[] = []; + const removedRoles: string[] = []; + + // Add missing roles + for (const roleId of Array.from(expectedRoles)) { + if (!currentRoles.has(roleId)) { + await discordFetch( + inputs.token, + `/guilds/${inputs.guildId}/members/${inputs.userId}/roles/${roleId}`, + { method: 'PUT' } + ); + addedRoles.push(roleId); + } + } + + // Remove roles that the user has but shouldn't (only managed roles) + for (const roleId of Array.from(currentRoles)) { + if (managedRoles.has(roleId) && !expectedRoles.has(roleId)) { + await discordFetch( + inputs.token, + `/guilds/${inputs.guildId}/members/${inputs.userId}/roles/${roleId}`, + { method: 'DELETE' } + ); + removedRoles.push(roleId); + } + } + + return { addedRoles, removedRoles, memberNotFound: false }; +} + +const discordMemberRoleSyncProvider: pulumi.dynamic.ResourceProvider = { + async create( + inputs: DiscordMemberRoleSyncInputs + ): Promise> { + const { addedRoles, removedRoles, memberNotFound } = await syncMemberRoles(inputs); + + return { + id: inputs.userId, + outs: { + ...inputs, + addedRoles, + removedRoles, + memberNotFound, + }, + }; + }, + + async read( + id: string, + props: DiscordMemberRoleSyncOutputs + ): Promise> { + let member: DiscordGuildMemberApiResponse; + try { + member = await discordFetch( + props.token, + `/guilds/${props.guildId}/members/${props.userId}` + ); + } catch (error) { + // If the member isn't on the server, return state indicating they're not found + if (error instanceof Error && error.message.includes('code: 10007')) { + return { + id, + props: { + ...props, + addedRoles: [], + removedRoles: [], + memberNotFound: true, + }, + }; + } + throw new Error(`Failed to read member roles for ${id}: ${error}`); + } + + const currentRoles = new Set(member.roles); + const expectedRoles = new Set(props.expectedRoleIds); + const managedRoles = new Set(props.managedRoleIds); + + // Check if roles are in sync (only considering managed roles) + const outOfSync = + Array.from(expectedRoles).some((r) => !currentRoles.has(r)) || + Array.from(currentRoles).some((r) => managedRoles.has(r) && !expectedRoles.has(r)); + + if (outOfSync) { + // Self-heal: apply the expected roles now. Without this, refresh would + // only observe drift, and since inputs are unchanged Pulumi would never + // trigger update() — leaving members who joined after create() stuck. + const { addedRoles, removedRoles, memberNotFound } = await syncMemberRoles(props); + return { + id, + props: { + ...props, + addedRoles, + removedRoles, + memberNotFound, + }, + }; + } + + return { id, props: { ...props, memberNotFound: false } }; + }, + + async update( + id: string, + _olds: DiscordMemberRoleSyncOutputs, + news: DiscordMemberRoleSyncInputs + ): Promise> { + const { addedRoles, removedRoles, memberNotFound } = await syncMemberRoles(news); + + return { + outs: { + ...news, + addedRoles, + removedRoles, + memberNotFound, + }, + }; + }, + + async delete(id: string, props: DiscordMemberRoleSyncOutputs): Promise { + // When a user is removed from config, remove all their managed roles + for (const roleId of props.expectedRoleIds) { + try { + await discordFetch( + props.token, + `/guilds/${props.guildId}/members/${props.userId}/roles/${roleId}`, + { method: 'DELETE' } + ); + } catch (error) { + console.warn(`Failed to remove role ${roleId} from user ${id}: ${error}`); + } + } + }, +}; + +class DiscordMemberRoleSync extends pulumi.dynamic.Resource { + public readonly addedRoles!: pulumi.Output; + public readonly removedRoles!: pulumi.Output; + public readonly memberNotFound!: pulumi.Output; + + constructor( + name: string, + args: { + guildId: pulumi.Input; + userId: pulumi.Input; + expectedRoleIds: pulumi.Input[]>; + managedRoleIds: pulumi.Input[]>; + token: pulumi.Input; + }, + opts?: pulumi.CustomResourceOptions + ) { + super( + discordMemberRoleSyncProvider, + name, + { + addedRoles: undefined, + removedRoles: undefined, + memberNotFound: undefined, + ...args, + }, + opts + ); + } +} + +const roleLookup = buildRoleLookup(); +// Discord roles keyed by Discord role name +const roles: Record = {}; + +/** + * Expand a set of role IDs to include all implied Discord roles. + * This traverses: + * 1. GitHub parent relationships (e.g., GO_SDK -> SDK_MAINTAINERS) + * 2. discordImplies relationships (e.g., SDK_MAINTAINERS -> MAINTAINERS) + */ +function expandDiscordRoles(roleIds: readonly RoleId[]): Set { + const expanded = new Set(); + const toProcess = [...roleIds]; + + while (toProcess.length > 0) { + const roleId = toProcess.pop()!; + if (expanded.has(roleId)) continue; + expanded.add(roleId); + + const role = roleLookup.get(roleId); + if (!role) continue; + + // Follow GitHub parent relationship + if (role.github?.parent) { + toProcess.push(role.github.parent); + } + + // Follow discordImplies relationships + if (role.discordImplies) { + toProcess.push(...role.discordImplies); + } + } + + return expanded; +} + +// Only create Discord resources if Discord is enabled +if (DISCORD_ENABLED) { + // These are guaranteed to be defined when DISCORD_ENABLED is true + const guildId = DISCORD_GUILD_ID!; + const botToken = DISCORD_BOT_TOKEN!; + + // Create Discord roles for roles that have Discord config + ROLES.forEach((role: Role) => { + if (!role.discord) return; + + roles[role.discord.role] = new DiscordRole(`discord-role-${role.id}`, { + guildId, + roleName: role.discord.role, + token: botToken, + }); + }); + + // Collect all managed role IDs (roles that have Discord config) + const allManagedRoleIds = ROLES.filter((r) => r.discord).map( + (r) => roles[r.discord!.role].roleId + ); + + // Sync roles for each member + MEMBERS.forEach((member) => { + if (!member.discord) return; + + // Expand roles to include parents and implied roles + const expandedRoleIds = expandDiscordRoles(member.memberOf); + + // Get the Discord role IDs this member should have + const expectedRoleIds = Array.from(expandedRoleIds) + .map((roleId: RoleId) => { + const role = roleLookup.get(roleId); + if (!role?.discord) return null; + return roles[role.discord.role].roleId; + }) + .filter((id): id is pulumi.Output => id !== null); + + // Create a sync resource for this member + new DiscordMemberRoleSync( + `discord-member-sync-${member.discord}`, + { + guildId, + userId: member.discord!, + expectedRoleIds, + managedRoleIds: allManagedRoleIds, + token: botToken, + }, + { dependsOn: Object.values(roles) } + ); + }); +} + +export { roles as discordRoles }; diff --git a/src/github.ts b/src/github.ts index 6ec40be..7076caa 100644 --- a/src/github.ts +++ b/src/github.ts @@ -1,30 +1,104 @@ +import * as pulumi from '@pulumi/pulumi'; import * as github from '@pulumi/github'; -import { GROUPS } from './config/groups'; -import type { Group } from './config/utils'; +import { ROLES, type Role, buildRoleLookup } from './config/roles'; +import { REPOSITORY_ACCESS } from './config/repoAccess'; +import { ORG_ROLE_ASSIGNMENTS } from './config/orgRoles'; +import { ORG_SETTINGS } from './config/orgSettings'; import { MEMBERS } from './config/users'; +import { sortRolesByGitHubDependency } from './config/utils'; +import type { RoleId } from './config/roleIds'; +const config = new pulumi.Config(); + +// The provider's Create for this resource is a PATCH on the existing org, so +// no import is needed; first apply writes the values below directly. +new github.OrganizationSettings( + 'org-settings', + { + ...ORG_SETTINGS, + billingEmail: config.requireSecret('githubBillingEmail'), + }, + { additionalSecretOutputs: ['billingEmail'] } +); + +const roleLookup = buildRoleLookup(); +// Teams keyed by GitHub team name (matches repoAccess.ts references) const teams: Record = {}; -GROUPS.forEach((group: Group) => { - // Skip groups that don't include github in their platforms - if (group.onlyOnPlatforms && !group.onlyOnPlatforms.includes('github')) return; +// Sort roles so parent teams are created before child teams +const sortedRoles = sortRolesByGitHubDependency(ROLES, roleLookup); + +// Create GitHub teams for roles that have GitHub config +sortedRoles.forEach((role: Role) => { + if (!role.github) return; + + // Resolve parent team ID if specified + // Parent is guaranteed to exist in `teams` due to topological sort + let parentTeamId: github.Team['id'] | undefined; + if (role.github.parent) { + const parentRole = roleLookup.get(role.github.parent); + if (parentRole?.github) { + parentTeamId = teams[parentRole.github.team]?.id; + } + } - teams[group.name] = new github.Team(group.name, { - name: group.name, - description: group.description + ' \n(Managed by github.com/modelcontextprotocol/access)', + teams[role.github.team] = new github.Team(role.github.team, { + name: role.github.team, + description: role.description + ' \n(Managed by github.com/modelcontextprotocol/access)', privacy: 'closed', - parentTeamId: group.memberOf?.[0] ? teams[group.memberOf[0]].id : undefined, + parentTeamId, }); }); +// Create team memberships MEMBERS.forEach((member) => { if (!member.github) return; - member.memberOf.forEach((teamKey) => { - new github.TeamMembership(`${member.github}-${teamKey}`, { - teamId: teams[teamKey].id, + member.memberOf.forEach((roleId: RoleId) => { + const role = roleLookup.get(roleId); + if (!role?.github) return; // Role doesn't have GitHub config + + new github.TeamMembership(`${member.github}-${role.github.team}`, { + teamId: teams[role.github.team].id, username: member.github!, role: 'member', }); }); }); + +// Assign organization-level roles to teams (grants access across all repos) +const orgRoles = github.getOrganizationRolesOutput(); +ORG_ROLE_ASSIGNMENTS.forEach((assignment) => { + const team = teams[assignment.team]; + if (!team) { + throw new Error( + `orgRoles.ts references team '${assignment.team}' which is not managed in roles.ts` + ); + } + const roleId = orgRoles.roles.apply((roles) => { + const match = roles.find((r) => r.name === assignment.role); + if (!match) throw new Error(`Organization role '${assignment.role}' not found`); + return match.roleId; + }); + new github.OrganizationRoleTeam(`orgrole-${assignment.team}-${assignment.role}`, { + teamSlug: team.slug, + roleId, + }); +}); + +// Configure repository access +REPOSITORY_ACCESS.forEach((repo) => { + new github.RepositoryCollaborators(`repo-${repo.repository}`, { + repository: repo.repository, + teams: repo.teams?.map((t) => ({ + teamId: teams[t.team]?.id, + permission: t.permission, + })), + users: repo.users?.map((u) => ({ + username: u.username, + permission: u.permission, + })), + }); +}); + +export { teams as githubTeams }; diff --git a/src/google.ts b/src/google.ts index e257ff1..78e8a5b 100644 --- a/src/google.ts +++ b/src/google.ts @@ -1,63 +1,183 @@ +import * as crypto from 'crypto'; +import * as pulumi from '@pulumi/pulumi'; import * as gworkspace from '@pulumi/googleworkspace'; -import { GROUPS } from './config/groups'; -import type { Group } from './config/utils'; +import * as random from '@pulumi/random'; +import { ROLES, type Role, buildRoleLookup } from './config/roles'; import { MEMBERS } from './config/users'; +import type { RoleId } from './config/roleIds'; +const roleLookup = buildRoleLookup(); +// Groups keyed by Google group name const groups: Record = {}; -GROUPS.forEach((group: Group) => { - // Skip groups that don't include google in their platforms - if (group.onlyOnPlatforms && !group.onlyOnPlatforms.includes('google')) return; +// Create Google groups for roles that have Google config +ROLES.forEach((role: Role) => { + if (!role.google) return; - groups[group.name] = new gworkspace.Group(group.name, { - email: `${group.name}@modelcontextprotocol.io`, - name: group.name, - description: group.description + ' \n(Managed by github.com/modelcontextprotocol/access)', + groups[role.google.group] = new gworkspace.Group(role.google.group, { + email: `${role.google.group}@modelcontextprotocol.io`, + name: role.google.group, + description: role.description + ' \n(Managed by github.com/modelcontextprotocol/access)', }); - new gworkspace.GroupSettings(group.name, { - email: groups[group.name].email, - - // Maximise visibility of group. It's visible in GitHub anyway - whoCanViewMembership: 'ALL_IN_DOMAIN_CAN_VIEW', - - // This specifies who can add/remove members. We want this to only be via this IaC. - whoCanModerateMembers: 'NONE', - whoCanLeaveGroup: 'NONE_CAN_LEAVE', - whoCanJoin: 'INVITED_CAN_JOIN', - - // Email groups allow anyone (including externals) to post - // Non-email groups are not intended as mailing lists, so use the most restrictive settings - // whoCanViewGroup is badly named, but actually means 'Permissions to view group messages'. See https://developers.google.com/workspace/admin/groups-settings/v1/reference/groups - ...(group.isEmailGroup ? { - whoCanPostMessage: 'ANYONE_CAN_POST', - whoCanContactOwner: 'ALL_OWNERS_CAN_CONTACT', - whoCanViewGroup: 'ALL_MEMBERS_CAN_VIEW', - } : { - whoCanPostMessage: 'ALL_OWNERS_CAN_POST', - whoCanContactOwner: 'ALL_OWNERS_CAN_CONTACT', - whoCanViewGroup: 'ALL_OWNERS_CAN_VIEW', - }), + new gworkspace.GroupSettings( + role.google.group, + { + email: groups[role.google.group].email, + // Maximise visibility of group. It's visible in GitHub anyway + whoCanViewMembership: 'ALL_IN_DOMAIN_CAN_VIEW', + + // This specifies who can add/remove members. We want this to only be via this IaC. + whoCanModerateMembers: 'NONE', + whoCanLeaveGroup: 'NONE_CAN_LEAVE', + whoCanJoin: 'INVITED_CAN_JOIN', + + // Email groups allow anyone (including externals) to post + // Non-email groups are not intended as mailing lists, so use the most restrictive settings + // whoCanViewGroup is badly named, but actually means 'Permissions to view group messages'. See https://developers.google.com/workspace/admin/groups-settings/v1/reference/groups + ...(role.google.isEmailGroup + ? { + whoCanPostMessage: 'ANYONE_CAN_POST', + whoCanContactOwner: 'ALL_OWNERS_CAN_CONTACT', + whoCanViewGroup: 'ALL_MEMBERS_CAN_VIEW', + } + : { + whoCanPostMessage: 'ALL_OWNERS_CAN_POST', + whoCanContactOwner: 'ALL_OWNERS_CAN_CONTACT', + whoCanViewGroup: 'ALL_OWNERS_CAN_VIEW', + }), + }, + { ignoreChanges: ['isArchived'] } + ); +}); + +// Create the organizational unit for MCP users +const mcpOrgUnit = new gworkspace.OrgUnit( + 'mcp-org-unit', + { + name: 'Model Context Protocol', + description: 'Model Context Protocol', + parentOrgUnitPath: '/', + }, + { ignoreChanges: ['description'] } +); + +// Provision Google Workspace user accounts for members in roles with provisionUser +const provisionedUsersByEmail: Record = {}; +const newUserPasswords: Record> = {}; + +MEMBERS.forEach((member) => { + if ( + !member.firstName || + !member.lastName || + !member.googleEmailPrefix || + member.skipGoogleUserProvisioning + ) + return; + + const needsUser = member.memberOf.some((roleId: RoleId) => { + const role = roleLookup.get(roleId); + return role?.google?.provisionUser === true; }); + if (!needsUser) return; - group.memberOf?.forEach((parentGroupKey) => { - new gworkspace.GroupMember(`${group.name}-in-${parentGroupKey}`, { - groupId: groups[parentGroupKey].id, - email: groups[group.name].email, - role: 'MEMBER', + const primaryEmail = `${member.googleEmailPrefix}@modelcontextprotocol.io`; + + if (member.existingGWSUser) { + // Existing GWS users are not managed by Pulumi — the GWS provider's import + // validation rejects empty email types that GWS itself sets on primary/alias + // emails, and there's no way to fix this at the provider level. + // Group memberships for these users are created without dependsOn since the + // user already exists in GWS. + return; + } else { + // Create new user with random password + const password = new random.RandomPassword(`gws-pwd-${member.googleEmailPrefix}`, { + length: 24, + special: true, }); - }); + const hashedPassword = password.result.apply((plaintext: string) => + crypto.createHash('sha1').update(plaintext).digest('hex') + ); + + const user = new gworkspace.User( + `gws-user-${member.googleEmailPrefix}`, + { + primaryEmail, + name: { familyName: member.lastName!, givenName: member.firstName! }, + password: hashedPassword, + hashFunction: 'SHA-1', + changePasswordAtNextLogin: true, + orgUnitPath: mcpOrgUnit.orgUnitPath, + }, + { + dependsOn: [mcpOrgUnit], + ignoreChanges: [ + 'recoveryEmail', + 'recoveryPhone', + 'password', + 'hashFunction', + 'changePasswordAtNextLogin', + 'orgUnitPath', + 'archived', + 'suspended', + 'isAdmin', + 'includeInGlobalAddressList', + 'ipAllowlist', + 'addresses', + 'aliases', + 'customSchemas', + 'emails', + 'externalIds', + 'ims', + 'keywords', + 'languages', + 'locations', + 'organizations', + 'phones', + 'posixAccounts', + 'relations', + 'sshPublicKeys', + 'websites', + 'name', + ], + } + ); + provisionedUsersByEmail[primaryEmail] = user; + + // Track password for export so an admin can retrieve it + newUserPasswords[primaryEmail] = password.result; + } }); +// Create group memberships for users MEMBERS.forEach((member) => { - if (!member.email) return; + // Prefer the provisioned GWS email over the personal email for group memberships + const gwsEmail = member.googleEmailPrefix + ? `${member.googleEmailPrefix}@modelcontextprotocol.io` + : undefined; + const memberEmail = gwsEmail || member.email; + if (!memberEmail) return; + const provisionedUser = gwsEmail ? provisionedUsersByEmail[gwsEmail] : undefined; - member.memberOf.forEach((teamKey) => { - new gworkspace.GroupMember(`${member.email}-${teamKey}`, { - groupId: groups[teamKey].id, - email: member.email!, - role: 'MEMBER', - }); + member.memberOf.forEach((roleId: RoleId) => { + const role = roleLookup.get(roleId); + if (!role?.google) return; // Role doesn't have Google config + + new gworkspace.GroupMember( + `${memberEmail}-${role.google.group}`, + { + groupId: groups[role.google.group].id, + email: memberEmail, + role: 'MEMBER', + }, + provisionedUser ? { dependsOn: [provisionedUser] } : undefined + ); }); }); + +export { groups as googleGroups }; +// Export initial passwords as secrets so an admin can retrieve them with: +// pulumi stack output --show-secrets newGWSUserPasswords +export const newGWSUserPasswords = pulumi.secret(newUserPasswords); diff --git a/src/index.ts b/src/index.ts index 4fe06f1..c72d079 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from './github'; export * from './google'; +export * from './discord'; diff --git a/tsconfig.json b/tsconfig.json index 4b8e53f..09e8d66 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,4 +13,4 @@ }, "include": ["src/**/*"], "exclude": ["node_modules"] -} \ No newline at end of file +}