diff --git a/.gitattributes b/.gitattributes index 869cb17d44..79d7150dac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,4 +4,4 @@ pkg/operator/crds/*.yaml linguist-generated=true Makefile text eol=lf tools/make/*.mk text eol=lf tools/image-tag text eol=lf -tools/release text eol=lf +tools/publish-release-assets.sh text eol=lf diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a0349e2e4f..b520907b5d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,9 @@ @@ -21,7 +20,6 @@ contributors around things like how to update the changelog. -- [ ] CHANGELOG.md updated - [ ] Documentation added - [ ] Tests updated - [ ] Config converters updated diff --git a/.github/workflows/autolock.yml b/.github/workflows/autolock.yml index c347b9b6b9..8e62d7591a 100644 --- a/.github/workflows/autolock.yml +++ b/.github/workflows/autolock.yml @@ -1,25 +1,35 @@ -name: Lock closed issues and PRs +name: Auto-lock conversations + on: - workflow_dispatch: {} + pull_request_target: # zizmor: ignore[dangerous-triggers] Only used to lock PRs from forks + types: [closed] schedule: - cron: '0 0 * * *' - -permissions: - issues: write - pull-requests: write - discussions: write - -concurrency: - group: lock-threads + workflow_dispatch: jobs: - action: + lock-pr-on-close: + if: github.event_name == 'pull_request_target' + runs-on: ubuntu-latest + steps: + - name: Lock PR conversation 🔏 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr lock "$PR_URL" + permissions: + pull-requests: write + + lock-stale-issues: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 + - name: Lock stale issue conversations 🔏 + uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 with: - pr-inactive-days: 30 - issue-inactive-days: 30 + issue-inactive-days: 14 add-issue-labels: 'frozen-due-to-age' - add-pr-labels: 'frozen-due-to-age' - process-only: 'issues, prs' + process-only: 'issues' + permissions: + issues: write diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml deleted file mode 100644 index 50f82d18ec..0000000000 --- a/.github/workflows/backport.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Backport PR Creator -on: - pull_request: - types: - - closed - - labeled - -permissions: - contents: read - -jobs: - main: - runs-on: ubuntu-latest - # For now, only run this on the main repo. - if: github.repository == 'grafana/alloy' - # These permissions are needed to assume roles from Github's OIDC. - permissions: - contents: read - id-token: write - steps: - - name: Checkout Actions - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - repository: "grafana/grafana-github-actions" - path: ./actions - # Pin the version to before https://github.com/grafana/grafana-github-actions/pull/113 - # to avoid the strict rules for PR labels. - ref: d284afd314ca3625c23595e9f62b52d215ead7ce - persist-credentials: false - - name: Install Actions - run: npm install --production --prefix ./actions - - id: get-secrets - uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 - with: - repo_secrets: | - ALLOYBOT_APP_ID=alloybot:app_id - ALLOYBOT_PRIVATE_KEY=alloybot:private_key - export_env: false - - - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 - id: app-token - with: - app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }} - private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }} - owner: grafana - repositories: alloy - - - name: Get GitHub App - env: - APP_SLUG: ${{ steps.app-token.outputs.app-slug }} - GH_TOKEN: ${{ steps.app-token.outputs.token }} - id: get-app - run: | - APP_ID="$(gh api "/apps/${APP_SLUG}" --jq .id)" || exit 1 - echo "app-id=${APP_ID}" >> "${GITHUB_OUTPUT}" - echo "app-login=${APP_SLUG}[bot]" >> "${GITHUB_OUTPUT}" - - - name: Configure Git - env: - APP_ID: ${{ steps.get-app.outputs.app-id }} - APP_LOGIN: ${{ steps.get-app.outputs.app-login }} - run: | - git config --global user.name "${APP_LOGIN}" - git config --global user.email "${APP_ID}+${APP_LOGIN}@users.noreply.github.com" - - - name: Run backport - uses: ./actions/backport - with: - token: ${{ steps.app-token.outputs.token }} - labelsToAdd: "backport" - title: "[{{base}}] {{originalTitle}}" diff --git a/.github/workflows/check-sync-module-dependencies.yml b/.github/workflows/check-sync-module-dependencies.yml index 2470fbc9af..a7061a423d 100644 --- a/.github/workflows/check-sync-module-dependencies.yml +++ b/.github/workflows/check-sync-module-dependencies.yml @@ -12,6 +12,7 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 + persist-credentials: false - name: Set up Go uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 diff --git a/.github/workflows/check-versioned-files.yml b/.github/workflows/check-versioned-files.yml deleted file mode 100644 index 51b247fcb7..0000000000 --- a/.github/workflows/check-versioned-files.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Test Versioned Files -on: pull_request - -permissions: - contents: read - -jobs: - regenerate-docs: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Regenerate versioned files - run: | - make generate-versioned-files - if ! git diff --exit-code; then - echo "Newly generated versioned files differ from those checked in. Make sure to only update the templates manually and run 'make generate-versioned-files'!" >&2 - exit 1 - fi diff --git a/.github/workflows/check-windows-container.yml b/.github/workflows/check-windows-container.yml index b2700617ad..3ece720d34 100644 --- a/.github/workflows/check-windows-container.yml +++ b/.github/workflows/check-windows-container.yml @@ -9,7 +9,7 @@ on: - '.github/workflows/check-windows-container.yml' - '.github/workflows/publish-alloy.yml' - '.github/workflows/publish-alloy-devel.yml' - - '.github/workflows/publish-alloy-release.yml' + - '.github/workflows/release-publish-alloy-artifacts.yml' pull_request: paths: - 'Dockerfile.windows' @@ -17,7 +17,7 @@ on: - '.github/workflows/check-windows-container.yml' - '.github/workflows/publish-alloy.yml' - '.github/workflows/publish-alloy-devel.yml' - - '.github/workflows/publish-alloy-release.yml' + - '.github/workflows/release-publish-alloy-artifacts.yml' permissions: contents: read diff --git a/.github/workflows/integration-tests-k8s.yml b/.github/workflows/integration-tests-k8s.yml index 1d2f102f41..14cbf89129 100644 --- a/.github/workflows/integration-tests-k8s.yml +++ b/.github/workflows/integration-tests-k8s.yml @@ -15,6 +15,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup Go uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5 with: diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml new file mode 100644 index 0000000000..fc418ee67c --- /dev/null +++ b/.github/workflows/lint-pr-title.yml @@ -0,0 +1,61 @@ +name: PR Title Lint + +on: + pull_request: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + id-token: write + +jobs: + lint-pr-title: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - name: Get GitHub app secrets 🔐 + id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 + with: + export_env: false + repo_secrets: | + ALLOYBOT_APP_ID=alloybot:app_id + ALLOYBOT_PRIVATE_KEY=alloybot:private_key + + - name: Generate token 🔐 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + id: app-token + with: + app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }} + private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }} + owner: grafana + repositories: alloy + + - name: Validate PR title 🔎 + uses: amannn/action-semantic-pull-request@e32d7e603df1aa1ba07e981f2a23455dee596825 # v5 + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + with: + # Require conventional commit types + types: | + feat + fix + docs + style + refactor + perf + test + build + ci + chore + revert + # Scope is optional + requireScope: false + # Disallow uppercase first letter in subject + subjectPattern: ^[a-z].+$ + subjectPatternError: | + The subject "{subject}" must start with a lowercase letter. + Example: "feat: add new component" not "feat: Add new component" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 70efb4fcf0..4238bdc3a7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -55,7 +55,7 @@ jobs: packaging,\ production,\ tools/ci,\ - tools/release,\ + tools/publish-release-assets.sh,\ docs/sources/tutorials/assets\ " diff --git a/.github/workflows/release-backport.yml b/.github/workflows/release-backport.yml new file mode 100644 index 0000000000..a84aa7c073 --- /dev/null +++ b/.github/workflows/release-backport.yml @@ -0,0 +1,94 @@ +name: Backport PR + +on: + pull_request: + types: [labeled, closed] + +permissions: + contents: write + pull-requests: write + id-token: write + +jobs: + backport: + runs-on: ubuntu-latest + steps: + - name: Check if PR is merged and find backport labels 🏷️ + id: check + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + + if (!pr.merged_at) { + console.log('PR is not merged, skipping'); + core.setOutput('should_backport', 'false'); + return; + } + + const labels = pr.labels || []; + const backportLabels = labels + .map(l => l.name) + .filter(name => name.startsWith('backport/')); + + if (backportLabels.length === 0) { + console.log('No backport labels found, skipping'); + core.setOutput('should_backport', 'false'); + return; + } + + console.log(`PR is merged and has backport labels: ${backportLabels.join(', ')}`); + core.setOutput('labels', JSON.stringify(backportLabels)); + core.setOutput('should_backport', 'true'); + + - name: Get GitHub app secrets 🔐 + if: steps.check.outputs.should_backport == 'true' + id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 + with: + export_env: false + repo_secrets: | + ALLOYBOT_APP_ID=alloybot:app_id + ALLOYBOT_PRIVATE_KEY=alloybot:private_key + + - name: Generate token 🔐 + if: steps.check.outputs.should_backport == 'true' + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + id: app-token + with: + app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }} + private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }} + owner: grafana + repositories: alloy + + - name: Checkout repository 🛎️ + if: steps.check.outputs.should_backport == 'true' + uses: actions/checkout@v4 + with: + token: ${{ steps.app-token.outputs.token }} + fetch-depth: 0 + persist-credentials: false + + - name: Set up Go 🏗️ + if: steps.check.outputs.should_backport == 'true' + uses: actions/setup-go@v5 + with: + go-version-file: tools/go.mod + cache-dependency-path: tools/go.sum + + - name: Run backport tool 🍒 + if: steps.check.outputs.should_backport == 'true' + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + LABELS: ${{ steps.check.outputs.labels }} + run: | + cd tools + labels="${LABELS}" + + for label in $(echo "$labels" | jq -r '.[]'); do + echo "🍒 Processing backport for label: $label" + go run ./release/backport \ + --pr "${{ github.event.pull_request.number }}" \ + --label "$label" + done diff --git a/.github/workflows/release-create-branch.yml b/.github/workflows/release-create-branch.yml new file mode 100644 index 0000000000..dab85e832a --- /dev/null +++ b/.github/workflows/release-create-branch.yml @@ -0,0 +1,62 @@ +name: Create Release Branch + +on: + workflow_dispatch: + inputs: + source_ref: + description: 'Source ref to branch from (default: main)' + required: false + default: 'main' + type: string + dry_run: + description: 'Dry run (do not create branch)' + type: boolean + default: true # For safety! + +permissions: + contents: write + id-token: write + +jobs: + create-branch: + runs-on: ubuntu-latest + steps: + - name: Get GitHub app secrets 🔐 + id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 + with: + export_env: false + repo_secrets: | + ALLOYBOT_APP_ID=alloybot:app_id + ALLOYBOT_PRIVATE_KEY=alloybot:private_key + + - name: Generate token 🔐 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + id: app-token + with: + app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }} + private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }} + owner: grafana + repositories: alloy + + - name: Checkout repository 🛎️ + uses: actions/checkout@v4 + with: + ref: ${{ inputs.source_ref }} + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + persist-credentials: false + + - name: Set up Go 🏗️ + uses: actions/setup-go@v5 + with: + go-version-file: tools/go.mod + cache-dependency-path: tools/go.sum + + - name: Run create-release-branch tool 🌿 + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + SOURCE_REF: ${{ inputs.source_ref }} + run: | + cd tools + go run ./release/create-release-branch --source "${SOURCE_REF}" ${{ inputs.dry_run == true && '--dry-run' || '' }} diff --git a/.github/workflows/release-create-rc.yml b/.github/workflows/release-create-rc.yml new file mode 100644 index 0000000000..6972b926ab --- /dev/null +++ b/.github/workflows/release-create-rc.yml @@ -0,0 +1,69 @@ +name: Create Release Candidate + +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run (do not create tag or release)' + type: boolean + default: true # For safety! + +permissions: + contents: write + pull-requests: read + id-token: write + +jobs: + create-rc: + runs-on: ubuntu-latest + steps: + - name: Get GitHub app secrets 🔐 + id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 + with: + export_env: false + repo_secrets: | + ALLOYBOT_APP_ID=alloybot:app_id + ALLOYBOT_PRIVATE_KEY=alloybot:private_key + + - name: Generate token 🔐 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + id: app-token + with: + app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }} + private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }} + owner: grafana + repositories: alloy + + - name: Validate branch format 🔎 + run: | + BRANCH="${GITHUB_REF_NAME}" + if [[ ! "$BRANCH" =~ ^release/v[0-9]+\.[0-9]+$ ]]; then + echo "::error::This workflow must be run from a release branch (release/vX.Y)" + echo "::error::Selected branch: $BRANCH" + exit 1 + fi + echo "✅ Running on release branch: $BRANCH" + env: + GITHUB_REF_NAME: ${{ github.ref_name }} + + - name: Checkout repository 🛎️ + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + persist-credentials: false + + - name: Set up Go 🏗️ + uses: actions/setup-go@v5 + with: + go-version-file: tools/go.mod + cache-dependency-path: tools/go.sum + + - name: Run create-rc tool 🚀 + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + GITHUB_REF_NAME: ${{ github.ref_name }} + run: | + cd tools + go run ./release/create-rc --branch "${GITHUB_REF_NAME}" ${{ inputs.dry_run == true && '--dry-run' || '' }} diff --git a/.github/workflows/release-enrich-release-notes.yml b/.github/workflows/release-enrich-release-notes.yml new file mode 100644 index 0000000000..b1f54472f2 --- /dev/null +++ b/.github/workflows/release-enrich-release-notes.yml @@ -0,0 +1,50 @@ +name: Enrich Release Notes with Contributors + +on: + release: + types: [published] + +permissions: + contents: write + id-token: write + +jobs: + enrich-release: + runs-on: ubuntu-latest + steps: + - name: Get GitHub app secrets 🔐 + id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 + with: + export_env: false + repo_secrets: | + ALLOYBOT_APP_ID=alloybot:app_id + ALLOYBOT_PRIVATE_KEY=alloybot:private_key + + - name: Generate token 🔐 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + id: app-token + with: + app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }} + private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }} + owner: grafana + repositories: alloy + + - name: Checkout repository 🛎️ + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Go 🏗️ + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + with: + go-version-file: tools/go.mod + cache-dependency-path: tools/go.sum + + - name: Enrich release notes 🙋 + run: | + cd tools + go run ./release/enrich-release-notes --tag "${RELEASE_TAG_NAME}" + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} diff --git a/.github/workflows/release-forwardport-to-main.yml b/.github/workflows/release-forwardport-to-main.yml new file mode 100644 index 0000000000..807d509240 --- /dev/null +++ b/.github/workflows/release-forwardport-to-main.yml @@ -0,0 +1,57 @@ +name: Forwardport Release Branch to Main + +on: + pull_request: + types: [closed] + branches: + - 'release/**' + +permissions: + contents: write + id-token: write + +jobs: + forwardport-release-to-main: + runs-on: ubuntu-latest + # Only run when a release-please PR is merged (not just closed) + if: | + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.head.ref, 'release-please--') + steps: + - name: Get GitHub app secrets 🔐 + id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 + with: + export_env: false + repo_secrets: | + ALLOYBOT_APP_ID=alloybot:app_id + ALLOYBOT_PRIVATE_KEY=alloybot:private_key + + - name: Generate token 🔐 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + id: app-token + with: + app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }} + private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }} + owner: grafana + repositories: alloy + + - name: Checkout repository 🛎️ + uses: actions/checkout@v4 + with: + token: ${{ steps.app-token.outputs.token }} + fetch-depth: 0 + persist-credentials: false + + - name: Set up Go 🏗️ + uses: actions/setup-go@v5 + with: + go-version-file: tools/go.mod + cache-dependency-path: tools/go.sum + + - name: Forwardport to main 🔀 + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + cd tools + go run ./release/forwardport-release-to-main --pr "${{ github.event.pull_request.number }}" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000000..79708e62e3 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,58 @@ +name: Release Please + +on: + push: + branches: + - 'release/v*' + +permissions: + contents: write + pull-requests: write + id-token: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - name: Get GitHub app secrets 🔐 + id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 + with: + export_env: false + repo_secrets: | + ALLOYBOT_APP_ID=alloybot:app_id + ALLOYBOT_PRIVATE_KEY=alloybot:private_key + + - name: Generate token 🔐 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + id: app-token + with: + app-id: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_APP_ID }} + private-key: ${{ fromJSON(steps.get-secrets.outputs.secrets).ALLOYBOT_PRIVATE_KEY }} + owner: grafana + repositories: alloy + + - name: Checkout repository 🛎️ + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Setup Node.js 🏗️ + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: tools/release/release-please-runner/.nvmrc + package-manager-cache: false + + - name: Install dependencies 📦 + working-directory: tools/release/release-please-runner + run: npm install + + - name: Run release-please 🚀 + working-directory: tools/release/release-please-runner + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + REPO_URL: ${{ github.repository }} + TARGET_BRANCH: ${{ github.ref_name }} + CONFIG_FILE: release-please-config.json + MANIFEST_FILE: .release-please-manifest.json + run: node index.js diff --git a/.github/workflows/publish-alloy-release.yml b/.github/workflows/release-publish-alloy-artifacts.yml similarity index 88% rename from .github/workflows/publish-alloy-release.yml rename to .github/workflows/release-publish-alloy-artifacts.yml index f3bbe2dfb7..fe6ef1c76c 100644 --- a/.github/workflows/publish-alloy-release.yml +++ b/.github/workflows/release-publish-alloy-artifacts.yml @@ -1,8 +1,7 @@ -name: Publish alloy release containers +name: Publish Alloy release artifacts on: - push: - tags: - - v* + release: + types: [published] permissions: contents: read @@ -45,6 +44,7 @@ jobs: - name: Checkout code uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: + ref: ${{ github.event.release.tag_name }} persist-credentials: false - name: Set ownership @@ -63,10 +63,9 @@ jobs: run: | RELEASE_BUILD=1 make -j4 dist env: - VERSION: ${{ github.ref_name }} + VERSION: ${{ github.event.release.tag_name }} - name: Publish dist - if: github.ref_type == 'tag' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dist @@ -74,7 +73,6 @@ jobs: if-no-files-found: error - name: Publish unsigned Windows executables - if: github.ref_type == 'tag' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: windows-executables @@ -85,7 +83,6 @@ jobs: sign_alloy_windows: name: Sign Alloy executables for Windows - if: github.ref_type == 'tag' runs-on: windows-2025 environment: name: azure-trusted-signing @@ -127,6 +124,7 @@ jobs: - name: Checkout code uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: + ref: ${{ github.event.release.tag_name }} persist-credentials: false - name: Set ownership @@ -152,7 +150,7 @@ jobs: run: | RELEASE_BUILD=1 makensis -V4 -DVERSION=${VERSION} -DOUT="../../dist/alloy-installer-windows-amd64.exe" ./packaging/windows/install_script.nsis env: - VERSION: ${{ github.ref_name }} + VERSION: ${{ github.event.release.tag_name }} - name: Publish unsigned Windows installer uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 @@ -193,38 +191,21 @@ jobs: azure-tenant-id: ${{ fromJSON(steps.get-signing-secrets.outputs.secrets).tenant-id }} signed-artifact-name: 'windows-installer-signed' - publish_github_release: - name: Publish GitHub release - if: github.ref_type == 'tag' + upload_release_assets: + name: Upload release assets container: grafana/alloy-build-image:v0.1.23@sha256:89dad4df5afe167898860bfa06c2eea290f4da0c2cff668fdf43299ddca867dc runs-on: labels: github-hosted-ubuntu-x64-large needs: - sign_alloy_windows_installer permissions: - # This job needs write access so it can create a draft GitHub release. + # This job needs write access to upload assets to the GitHub release. contents: write id-token: write steps: - - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set ownership - # https://github.com/actions/runner/issues/2033#issuecomment-1204205989 - run: | - # This is to fix Git not liking owner of the checkout directory - chown -R $(id -u):$(id -g) $PWD - - - name: Set up Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 - with: - go-version-file: go.mod - cache: false - - # A GitHub App is used to create the release instead of github-actions so that submit-winget-manifest is triggered when the release is published - - name: Get GitHub app secrets + # A GitHub App is used to create the release instead of github-actions so that + # submit-winget-manifest is triggered when the release is published + - name: Get GitHub app secrets 🔐 id: get-secrets uses: grafana/shared-workflows/actions/get-vault-secrets@a37de51f3d713a30a9e4b21bcdfbd38170020593 # get-vault-secrets/v1.3.0 with: @@ -233,7 +214,7 @@ jobs: ALLOYBOT_APP_ID=alloybot:app_id ALLOYBOT_PRIVATE_KEY=alloybot:private_key - - name: Create GitHub app token + - name: Create GitHub app token 🔐 uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 id: app-token with: @@ -242,13 +223,19 @@ jobs: owner: grafana repositories: alloy - - name: Checkout + - name: Checkout repository 🛎️ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: - fetch-depth: 0 + ref: ${{ github.event.release.tag_name }} token: ${{ steps.app-token.outputs.token }} persist-credentials: false + - name: Set ownership + # https://github.com/actions/runner/issues/2033#issuecomment-1204205989 + run: | + # This is to fix Git not liking owner of the checkout directory + chown -R $(id -u):$(id -g) $PWD + - name: Download dist uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: @@ -269,8 +256,19 @@ jobs: name: windows-installer-signed path: dist - - name: Publish - run: | - VERSION="${GITHUB_REF_NAME}" RELEASE_DOC_TAG=$(echo "${GITHUB_REF_NAME}" | awk -F '.' '{print $1"."$2}') ./tools/release + - name: Publish release assets + run: ./tools/publish-release-assets.sh env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + + submit_winget_manifest: + name: Submit WinGet Manifest + needs: + - upload_release_assets + uses: ./.github/workflows/release-submit-winget-manifest.yml + permissions: + contents: read + id-token: write + with: + release-tag: ${{ github.event.release.tag_name }} diff --git a/.github/workflows/submit-winget-manifest.yml b/.github/workflows/release-submit-winget-manifest.yml similarity index 84% rename from .github/workflows/submit-winget-manifest.yml rename to .github/workflows/release-submit-winget-manifest.yml index d573ce949e..7f8bf7199b 100644 --- a/.github/workflows/submit-winget-manifest.yml +++ b/.github/workflows/release-submit-winget-manifest.yml @@ -1,8 +1,12 @@ name: Submit WinGet Manifest on: - release: - types: [published] + workflow_call: + inputs: + release-tag: + description: 'Release tag (e.g., v1.0.0)' + required: true + type: string permissions: {} @@ -33,7 +37,7 @@ jobs: - name: Submit WinGet Manifest env: PACKAGE_ID: GrafanaLabs.Alloy - PACKAGE_VERSION: ${{ github.event.release.tag_name }} + PACKAGE_VERSION: ${{ inputs.release-tag }} WINGET_CREATE_GITHUB_TOKEN: ${{ fromJSON(steps.get-token.outputs.secrets).token }} shell: pwsh run: | diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000000..a09efe7f87 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.12.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index b22528bb08..ef2fca5053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,84 +1,5 @@ # Changelog -> _Contributors should read our [contributors guide][] for instructions on how -> to update the changelog._ - -This document contains a historical list of changes between releases. Only -changes that impact end-user behavior are listed; changes to documentation or -internal API changes are not present. - -Main (unreleased) ------------------ - -- Support specifying DNS discovery mode prefixes in `--cluster.join-addresses` flag. (@x1unix) - -### Features - -- Add `otelcol.connector.count` component to count the number of spans, metrics, and logs passing through it. (@hhertout) - -- A new `mimir.alerts.kubernetes` component which discovers `AlertmanagerConfig` Kubernetes resources and loads them into a Mimir instance. (@ptodev) - -- Mark `stage.windowsevent` block in the `loki.process` component as GA. (@kgeckhart) - -- (_Experimental_) A new `otelcol.receiver.awss3` component to receive traces previously stored in S3 by the [AWS S3 Exporter](https://grafana.com/docs/alloy/latest/reference/components/otelcol/otelcol.exporter.awss3/). (@x1unix) - -- (_Experimental_) Add `pyroscope.enrich` component to enrich profiles using labels from `discovery.*` components. (@AndreZiviani) - -- Add htpasswd file based authentication for `otelcol.auth.basic` (@pkarakal) - -- Add `prometheus.echo` component for local inspection of Prometheus metrics. The component writes received metrics to stdout in Prometheus exposition format, enabling easier debugging and testing of metrics flow. (@iamrajiv) - -### Enhancements - -- update promtail converter to use `file_match` block for `loki.source.file` instead of going through `local.file_match`. (@kalleep) - -- Add `send_traceparent` option for `tracing` config to enable traceparent header propagation. (@MyDigitalLife) - -- Add support for HTTP service discovery in `prometheus.operator.scrapeconfigs` component using `httpSDConfigs` in ScrapeConfig CRDs. (@QuentinBisson) - -- Add `delay` option to `prometheus.exporter.cloudwatch` component to delay scraping of metrics to account for CloudWatch ingestion latency. (@tmeijn) - -- Export `yace_.*` metrics from the underlying YACE Exporter to `prometheus.exporter.cloudwatch`. (@tmeijn) - -- (_Public Preview_) Additions to `database_observability.mysql` and `database_observability.postgres` components: - - `explain_plans` - - always send an explain plan log message for each query, even skipped or errored queries. (@rgeyer) - - Metrics are now appended with cloud provider information labels (@matthewnolf) - -- Reduced resource overhead of `prometheus.scrape`, `prometheus.relabel`, `prometheus.enrich`, and `prometheus.remote_write` by removing unnecessary usage of labelstore.LabelStore. (@kgeckhart) - -- Updated Prometheus dependencies to v3.8.0. (@thampiotr) - -- Updated Loki dependencies to v3.6.2. (@thampiotr) - -- Refactor tailer used in `loki.source.file` to reduce resource usage. (@kalleep) - -### Bugfixes - -- (_Public Preview_) Additions to `database_observability.postgres` component: - - `schema_details` - - fixes collection of schema details for mixed case table names (@fridgepoet) - -- (_Public Preview_) Additions to `database_observability.mysql` component: - - replace the internal `server_id` label attribution in favor of a hash composed from `@@server_uuid` and `@@hostname` - - add `setup_actors` collector that checks and optionally updates settings to avoid tracking queries for the currently connected user (@cristiangreco) - -- Fix the `prometheus.operator.*` components internal scrape manager now having a way to enable ingesting native histograms. (@dehaansa) - -- [`mimir.alerts.kubernetes`] Fixed a bug which caused Alloy to crash when using a Kubernetes secret or configmap in the AlertmanagerConfig CRD. (@synthe102) - -- Remove extraneous `output` stage from the `cri` stage pipeline in `loki.process`. (@kalleep) - -- Fix Docker log corruption for multiplexed long lines. (@axd1x8a) - -- Fix the promtail converter behavior to mimic promtail behavior by default and limit kubernetes discovery to the same node. (@dehaansa) - -- Allow configuration of `force_attempt_http2` and default it to `true` for otelcol exporters with HTTP client configurations. (@dehaansa) - -- Fix default values for relabel rules, this caused issues in e.g. `prometheus.operator.servicemonitors` when using labeldrop. (@kalleep) - -- Fix an issue in the `prometheus.exporter.gcp` component where colons inside `extra_filters` were incorrectly removed. Filter expressions such as `database_id="project_id:database_name"` are now preserved as expected. (@Kim-Yukyung) - v1.12.0 ----------------- diff --git a/Makefile b/Makefile index 1dbc72e13a..421eabeea8 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,6 @@ ## generate-helm-docs Generate Helm chart documentation. ## generate-helm-tests Generate Helm chart tests. ## generate-ui Generate the UI assets. -## generate-versioned-files Generate versioned files. ## generate-winmanifest Generate the Windows application manifest. ## generate-snmp Generate SNMP modules from prometheus/snmp_exporter for prometheus.exporter.snmp and bumps SNMP version in _index.md.t. ## generate-module-dependencies Generate replace directives from dependency-replacements.yaml and inject them into go.mod and builder-config.yaml. @@ -241,8 +240,8 @@ alloy-image-windows: # Targets for generating assets # -.PHONY: generate generate-helm-docs generate-helm-tests generate-ui generate-versioned-files generate-winmanifest generate-snmp -generate: generate-helm-docs generate-helm-tests generate-ui generate-versioned-files generate-docs generate-winmanifest generate-snmp +.PHONY: generate generate-helm-docs generate-helm-tests generate-ui generate-winmanifest generate-snmp +generate: generate-helm-docs generate-helm-tests generate-ui generate-docs generate-winmanifest generate-snmp generate-helm-docs: ifeq ($(USE_CONTAINER),1) @@ -272,13 +271,6 @@ else cd ./internal/web/ui && npm install && npm run build endif -generate-versioned-files: -ifeq ($(USE_CONTAINER),1) - $(RERUN_IN_CONTAINER) -else - sh ./tools/gen-versioned-files/gen-versioned-files.sh -endif - generate-docs: ifeq ($(USE_CONTAINER),1) $(RERUN_IN_CONTAINER) diff --git a/docs/developer/.prettierrc b/docs/developer/.prettierrc new file mode 100644 index 0000000000..37ed729778 --- /dev/null +++ b/docs/developer/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 2, + "useTabs": false, + "printWidth": 100, + "proseWrap": "always" +} diff --git a/docs/developer/breaking-changes.md b/docs/developer/breaking-changes.md index 9bbb4dffd0..7af87010bb 100644 --- a/docs/developer/breaking-changes.md +++ b/docs/developer/breaking-changes.md @@ -89,8 +89,8 @@ We'd typically prefer options in the following order: ## Communicating the breaking changes Currently, we use our CHANGELOG.md to communicate the presence of breaking -changes. They are also included on a GitHub release page under the notable -changes section. For changes that require manual steps to migrate, we must also +changes. They are also included on a GitHub release page under the Breaking +Changes section. For changes that require manual steps to migrate, we must also include a migration guide. ## Tracking work that needs to be done for the next major release diff --git a/docs/developer/contributing.md b/docs/developer/contributing.md index 86933b3616..577d1253fc 100644 --- a/docs/developer/contributing.md +++ b/docs/developer/contributing.md @@ -114,54 +114,6 @@ you can @-reply a reviewer asking for a review in the pull request or a comment, or you can ask for a review on the Slack channel [#alloy](https://slack.grafana.com). -## Updating the changelog - -We keep a [changelog](../../CHANGELOG.md) of code changes which result in new -or changed user-facing behavior. - -Changes are grouped by change type, denoted by `### Category_Name`. The change -types are, in order: - -1. Security fixes -2. Breaking changes -3. Deprecations -4. Features -5. Enhancements -6. Bugfixes -7. Other changes - -Categories won't be listed if there's not any changes for that category. - -When opening a PR which impacts user-facing behavior, contributors should: - -1. Determine which changes need to be documented in the changelog (a PR may - change more than one user-facing behavior). - -2. If there are no other changes for that change type, add a header for it - (e.g., `### Bugfixes`). Make sure to keep the order listed above. - -3. Add relevant entries into the changelog. - -When in doubt, look at a previous release for style and ordering examples. - -### Changelog entry style tips - -Change entries in the changelog should: - -1. Be complete sentences, ending in a period. It is acceptable to use multiple - complete sentences if one sentence can't accurately describe the change. -2. Describe the impact on the user which is reading the changelog. -3. Include credit to the GitHub user that opened the PR following the sentence. - -For example: -`- Config file reading is now 1500% faster. (@torvalds)` - -> Readers should be able to understand how a change impacts them. Default to -> being explicit over vague. -> -> * Vague: `- Fixed issue with metric names. (@ghost)` -> * Explicit: `- Fixed issue where instances of the letter s in metric names were replaced with z. (@ghost)` - ## Dependency management Alloy uses [Go modules][go-modules] to manage dependencies on external diff --git a/docs/developer/release/00-ensure-otel-dep-updated.md b/docs/developer/release/00-ensure-otel-dep-updated.md deleted file mode 100644 index ea87009aba..0000000000 --- a/docs/developer/release/00-ensure-otel-dep-updated.md +++ /dev/null @@ -1,19 +0,0 @@ -# Ensure OpenTelemetry Collector dependency has been updated - -Every minor release **must** include an update to a newer version of OpenTelemetry -Collector (when available). Because the release cadence of OpenTelemetry is -three times more frequent, this update should happen near the end of a six-week -release cycle, such as 1-2 weeks out. - -If the OpenTelemetry Collector dependency has not been updated within a release -cycle, **the release should be blocked.** - -## Steps - -1. Examine the CHANGELOG to ensure that the OpenTelemetry Collector dependency - has been updated within the release cycle. - -2. If the dependency has been updated: continue the release process as normal. - -3. If the dependency has not been updated: pause the release process and - orchestrate updating the dependency. diff --git a/docs/developer/release/01-create-release-branch.md b/docs/developer/release/01-create-release-branch.md deleted file mode 100644 index 2d130b8cf3..0000000000 --- a/docs/developer/release/01-create-release-branch.md +++ /dev/null @@ -1,30 +0,0 @@ -# Create Release Branch - -A single release branch is created for every major or minor release. That release -branch is then used for all Release Candidates, the Stable Release, and all -Patch Releases for that version of Alloy. - -## Before you begin - -1. Determine the [VERSION_PREFIX](concepts/version.md). - -## Steps - -1. Determine which commit should be used as a base for the release branch. - -2. Create and push the release branch from the selected base commit: - - The name of the release branch should be `release/VERSION_PREFIX` - defined above, such as `release/v0.31`. - - > **NOTE**: Branches are only made for VERSION_PREFIX; do not create branches for the full VERSION such as `release/v0.31-rc.0` or `release/v0.31.0`. - - - If the consensus commit is the latest commit from main you can branch from main. - - If the consensus commit is not the latest commit from main, branch from that instead. - - > **NOTE**: Don't create any other branches that are prefixed with `release` when creating PRs or - > those branches will collide with our automated release build publish rules. - -3. Open a PR against `main` to update the VERSION file at the root of the - repository to the next minor release planned. For example, if you have just - created `release/v1.0`, then VERSION should be updated to `v1.1.0`. diff --git a/docs/developer/release/02-update-version-in-code.md b/docs/developer/release/02-update-version-in-code.md deleted file mode 100644 index 3205783184..0000000000 --- a/docs/developer/release/02-update-version-in-code.md +++ /dev/null @@ -1,98 +0,0 @@ -# Update the "main" and "release/" branches - -You need to update the CHANGELOG and the [VERSION file][VERSION-file]. -You may also need to cherry pick commits on the `release/` branch. - -## Before you begin - -1. Determine the [VERSION](concepts/version.md). - -2. Determine the [VERSION_PREFIX](concepts/version.md) - -## 1. Update the "release/VERSION_PREFIX" branch - -Example PRs: - -* [Release Candidate](https://github.com/grafana/alloy/pull/1410). - Here the PR is done on the main branch, before creating the release branch. - You can do this to save time by not having to update the release branch separately. -* [Additional Release Candidate](https://github.com/grafana/alloy/pull/1701) -* [Stable Release](https://github.com/grafana/alloy/pull/1747) - There is no need to update the VERSION file in this PR. - The VERSION file is already pointing to the version being released ever since the `release/` branch was created. -* [Patch Release](https://github.com/grafana/alloy/pull/1767) - -### 1.1. Add the new version to the CHANGELOG - -For a First Release Candidate (`rc.0`), add a new header below the `Main (unreleased)` for `VERSION`. For example: -``` -Main (unreleased) ------------------ - -v1.12.0-rc.0 ------------------ - -(Release notes that were previously under Main (unreleased)) -``` - -For an Additional Release Candidate or SRV, increment the number in the release candidate header to match your new -`VERSION`. For example, `v1.12.0-rc.0` becomes `v1.12.0-rc.1`. - -For a patch release, add a new header for `VERSION`. - -### 1.2. Cherry pick commits - -If you need certain changes on the release branch but they're not yet there, cherry-pick them onto the release branch. -In the CHANGELOG, make sure they are listed under the header for the new VERSION and not under `Main (unreleased)`. - -### 1.3. Update the VERSION file - -The [VERSION file][VERSION-file] is used by the CI to ensure that templates and generated files are in sync. - -The VERSION file on the `release/` branch should point to the stable (or patch) version you're about to release. - -The contents of the VERSION file should not contain `rc` information. -Therefore, there is no need to update the VERSION file for additional release candidates (e.g. `rc.1`, `rc.2`). - -For example: -* If you are going to release `v1.2.0-rc.0`, then the VERSION file should contain `v1.2.0`. -* If you are going to release `v1.5.1`, then the VERSION file should contain `v1.5.1`. - -After updating the VERSION file, run: - -```bash -make generate-versioned-files -``` - -## 2. Update the "main" branch - -Examples: - -* Release Candidate example PR [here](https://github.com/grafana/alloy/pull/1410) -* Stable Release example PR [here](https://github.com/grafana/alloy/pull/1419) -* Patch Release example PR [here](https://github.com/grafana/alloy/pull/1769) - -### 2.1. Add the new version to the CHANGELOG - -For a First Release Candidate or a Patch Release, add a new header under `Main (unreleased)` for `VERSION`. - -For an Additional Release Candidate or SRV, update the header `PREVIOUS_RELEASE_CANDIDATE_VERSION` to `VERSION`. - -### 2.2. Update the VERSION file - -The [VERSION file][VERSION-file] on the "main" branch should point to the next minor version. -For example: -* If you are going to release `v1.2.0-rc.0`, then the VERSION file should contain `v1.3.0`. -* If you are going to release `v1.5.1`, then the VERSION file should contain `v1.6.0`. - -The reasoning behind this is that any builds of the main branch should contain the next minor version they are meant to go to. -If the latest release branch that was cut is `release/1.3`, then `main` is preparing for `1.4.0`. -Any builds of the main branch will therefore be labeled `devel-1.4.0`. - -After updating the VERSION file, run: - -```bash -make generate-versioned-files -``` - -[VERSION-file]: https://github.com/grafana/alloy/blob/main/VERSION diff --git a/docs/developer/release/03-tag-release.md b/docs/developer/release/03-tag-release.md deleted file mode 100644 index d7e42ec67c..0000000000 --- a/docs/developer/release/03-tag-release.md +++ /dev/null @@ -1,53 +0,0 @@ -# Tag Release - -A tag is required to create GitHub artifacts and as a prerequisite for publishing. - -## Before you begin - -1. All required commits for the release should exist on the release branch. This includes functionality and documentation such as the `CHANGELOG.md`. All versions in code should have already been updated. - -2. Make sure you are up to date on the release branch: - - ``` - git checkout release/VERSION_PREFIX - git fetch origin - git pull origin - ``` - -3. Determine the [VERSION](concepts/version.md). - -4. Follow the GitHub [instructions](https://docs.github.com/en/authentication/managing-commit-signature-verification) to set up GPG for signature verification. - -5. Optional: Configure git to always sign on commit or tag. - -```bash -git config --global commit.gpgSign true -git config --global tag.gpgSign true -``` - -If you are on macOS or linux and using an encrypted GPG key, `gpg-agent` or `gpg` may be unable -to prompt you for your private key passphrase. This will be denoted by an error -when creating a commit or tag. To circumvent the error, add the following into -your `~/.bash_profile`, `~/.bashrc` or `~/.zshrc`, depending on which shell you are using. - -``` -export GPG_TTY=$(tty) -``` - -## Steps - -1. Tag the release: - - Example commands: - - ``` - git tag -s VERSION - git push origin VERSION - ``` - -2. After a tag has been pushed, GitHub Tasks will create release assets and open a release draft for every pushed tag. - - - This will take ~20-40 minutes. - - You can monitor this by viewing the GitHub Actions log on the commit for the release tag. - - If the Homebrew Formula fails to update, close the existing open PR and re-run the failed CI. diff --git a/docs/developer/release/04-publish-release.md b/docs/developer/release/04-publish-release.md deleted file mode 100644 index 0275f1ca29..0000000000 --- a/docs/developer/release/04-publish-release.md +++ /dev/null @@ -1,28 +0,0 @@ -# Publish Release - -This is how to publish the release in GitHub. - -## Before you begin - -1. You should see a new draft release created [here](https://github.com/grafana/alloy/releases). If not go back to [Tag Release](./4-tag-release.md). - -## Steps - -1. Edit the release draft by filling in the `Notable Changes` section with all `Breaking Changes` and `Features` from the CHANGELOG.md. - -2. Add any additional changes that you think are notable to the list. - -3. Add a footer to the `Notable Changes` section: - - `For a full list of changes, please refer to the [CHANGELOG](https://github.com/grafana/alloy/blob/RELEASE_VERSION/CHANGELOG.md)!` - - Do not substitute the value for `CHANGELOG`. - -4. At the bottom of the release page, perform the following: - - For a Release Candidate, tick the checkbox to "pre-release". - - For a Stable Release or Patch Release, tick the checkbox to "set as the latest release". - -5. Optionally, have other team members review the release draft if you wish - to feel more comfortable with it. - -6. Publish the release! diff --git a/docs/developer/release/05-test-release.md b/docs/developer/release/05-test-release.md deleted file mode 100644 index dab2b10ea5..0000000000 --- a/docs/developer/release/05-test-release.md +++ /dev/null @@ -1,9 +0,0 @@ -# Test Release - -Validate the new version is working by running it incrementally in all dogfooding clusters. - -## Steps - -1. Validate performance metrics are consistent with the prior version. - -2. Validate components are healthy. \ No newline at end of file diff --git a/docs/developer/release/06-update-helm-charts.md b/docs/developer/release/06-update-helm-charts.md deleted file mode 100644 index b77f7142e0..0000000000 --- a/docs/developer/release/06-update-helm-charts.md +++ /dev/null @@ -1,21 +0,0 @@ -# Update Helm Charts - -Our Helm charts require some version updates as well. - -## Before you begin - -1. Install [helm-docs](https://github.com/norwoodj/helm-docs) on macOS/Linux. - -2. Install [yamllint](https://github.com/adrienverge/yamllint) on macOS/Linux. - -## Steps - -1. Create a branch from `main` for [grafana/alloy](https://github.com/grafana/alloy). - -2. Update the helm chart code in `$alloyRepo/operations/helm`: - - 1. Update `Chart.yaml` with the new helm version and app version. - 2. Update `CHANGELOG.md` with a new section for the helm version. - 3. Run `make docs rebuild-tests` from the `operations/helm` directory. - -3. Open a PR, following the pattern in PR [#3782](https://github.com/grafana/alloy/pull/3782). diff --git a/docs/developer/release/07-update-homebrew.md b/docs/developer/release/07-update-homebrew.md deleted file mode 100644 index bbb83fa0e9..0000000000 --- a/docs/developer/release/07-update-homebrew.md +++ /dev/null @@ -1,18 +0,0 @@ -# Update Homebrew - -After a stable or patch release is created, a bot will automatically create a PR in the [homebrew-grafana][] repository. -The PR will bump the version of Alloy in Alloy's Brew formula. - -## Steps - -1. Navigate to the [homebrew-grafana][] repository. - -2. Find the PR which bumps the Alloy formula to the release that was just published. It will look like [this one][example-pr]. - -3. If needed, update the contents of the PR with any additional changes needed to support the new Alloy version. This - might mean updating the Go version, changing build tags, default config file contents, etc. - -4. Merge the PR. - -[homebrew-grafana]: https://github.com/grafana/homebrew-grafana -[example-pr]: https://github.com/grafana/homebrew-grafana/pull/89 diff --git a/docs/developer/release/08-announce-release.md b/docs/developer/release/08-announce-release.md deleted file mode 100644 index 691ebbcb8b..0000000000 --- a/docs/developer/release/08-announce-release.md +++ /dev/null @@ -1,15 +0,0 @@ -# Announce Release - -You made it! This is the last step for any release. - -## Steps - -1. Announce the release in the Grafana Labs Community `#alloy` channel. - - - Example Stable Release or Patch Release message: - - ``` - :alloy: Grafana Alloy RELEASE_VERSION is now available! :alloy: - Release: https://github.com/grafana/alloy/releases/tag/RELEASE_VERSION - Full changelog: https://github.com/grafana/alloy/blob/RELEASE_VERSION/CHANGELOG.md - ``` diff --git a/docs/developer/release/README.md b/docs/developer/release/README.md deleted file mode 100644 index 41c326944a..0000000000 --- a/docs/developer/release/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Releasing - -This document describes the process of creating a release for the -`grafana/alloy` repo. A release includes release assets for everything inside -the repository. - -The processes described here are for v1.0 and above. - -# Release Cycle - -A typical release cycle is to have a Release Candidate published for at least 48 -hours followed by a Stable Release. 0 or more Patch Releases may occur between the Stable Release -and the creation of the next Release Candidate. - -# Workflows - -Once a release is scheduled, a release shepherd is determined. This person will be -responsible for ownership of the following workflows: - -## First release candidate (`rc.0`) -1. [Ensure our OpenTelemetry Collector dependency has been updated](./00-ensure-otel-dep-updated.md) -2. [Create Release Branch](./01-create-release-branch.md) -3. [Update the "main" and "release/" branches](./02-update-version-in-code.md) -4. [Tag Release](./03-tag-release.md) -5. [Publish Release](./04-publish-release.md) -6. [Test Release](./05-test-release.md) -7. [Announce Release](./08-announce-release.md) - -## Additional release candidate (`rc.1`, `rc.2`...) -1. [Update the "main" and "release/" branches](./02-update-version-in-code.md) -2. [Tag Release](./03-tag-release.md) -3. [Publish Release](./04-publish-release.md) -4. [Test Release](./05-test-release.md) -5. [Announce Release](./08-announce-release.md) - -## Stable Release Publish (`1.2.0`, `1.6.0`...) -1. [Update the "main" and "release/" branches](./02-update-version-in-code.md) -2. [Tag Release](./03-tag-release.md) -3. [Publish Release](./04-publish-release.md) -4. [Test Release](./05-test-release.md) -5. [Update Helm Charts](./06-update-helm-charts.md) -6. [Update Homebrew](./07-update-homebrew.md) -7. [Announce Release](./08-announce-release.md) - -## Patch Release Publish - latest version (`1.15.1`, `1.15.2`...) -1. [Update the "main" and "release/" branches](./02-update-version-in-code.md) -2. [Tag Release](./03-tag-release.md) -3. [Publish Release](./04-publish-release.md) -4. [Test Release](./05-test-release.md) -5. [Update Helm Charts](./06-update-helm-charts.md) -6. [Update Homebrew](./07-update-homebrew.md) -7. [Announce Release](./08-announce-release.md) - -## Patch Release Publish - older version (`1.0.1`, `1.0.2`...) -- Not documented yet (but here are some hints) - - somewhat similar to Patch Release Publish (latest version) - - find the old release branch - - cherry-pick commit[s] into it - - don't update the version in the project on main - - changes go into the changelog under the patch release version plus stay in unreleased - - don't publish in github as latest release - - don't update deployment tools or helm charts diff --git a/docs/developer/release/concepts/version.md b/docs/developer/release/concepts/version.md deleted file mode 100644 index d7054f5e25..0000000000 --- a/docs/developer/release/concepts/version.md +++ /dev/null @@ -1,22 +0,0 @@ -# Version - -Grafana Alloy uses Semantic Versioning. The next version can be determined -by looking at the current version and incrementing it. - -## Version - -To determine the `VERSION` for a Stable Release or Patch Release, use the Semantic Version `vX.Y.Z`. - -To determine the `VERSION` for a Release Candidate, append `-rc.#` to the Semantic Version. - -- Examples - - For example, `v0.31.0` is the Stable Release `VERSION` for the v0.31.0 release. - - For example, `v0.31.1` is the first Patch Release `VERSION` for the v0.31.0 release. - - For example, `v0.31.0-rc.0` is the first Release Candidate `VERSION` for the v0.31.0 release. - -## Version Prefix - -To determine the `VERSION PREFIX`, use only the major and minor version `vX.Y`. - -- Examples - - `v0.31` diff --git a/docs/developer/shepherding-releases.md b/docs/developer/shepherding-releases.md new file mode 100644 index 0000000000..cbbf4f9136 --- /dev/null +++ b/docs/developer/shepherding-releases.md @@ -0,0 +1,130 @@ +# Shepherding releases + +Time to cut a release? Don't worry! It's pretty straightforward. Below are all the situations you +might encounter and how to handle them. + +## Cutting a new MINOR release + +### Prerequisites + +- [OTel dependencies](./updating-otel/README.md) should be updated every ~6 weeks. +- Prometheus dependencies should be updated every ~6 weeks. + +### 1. Run the `Create Release Branch` pinned workflow on the Actions page + +**_NOTE: Creating a release branch should be considered as "cutting off" the release. Past this +point, only critical fixes should be merged into the branch until the release is final._** + +1. Leave everything as it is except uncheck the `Dry run` box. +2. (This will create a new release branch, a new backport tag, and open a draft release-please PR.) + +### 2. When ready, cut an RC by running the `Create Release Candidate` pinned workflow on the Actions page + +1. For `Use workflow from`, select the release branch in question. +2. Make sure to uncheck the `Dry run` box. +3. (This will create a draft release on GitHub.) +4. Add any relevant changelog details to the RC draft release and publish it. +5. (This will trigger CI to build/publish release artifacts and attach them to the release.) + +### 3. (Optional) Add critical fixes to the release + +1. If you need to add critical fixes to the release branch after testing an RC, check out the + section below on backporting fixes to a release branch. + +### 4. When ready, move the release-please PR out of draft, review it, and merge it in + +1. You might realize that some changelog entries don't look the way you want. To address that, + check out the section below on modifying a PR's changelog entry after it's been merged. +1. (This will trigger CI to build/publish release artifacts and attach them to the release.) + +### 5. Validate the release on internal deployments + +1. Validate performance metrics are consistent with the prior version. +2. Validate components are healthy. + +### 6. Update Helm Chart + +1. Create PR against `main` to update the helm chart code as follows: + 1. Update `Chart.yaml` with the new helm version and app version. + 2. Update `CHANGELOG.md` with a new section for the helm version. + 3. Run `make docs rebuild-tests` from the `operations/helm` directory. + +### 7. Update Homebrew + +**_NOTE: Publishing a release should automatically create a PR against the Homebrew repo to bump the +version. If it doesn't you'll need to do this manually._** + +1. Navigate to the [homebrew-grafana](https://github.com/grafana/homebrew-grafana) repository. +2. Find the PR which bumps the Alloy formula to the release that was just published. It will look + like [this one](https://github.com/grafana/homebrew-grafana/pull/89). +3. If needed, update the contents of the PR with any additional changes needed to support the new + Alloy version. This might mean updating the Go version, changing build tags, default config file + contents, etc. +4. Merge the PR. + +### 8. Announce + +You did it! Now it's time to celebrate by announcing the Alloy release in the following Slack +channels: + +[#alloy (internal slack)](https://grafanalabs.enterprise.slack.com/archives/CSN5HV0CQ) + +[#alloy (community slack)](https://grafana.slack.com/archives/C01050C3D8F) + +**Message format:** + +``` +:alloy: Grafana Alloy is now available! :alloy: +Release: https://github.com/grafana/alloy/releases/tag/ +Full changelog: https://github.com/grafana/alloy/blob//CHANGELOG.md +``` + +## Cutting a new PATCH release + +The process for this is exactly the same as a minor release with two notable exceptions: + +1. You don't need to create a release branch. Just continue using the appropriate minor release + branch. +2. You need to ensure that the changes on the release branch are **only resulting in a patch version + bump**. If they're not, follow the steps below for modifying PR changelog entries, or if you + truly goofed and backported a feature instead of a fix, revert it and update changelog entries as + necessary. + +## Modifying a PR's CHANGELOG entry post-merge + +By default, the semantics of each commit message (derived from PR titles) become the basis for +changelog entries. If you need to change one after the PR has already been merged, do the following: + +1. Navigate to the PR in question +2. Edit the PR's description and append a block such as the following to the bottom of it: + ``` + BEGIN_COMMIT_OVERRIDE + feat: this is the overridden semantic commit title + END_COMMIT_OVERRIDE + ``` + +If you need to mark something as a **breaking change**, use the following: + +``` +BEGIN_COMMIT_OVERRIDE +feat!: this is the overridden semantic commit title + +BREAKING-CHANGE: This is where you write a detailed description about the breaking change. You can use markdown if needed. +END_COMMIT_OVERRIDE +``` + +## Backporting a fix to a release branch + +This is super simple! Once you have a PR with a fix on the `main` branch (doesn't matter if it's +open or merged), do the following: + +1. Add a backport label to it (e.g. `backport/v1.12`). +2. Merge the PR. + +The order of the above steps does not matter. + +Once the PR is merged AND has the label, a backport PR will automatically be created for the PR +against the appropriate release branch. Merge it in and you're done! + +If there's no other pending content on the release branch, you'll see a new release-please PR get +created for the next release. diff --git a/docs/developer/updating-otel/README.md b/docs/developer/updating-otel/README.md index ff2dfa7e60..0d3ea22d53 100644 --- a/docs/developer/updating-otel/README.md +++ b/docs/developer/updating-otel/README.md @@ -1,6 +1,13 @@ # Updating OpenTelemetry Collector dependencies +## Overview + +Every ~6 weeks, a release should include an update to a newer version of the OpenTelemetry Collector +(when available). The OpenTelemetry release cadence is three times more frequent. Therefore, this +update should happen 1-2 weeks out from release cutoff. + Alloy depends on various OpenTelemetry (Otel) modules such as these: + ``` github.com/open-telemetry/opentelemetry-collector-contrib/exporter/jaegerexporter github.com/open-telemetry/opentelemetry-collector-contrib/extension/sigv4authextension @@ -13,115 +20,149 @@ go.opentelemetry.io/otel/sdk The dependencies mostly come from these repositories: -* [opentelemetry-collector](https://github.com/open-telemetry/opentelemetry-collector) -* [opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib) -* [opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go) +- [opentelemetry-collector](https://github.com/open-telemetry/opentelemetry-collector) +- [opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib) +- [opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go) Unfortunately, updating Otel dependencies is not straightforward: -* Some of the modules in `opentelemetry-collector` come from a [grafana/opentelemetry-collector](https://github.com/grafana/opentelemetry-collector) fork. - * This is mostly so that we can include metrics of Collector components with the metrics shown under Alloy's `/metrics` endpoint. -* All Collector and Collector-Contrib dependencies should be updated at the same time, because they +- Some of the modules in `opentelemetry-collector` come from a + [grafana/opentelemetry-collector](https://github.com/grafana/opentelemetry-collector) fork. + - This is mostly so that we can include metrics of Collector components with the metrics shown + under Alloy's `/metrics` endpoint. +- All Collector and Collector-Contrib dependencies should be updated at the same time, because they are kept in sync on the same version. - * E.g. if we use `v0.85.0` of `go.opentelemetry.io/collector`, we also use `v0.85.0` of `spanmetricsconnector`. - * This is in line with how the Collector itself imports dependencies. - * It helps us avoid bugs. - * It makes it easier to communicate to customers the version of Collector which we use in Alloy. - * Unfortunately, updating everything at once makes it tedious to check if any of our docs or code need updating due to changes in Collector components. A lot of these checks are manual - for example, cross checking the Otel config and Otel documentation between versions. - * There are some exceptions for modules which don't follow the same versioning. For example, `collector/pdata` is usually on a different version, like `v1.0.0-rcv0013`. + - E.g. if we use `v0.85.0` of `go.opentelemetry.io/collector`, we also use `v0.85.0` of + `spanmetricsconnector`. + - This is in line with how the Collector itself imports dependencies. + - It helps us avoid bugs. + - It makes it easier to communicate to customers the version of Collector which we use in Alloy. + - Unfortunately, updating everything at once makes it tedious to check if any of our docs or code + need updating due to changes in Collector components. A lot of these checks are manual - for + example, cross checking the Otel config and Otel documentation between versions. + - There are some exceptions for modules which don't follow the same versioning. For example, + `collector/pdata` is usually on a different version, like `v1.0.0-rcv0013`. ## Updating walkthrough ### Update the Grafana fork of Otel Collector -1. Create a new release branch from the [opentelemetry release branch](https://github.com/open-telemetry/opentelemetry-collector) with a `-grafana` suffix under [grafana/opentelemetry-collector](https://github.com/grafana/opentelemetry-collector). For example, if porting branch `v0.86.0`, make a branch under the fork repo called `0.86-grafana`. +1. Create a new release branch from the + [opentelemetry release branch](https://github.com/open-telemetry/opentelemetry-collector) with a + `-grafana` suffix under + [grafana/opentelemetry-collector](https://github.com/grafana/opentelemetry-collector). For + example, if porting branch `v0.86.0`, make a branch under the fork repo called `0.86-grafana`. 2. Check which branch of the fork repo Alloy currently uses. 3. See what commits were pushed onto that branch to customize it. -4. Create a PR to cherry-pick the same commits to the new branch. See the [changes to the 0.85 branch](https://github.com/grafana/opentelemetry-collector/pull/8) for an example PR. +4. Create a PR to cherry-pick the same commits to the new branch. See the + [changes to the 0.85 branch](https://github.com/grafana/opentelemetry-collector/pull/8) for an + example PR. 5. Run `make` on the branch to make sure it builds and that the tests pass. ### Update Alloy's dependencies -1. Make sure we use the same version of Collector and Collector-Contrib for all relevant modules. For example, if we use version `v0.86.0` of Collector, we should also use version `v0.86.0` for all Contrib modules. -2. Update the `replace` directives in the go.mod file to point to the latest commit of the forked release branch. Use a command like this: +1. Make sure we use the same version of Collector and Collector-Contrib for all relevant modules. + For example, if we use version `v0.86.0` of Collector, we should also use version `v0.86.0` for + all Contrib modules. +2. Update the `replace` directives in the go.mod file to point to the latest commit of the forked + release branch. Use a command like this: ``` go mod edit -replace=go.opentelemetry.io/collector=github.com/grafana/opentelemetry-collector@asdf123jkl ``` - Repeat this for any other modules where a replacement is necessary. For debugging purposes, you can first have the replace directive pointing to your local repo. -3. Note that sometimes Collector depends on packages with "rc" versions such as `v1.0.0-rcv0013`. This is ok, as long as the go.mod of Collector also references the same versions - for example, [pdata](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.81.0/go.mod#L25) and [featuregate](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.81.0/go.mod#L24). + Repeat this for any other modules where a replacement is necessary. For debugging purposes, you + can first have the replace directive pointing to your local repo. +3. Note that sometimes Collector depends on packages with "rc" versions such as `v1.0.0-rcv0013`. + This is ok, as long as the go.mod of Collector also references the same versions - for example, + [pdata](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.81.0/go.mod#L25) and + [featuregate](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.81.0/go.mod#L24). ### Update otelcol Alloy components 1. Note which Otel components are in use by Alloy. - * For every "otelcol" Alloy component there is usually a corresponding Collector component. - * For example, the Otel component used by [otelcol.auth.sigv4](https://grafana.com/docs/alloy/latest/reference/components/otelcol.auth.sigv4/) is [sigv4auth](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/sigv4authextension). - * In some cases we don't use the corresponding Collector component: - * For example, [otelcol.receiver.prometheus](https://grafana.com/docs/alloy/latest/reference/components/otelcol.receiver.prometheus/) and [otelcol.exporter.prometheus](https://grafana.com/docs/alloy/latest/reference/components/otelcol.exporter.prometheus/). - * Those components usually have a note like this: - > NOTE: otelcol.exporter.prometheus is a custom component unrelated to the prometheus exporter from OpenTelemetry Collector. + - For every "otelcol" Alloy component there is usually a corresponding Collector component. + - For example, the Otel component used by + [otelcol.auth.sigv4](https://grafana.com/docs/alloy/latest/reference/components/otelcol.auth.sigv4/) + is + [sigv4auth](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/sigv4authextension). + - In some cases we don't use the corresponding Collector component: + - For example, + [otelcol.receiver.prometheus](https://grafana.com/docs/alloy/latest/reference/components/otelcol.receiver.prometheus/) + and + [otelcol.exporter.prometheus](https://grafana.com/docs/alloy/latest/reference/components/otelcol.exporter.prometheus/). + - Those components usually have a note like this: + > NOTE: otelcol.exporter.prometheus is a custom component unrelated to the prometheus + > exporter from OpenTelemetry Collector. 2. Make a list of the components which have changed since the previously used version. - 1. Go through the changelogs of both [Collector](https://github.com/open-telemetry/opentelemetry-collector/releases) and [Collector-Contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/releases). + 1. Go through the changelogs of both + [Collector](https://github.com/open-telemetry/opentelemetry-collector/releases) and + [Collector-Contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/releases). 2. If a component which is in use by Alloy has changed, note it down. 3. For each Otel component which has changed, compare how they changed. 1. Compare the old and new version of Otel's documentation. 2. Compare the config.go file to see if new parameters were added. 4. Update Alloy's code and documentation where needed. - * Pay attention to stability labels: - * Never lower the stability label in Alloy. E.g. if the stability - of an Otel component is "alpha", there are cases where it might be - stable in Alloy and that is ok. Stability labels in Alloy can - be increased, but not decreased. - * If the stability level of an Otel component has increased, consult - the rest of the team on whether the stability of the corresponding - Alloy component should also be increased. - * Search the Alloy repository for the old version (e.g. "0.87") to find code and - documentation which also needs updating. - * Update the `OTEL_VERSION` parameter in the `docs/sources/_index.md.t` file. - Then run `make generate-versioned-files`, which will update `docs/sources/_index.md`. + - Pay attention to stability labels: + - Never lower the stability label in Alloy. E.g. if the stability of an Otel component is + "alpha", there are cases where it might be stable in Alloy and that is ok. Stability labels + in Alloy can be increased, but not decreased. + - If the stability level of an Otel component has increased, consult the rest of the team on + whether the stability of the corresponding Alloy component should also be increased. + - Search the Alloy repository for the old version (e.g. "0.87") to find code and documentation + which also needs updating. + - Update the `OTEL_VERSION` parameter in the `docs/sources/_index.md.t` file. Then run + `make generate-versioned-files`, which will update `docs/sources/_index.md`. 5. Some alloy components reuse OpenTelemetry code, but do not import it: - * `otelcol.extension.jaeger_remote_sampling`: a lot of this code has - been copy-pasted from Otel and modified slightly to fit Alloy's needs. - This component needs to be updated by copy-pasting the new Otel code - and modifying it again. -6. Note that we don't port every single config option which OpenTelemetry Collector exposes. - For example, Collector's [oauth2client extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/v0.85.0/extension/oauth2clientauthextension) supports `client_id_file` and `client_secret_file` - parameters. However, Alloy's [otelcol.auth.oauth2](https://grafana.com/docs/alloy/latest/reference/components/otelcol.auth.oauth2/) does not support them because the idiomatic way of doing the same - in Alloy is to use the local.file component. -7. When updating semantic conventions, check those the changelogs of those repositories for breaking changes: - * [opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go/releases) - * [semantic-conventions](https://github.com/open-telemetry/semantic-conventions/releases) - * [opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification/releases) - -You can refer to [PR grafana/agent#5290](https://github.com/grafana/agent/pull/5290) -for an example on how to update Alloy. + - `otelcol.extension.jaeger_remote_sampling`: a lot of this code has been copy-pasted from Otel + and modified slightly to fit Alloy's needs. This component needs to be updated by copy-pasting + the new Otel code and modifying it again. +6. Note that we don't port every single config option which OpenTelemetry Collector exposes. For + example, Collector's + [oauth2client extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/v0.85.0/extension/oauth2clientauthextension) + supports `client_id_file` and `client_secret_file` parameters. However, Alloy's + [otelcol.auth.oauth2](https://grafana.com/docs/alloy/latest/reference/components/otelcol.auth.oauth2/) + does not support them because the idiomatic way of doing the same in Alloy is to use the + local.file component. +7. When updating semantic conventions, check those the changelogs of those repositories for breaking + changes: + - [opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go/releases) + - [semantic-conventions](https://github.com/open-telemetry/semantic-conventions/releases) + - [opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification/releases) + +You can refer to [PR grafana/agent#5290](https://github.com/grafana/agent/pull/5290) for an example +on how to update Alloy. ### Notify community component contributors -You can find the community contributor GitHub handles at the top of the community component files (the components that have "Community" set to true). -Once the PR is created, you can ping them in the PR and message them on Slack. Each contributor should create a PR in your branch with the updated component. -Notify the contributors a few days before the release so they have enough time to do the update. -It should not be a blocker for the release. If the contributors can't do the update, you can fix the code to resolve the breaking changes or disable the component if that's too complicated. +You can find the community contributor GitHub handles at the top of the community component files +(the components that have "Community" set to true). Once the PR is created, you can ping them in the +PR and message them on Slack. Each contributor should create a PR in your branch with the updated +component. Notify the contributors a few days before the release so they have enough time to do the +update. It should not be a blocker for the release. If the contributors can't do the update, you can +fix the code to resolve the breaking changes or disable the component if that's too complicated. ### Check for metric updates -Some Otel metrics are used in the k8s monitoring helm chart and in integrations. -Make sure to update this [list](https://github.com/grafana/k8s-monitoring-helm/blob/main/charts/k8s-monitoring-v1/default_allow_lists/alloy_integration.yaml) if any of these metrics has been removed or renamed. +Some Otel metrics are used in the k8s monitoring helm chart and in integrations. Make sure to update +this +[list](https://github.com/grafana/k8s-monitoring-helm/blob/main/charts/k8s-monitoring-v1/default_allow_lists/alloy_integration.yaml) +if any of these metrics has been removed or renamed. ## Testing ### Testing a tracing pipeline locally Firstly, start a K6 trace generator to simulate an application instrumented for tracing: + ``` cd docs/developer/updating-otel/k6-trace-gen/ docker compose up -d ``` -K6 will be configured to send traces on `ENDPOINT=host.docker.internal:4320`. -This means that the local Alloy instance must be configured to accept traces on `0.0.0.0:4320`. +K6 will be configured to send traces on `ENDPOINT=host.docker.internal:4320`. This means that the +local Alloy instance must be configured to accept traces on `0.0.0.0:4320`. -The ["otelcol" components][otelcol-components] are the only components which use OTel. -Try to test as many of them as possible using a config file like this one: +The ["otelcol" components][otelcol-components] are the only components which use OTel. Try to test +as many of them as possible using a config file like this one: [otelcol-components](https://grafana.com/docs/alloy/latest/reference/components/otelcol/) @@ -188,10 +229,12 @@ otelcol.exporter.otlp "default" { -Run this file for two types of Alloy instances - an upgraded one, and another one built using the codebase of the `main` branch. Check the following: +Run this file for two types of Alloy instances - an upgraded one, and another one built using the +codebase of the `main` branch. Check the following: -* Open `localhost:12345/metrics` in your browser for both Alloy instances. - * Are new metrics added? Mention them in the changelog. - * Are metrics missing? Did any metrics change names? If it's intended, mention them in the changelog and the upgrade guide. -* Check the logs for errors or anything else that's suspicious. -* Check Tempo to make sure the traces were received. +- Open `localhost:12345/metrics` in your browser for both Alloy instances. + - Are new metrics added? Mention them in the changelog. + - Are metrics missing? Did any metrics change names? If it's intended, mention them in the + changelog and the upgrade guide. +- Check the logs for errors or anything else that's suspicious. +- Check Tempo to make sure the traces were received. diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000000..53214d1f31 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "changelog-sections": [ + { "type": "feat", "section": "Features 🌟" }, + { "type": "fix", "section": "Bug Fixes 🐛" }, + { "type": "docs", "section": "Documentation", "hidden": true }, + { "type": "style", "section": "Styles", "hidden": true }, + { "type": "refactor", "section": "Refactoring", "hidden": true }, + { "type": "perf", "section": "Performance", "hidden": true }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "build", "section": "Build", "hidden": true }, + { "type": "ci", "section": "CI", "hidden": true }, + { "type": "chore", "section": "Chores", "hidden": true }, + { "type": "revert", "section": "Reverts", "hidden": true } + ], + "draft-pull-request": true, + "packages": { + ".": { + "release-type": "go", + "versioning": "minor-breaking", + "changelog-path": "CHANGELOG.md", + "extra-files": [ + { + "type": "generic", + "path": "docs/sources/_index.md" + } + ] + } + } +} diff --git a/tools/gen-versioned-files/gen-versioned-files.sh b/tools/gen-versioned-files/gen-versioned-files.sh deleted file mode 100755 index 140fa6fe12..0000000000 --- a/tools/gen-versioned-files/gen-versioned-files.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -ALLOY_VERSION=$(sed -e '/^#/d' -e '/^$/d' VERSION | tr -d '\n') - -if [ -z "$ALLOY_VERSION" ]; then - echo "ALLOY_VERSION can't be found. Are you running this from the repo root?" - exit 1 -fi - -versionMatcher='^v[0-9]+\.[0-9]+\.[0-9]$' - -if ! echo "$ALLOY_VERSION" | grep -Eq "$versionMatcher"; then - echo "ALLOY_VERSION env var is not in the correct format. It should be in the format of vX.Y.Z" - exit 1 -fi - -templates=$(find . -type f -name "*.t" -not -path "./.git/*") -for template in $templates; do - echo "Generating ${template%.t}" - sed -e "s/\$ALLOY_VERSION/$ALLOY_VERSION/g" < "$template" > "${template%.t}" -done diff --git a/tools/go.mod b/tools/go.mod index 54542a1461..89d45795a2 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -5,6 +5,7 @@ go 1.25.1 require ( github.com/google/go-github/v57 v57.0.0 github.com/openai/openai-go/v3 v3.7.0 + golang.org/x/mod v0.31.0 golang.org/x/oauth2 v0.32.0 ) diff --git a/tools/go.sum b/tools/go.sum index 6f581357ab..ad3155c8f9 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -17,6 +17,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tools/publish-release-assets.sh b/tools/publish-release-assets.sh new file mode 100644 index 0000000000..37e325a88c --- /dev/null +++ b/tools/publish-release-assets.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# This script prepares and uploads release assets to an existing GitHub release. +# It should be run from the root of the repository after dist artifacts have been +# built and signed Windows executables have been placed in the dist directory. +# +# Required environment variables: +# RELEASE_TAG - The release tag to upload assets to (e.g., v1.0.0) +# GH_TOKEN - GitHub token with write access to releases +# +set -euxo pipefail + +if [ -z "${RELEASE_TAG:-}" ]; then + echo "Error: RELEASE_TAG environment variable is required" + exit 1 +fi + +# Disable xtrace to avoid leaking GH_TOKEN in logs +set +x +if [ -z "${GH_TOKEN:-}" ]; then + echo "Error: GH_TOKEN environment variable is required" + exit 1 +fi +# Re-enable xtrace +set -x + +# Zip up all the binaries to reduce the download size. DEBs and RPMs +# aren't included to be easier to work with. +find dist/ -type f \ + -name 'alloy*' -not -name '*.deb' -not -name '*.rpm' -not -name 'alloy-installer-windows-*.exe' \ + -exec zip -j -m "{}.zip" "{}" \; + +# For the Windows installer only, we want to keep the original .exe file and create a zipped copy. +find dist/ -type f \ + -name 'alloy-installer-windows-*.exe' \ + -exec zip -j "{}.zip" "{}" \; + +# Generate SHA256 checksums for all release assets. +pushd dist && sha256sum -- * > SHA256SUMS && popd + +# Upload all assets to the existing GitHub release. +gh release upload "${RELEASE_TAG}" dist/* --clobber diff --git a/tools/release b/tools/release deleted file mode 100755 index f8df4d09aa..0000000000 --- a/tools/release +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -# -# This script should be run from the root of the repository. -set -ex - -# Zip up all the binaries to reduce the download size. DEBs and RPMs -# aren't included to be easier to work with. -find dist/ -type f \ - -name 'alloy*' -not -name '*.deb' -not -name '*.rpm' -not -name 'alloy-installer-windows-*.exe' \ - -exec zip -j -m "{}.zip" "{}" \; - -# For the Windows installer only, we want to keep the original .exe file and create a zipped copy. -find dist/ -type f \ - -name 'alloy-installer-windows-*.exe' \ - -exec zip -j "{}.zip" "{}" \; - -# Get the SHA256SUMS before continuing. -pushd dist && sha256sum -- * > SHA256SUMS && popd || exit - -ghr \ - -t "${GITHUB_TOKEN}" \ - -u "grafana" \ - -r "alloy" \ - -b="$(envsubst < ./tools/release-note.md)" \ - -delete -draft \ - "${VERSION}" ./dist/ diff --git a/tools/release/backport/main.go b/tools/release/backport/main.go new file mode 100644 index 0000000000..9848732c81 --- /dev/null +++ b/tools/release/backport/main.go @@ -0,0 +1,184 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "strings" + + "github.com/google/go-github/v57/github" + + "github.com/grafana/alloy/tools/release/internal/git" + gh "github.com/grafana/alloy/tools/release/internal/github" +) + +func main() { + var ( + prNumber int + label string + dryRun bool + ) + flag.IntVar(&prNumber, "pr", 0, "PR number to backport") + flag.StringVar(&label, "label", "", "Backport label (e.g., backport/v1.15)") + flag.BoolVar(&dryRun, "dry-run", false, "Dry run (do not create PR)") + flag.Parse() + + if prNumber == 0 { + log.Fatal("PR number is required (use --pr flag)") + } + if label == "" { + log.Fatal("Label is required (use --label flag)") + } + + // Parse version from label (backport/v1.15 -> v1.15) + version := strings.TrimPrefix(label, "backport/") + if version == label { + log.Fatalf("Invalid backport label format: %s (expected backport/vX.Y)", label) + } + if !strings.HasPrefix(version, "v") { + log.Fatalf("Invalid version format: %s (expected vX.Y)", version) + } + + targetBranch := fmt.Sprintf("release/%s", version) + backportBranch := fmt.Sprintf("backport/pr-%d-to-%s", prNumber, version) + backportMarker := fmt.Sprintf("chore: backport #%d", prNumber) + + fmt.Printf("🍒 Backporting PR #%d to %s\n", prNumber, targetBranch) + + ctx := context.Background() + + client, err := gh.NewClientFromEnv(ctx) + if err != nil { + log.Fatal(err) + } + + // Verify the target release branch exists + exists, err := git.BranchExistsOnRemote(targetBranch) + if err != nil { + log.Fatalf("Failed to check if target branch exists: %v", err) + } + if !exists { + log.Fatalf("Target branch %s does not exist", targetBranch) + } + + // Check if backport branch already exists (means there's an open PR or work in progress) + branchExists, err := git.BranchExistsOnRemote(backportBranch) + if err != nil { + log.Fatalf("Failed to check if backport branch exists: %v", err) + } + if branchExists { + fmt.Printf("ℹ️ Backport branch %s already exists\n", backportBranch) + return + } + + // Get the original PR details + originalPR, err := client.GetPR(ctx, prNumber) + if err != nil { + log.Fatalf("Failed to get original PR: %v", err) + } + + // Get the app identity for git commits + appIdentity, err := client.GetAppIdentity(ctx) + if err != nil { + log.Fatalf("Failed to get app identity: %v", err) + } + + // Check if backport was already merged by looking for the marker in the release branch history + alreadyMerged, err := client.CommitExistsWithPattern(ctx, gh.FindCommitParams{ + Branch: targetBranch, + Pattern: backportMarker, + }) + if err != nil { + log.Fatalf("Failed to check for existing backport commit: %v", err) + } + if alreadyMerged { + fmt.Printf("ℹ️ Backport already merged (found commit with %s in %s)\n", backportMarker, targetBranch) + return + } + + // Find the commit on main that corresponds to this PR + commitSHA, err := client.FindCommitWithPattern(ctx, gh.FindCommitParams{ + Branch: "main", + Pattern: fmt.Sprintf("(#%d)", prNumber), + }) + if err != nil { + log.Fatalf("Failed to find commit for PR #%d: %v", prNumber, err) + } + fmt.Printf(" Found commit: %s\n", commitSHA) + fmt.Printf(" Backport branch: %s\n", backportBranch) + + if dryRun { + fmt.Println("\n🏃 DRY RUN - No changes made") + fmt.Printf("Would create backport branch: %s\n", backportBranch) + fmt.Printf("Would cherry-pick commit: %s\n", commitSHA) + fmt.Printf("Would create PR: %s → %s\n", backportBranch, targetBranch) + return + } + + // Configure git with app identity for commit authorship + if err := git.ConfigureUser(appIdentity.Name, appIdentity.Email); err != nil { + log.Fatalf("Failed to configure git: %v", err) + } + + // Fetch target branch for cherry-pick + if err := git.Fetch(targetBranch); err != nil { + log.Fatalf("Failed to fetch target branch: %v", err) + } + + // Create backport branch from target branch + if err := git.CreateBranchFrom(backportBranch, "origin/"+targetBranch); err != nil { + log.Fatalf("Failed to create backport branch: %v", err) + } + + // Cherry-pick the commit + if err := git.CherryPick(commitSHA); err != nil { + log.Fatalf("Failed to cherry-pick commit: %v\n\nThis may be due to conflicts. Please create the backport manually.", err) + } + + // Push the backport branch + if err := git.Push(backportBranch); err != nil { + log.Fatalf("Failed to push backport branch: %v", err) + } + + fmt.Printf("✅ Pushed backport branch: %s\n", backportBranch) + + // Create the backport PR + backportPR, err := createBackportPR(ctx, client, originalPR, backportBranch, targetBranch, backportMarker) + if err != nil { + log.Fatalf("Failed to create backport PR: %v", err) + } + + fmt.Printf("✅ Created backport PR: %s\n", backportPR.GetHTMLURL()) +} + +func createBackportPR(ctx context.Context, client *gh.Client, originalPR *github.PullRequest, backportBranch, targetBranch, backportMarker string) (*github.PullRequest, error) { + body := fmt.Sprintf(`## Backport of #%d + +This PR backports #%d to %s. + +### Original PR +- **Title:** %s +- **Author:** @%s + +### Description +%s + +--- +*This backport was created automatically.* +`, + originalPR.GetNumber(), + originalPR.GetNumber(), + targetBranch, + originalPR.GetTitle(), + originalPR.GetUser().GetLogin(), + originalPR.GetBody(), + ) + + return client.CreatePR(ctx, gh.CreatePRParams{ + Title: backportMarker, + Head: backportBranch, + Base: targetBranch, + Body: body, + }) +} diff --git a/tools/release/create-rc/main.go b/tools/release/create-rc/main.go new file mode 100644 index 0000000000..fe83582d95 --- /dev/null +++ b/tools/release/create-rc/main.go @@ -0,0 +1,209 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "regexp" + "sort" + "strconv" + + "github.com/google/go-github/v57/github" + + gh "github.com/grafana/alloy/tools/release/internal/github" + "github.com/grafana/alloy/tools/release/internal/version" +) + +// prereleaseParams holds parameters for creating a draft prerelease. +type prereleaseParams struct { + Tag string // Tag name (e.g., "v1.0.0-rc.0") + TargetSHA string // Commit SHA to tag + Version string // Version string without 'v' prefix (e.g., "1.0.0") + RCNumber int // Release candidate number + PRNumber int // Associated release-please PR number +} + +func main() { + var ( + dryRun bool + releaseBranch string + ) + flag.BoolVar(&dryRun, "dry-run", false, "Dry run (do not create tag or release)") + flag.StringVar(&releaseBranch, "branch", "", "Release branch to create RC for (e.g., release/v1.15)") + flag.Parse() + + if releaseBranch == "" { + log.Fatal("Release branch is required (use --branch flag, e.g., --branch release/v1.15)") + } + + if _, err := version.ParseReleaseBranch(releaseBranch); err != nil { + log.Fatal(err) + } + + ctx := context.Background() + + client, err := gh.NewClientFromEnv(ctx) + if err != nil { + log.Fatal(err) + } + + // Find the release-please PR for the specified branch + pr, err := findReleasePleasePR(ctx, client, releaseBranch) + if err != nil { + log.Fatalf("Failed to find release-please PR: %v", err) + } + + fmt.Printf("Found release-please PR #%d: %s\n", pr.GetNumber(), pr.GetTitle()) + fmt.Printf("Base branch: %s\n", pr.GetBase().GetRef()) + fmt.Printf("Head branch: %s\n", pr.GetHead().GetRef()) + + // Extract version from PR title + ver, err := extractVersionFromTitle(pr.GetTitle()) + if err != nil { + log.Fatalf("Failed to extract version from PR title: %v", err) + } + fmt.Printf("Target version: %s\n", ver) + + // Find existing RC tags and determine next RC number + rcNumber, err := findNextRCNumber(ctx, client, ver) + if err != nil { + log.Fatalf("Failed to determine next RC number: %v", err) + } + + rcTag := fmt.Sprintf("v%s-rc.%d", ver, rcNumber) + fmt.Printf("Next RC tag: %s\n", rcTag) + + if dryRun { + fmt.Println("\n🏃 DRY RUN - No changes made") + fmt.Printf("Would create tag: %s\n", rcTag) + fmt.Printf("From branch: %s\n", pr.GetHead().GetRef()) + return + } + + // Get the SHA of the PR branch head + branchSHA := pr.GetHead().GetSHA() + fmt.Printf("Branch HEAD SHA: %s\n", branchSHA) + + // Create draft prerelease (this also creates the tag - GitHub signs tags created via Releases API) + releaseURL, err := createDraftPrerelease(ctx, client, prereleaseParams{ + Tag: rcTag, + TargetSHA: branchSHA, + Version: ver, + RCNumber: rcNumber, + PRNumber: pr.GetNumber(), + }) + if err != nil { + log.Fatalf("Failed to create draft prerelease: %v", err) + } + fmt.Printf("✅ Created tag: %s\n", rcTag) + fmt.Printf("✅ Created draft prerelease: %s\n", releaseURL) +} + +func findReleasePleasePR(ctx context.Context, client *gh.Client, baseBranch string) (*github.PullRequest, error) { + opts := &github.PullRequestListOptions{ + State: "open", + Base: baseBranch, + ListOptions: github.ListOptions{ + PerPage: 100, + }, + } + + prs, _, err := client.API().PullRequests.List(ctx, client.Owner(), client.Repo(), opts) + if err != nil { + return nil, fmt.Errorf("listing pull requests: %w", err) + } + + // Look for PR with "autorelease: pending" label + for _, pr := range prs { + for _, label := range pr.Labels { + if label.GetName() == "autorelease: pending" { + return pr, nil + } + } + } + + // Fallback: look for PR with release-please title pattern + titlePattern := regexp.MustCompile(fmt.Sprintf(`^chore\(%s\): release`, regexp.QuoteMeta(baseBranch))) + for _, pr := range prs { + if titlePattern.MatchString(pr.GetTitle()) { + return pr, nil + } + } + + return nil, fmt.Errorf("no release-please PR found for branch %s (looked for 'autorelease: pending' label or release-please title pattern)", baseBranch) +} + +func extractVersionFromTitle(title string) (string, error) { + pattern := regexp.MustCompile(`release\s+(\d+\.\d+\.\d+)`) + matches := pattern.FindStringSubmatch(title) + if len(matches) < 2 { + return "", fmt.Errorf("could not extract version from title: %s", title) + } + return matches[1], nil +} + +func findNextRCNumber(ctx context.Context, client *gh.Client, ver string) (int, error) { + opts := &github.ListOptions{PerPage: 100} + var allTags []*github.RepositoryTag + + for { + tags, resp, err := client.API().Repositories.ListTags(ctx, client.Owner(), client.Repo(), opts) + if err != nil { + return 0, fmt.Errorf("listing tags: %w", err) + } + allTags = append(allTags, tags...) + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + // Find existing RC tags for this version + rcPattern := regexp.MustCompile(fmt.Sprintf(`^v%s-rc\.(\d+)$`, regexp.QuoteMeta(ver))) + var rcNumbers []int + + for _, tag := range allTags { + matches := rcPattern.FindStringSubmatch(tag.GetName()) + if len(matches) == 2 { + num, _ := strconv.Atoi(matches[1]) + rcNumbers = append(rcNumbers, num) + } + } + + if len(rcNumbers) == 0 { + return 0, nil + } + + sort.Ints(rcNumbers) + return rcNumbers[len(rcNumbers)-1] + 1, nil +} + +func createDraftPrerelease(ctx context.Context, client *gh.Client, p prereleaseParams) (string, error) { + body := fmt.Sprintf(`## Release Candidate %d for v%s + +This is a **release candidate** and should be used for testing purposes only. + +**⚠️ This is a pre-release. Do not use in production.** + +### Changes + +See the [release PR #%d](https://github.com/%s/%s/pull/%d) for the full changelog. +`, p.RCNumber, p.Version, p.PRNumber, client.Owner(), client.Repo(), p.PRNumber) + + release := &github.RepositoryRelease{ + TagName: github.String(p.Tag), + TargetCommitish: github.String(p.TargetSHA), // GitHub creates & signs the tag when using Releases API + Name: github.String(p.Tag), + Body: github.String(body), + Draft: github.Bool(true), + Prerelease: github.Bool(true), + } + + created, _, err := client.API().Repositories.CreateRelease(ctx, client.Owner(), client.Repo(), release) + if err != nil { + return "", fmt.Errorf("creating release: %w", err) + } + + return created.GetHTMLURL(), nil +} diff --git a/tools/release/create-release-branch/main.go b/tools/release/create-release-branch/main.go new file mode 100644 index 0000000000..5ccc252e0a --- /dev/null +++ b/tools/release/create-release-branch/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "sort" + "strings" + + gh "github.com/grafana/alloy/tools/release/internal/github" + "github.com/grafana/alloy/tools/release/internal/version" +) + +const ( + // backportLabelColor is the hex color for backport labels (without '#' prefix). + backportLabelColor = "63a504" + // backportLabelPrefix is the prefix for backport labels. + backportLabelPrefix = "backport/v" + // maxBackportLabels is the maximum number of backport labels to keep. + maxBackportLabels = 3 +) + +func main() { + var ( + dryRun bool + sourceRef string + ) + flag.BoolVar(&dryRun, "dry-run", false, "Dry run (do not create branch)") + flag.StringVar(&sourceRef, "source", "main", "Source ref to branch from") + flag.Parse() + + ctx := context.Background() + + client, err := gh.NewClientFromEnv(ctx) + if err != nil { + log.Fatal(err) + } + + // Read manifest to determine current version + manifest, err := client.ReadManifest(ctx, sourceRef) + if err != nil { + log.Fatalf("Failed to read manifest: %v", err) + } + + currentVersion, ok := manifest["."] + if !ok { + log.Fatal("No root version found in manifest (expected '.' key)") + } + fmt.Printf("Current version in manifest: %s\n", currentVersion) + + // Calculate next minor version + nextMinor, err := version.NextMinor(currentVersion) + if err != nil { + log.Fatalf("Failed to calculate next minor version: %v", err) + } + fmt.Printf("Next minor version: %s\n", nextMinor) + + branchName := fmt.Sprintf("release/v%s", nextMinor) + fmt.Printf("Release branch: %s\n", branchName) + + backportLabel := fmt.Sprintf("backport/v%s", nextMinor) + fmt.Printf("Backport label: %s\n", backportLabel) + + // Check if branch already exists + exists, err := client.BranchExists(ctx, branchName) + if err != nil { + log.Fatalf("Failed to check if branch exists: %v", err) + } + if exists { + log.Fatalf("Branch %s already exists", branchName) + } + + if dryRun { + fmt.Println("\n🏃 DRY RUN - No changes made") + fmt.Printf("Would create branch: %s\n", branchName) + fmt.Printf("Would create label: %s\n", backportLabel) + fmt.Printf("From: %s\n", sourceRef) + return + } + + // Get the SHA of the source ref + sourceSHA, err := client.GetRefSHA(ctx, sourceRef) + if err != nil { + log.Fatalf("Failed to get SHA for %s: %v", sourceRef, err) + } + fmt.Printf("Source SHA: %s\n", sourceSHA) + + // Create the branch + err = client.CreateBranch(ctx, gh.CreateBranchParams{ + Branch: branchName, + SHA: sourceSHA, + }) + if err != nil { + log.Fatalf("Failed to create branch: %v", err) + } + + fmt.Printf("✅ Created branch: %s\n", branchName) + fmt.Printf("🔗 https://github.com/%s/%s/tree/%s\n", client.Owner(), client.Repo(), branchName) + + // Create the backport label + err = client.CreateLabel(ctx, gh.CreateLabelParams{ + Name: backportLabel, + Color: backportLabelColor, + Description: fmt.Sprintf("Backport to %s", branchName), + }) + if err != nil { + log.Fatalf("Failed to create label: %v", err) + } + + fmt.Printf("✅ Created label: %s\n", backportLabel) + + // Clean up old backport labels (keep only the most recent maxBackportLabels) + if err := cleanupOldBackportLabels(ctx, client); err != nil { + log.Fatalf("Failed to clean up old backport labels: %v", err) + } +} + +// cleanupOldBackportLabels removes backport labels older than n-2. +func cleanupOldBackportLabels(ctx context.Context, client *gh.Client) error { + labels, err := client.ListLabelsWithPrefix(ctx, backportLabelPrefix) + if err != nil { + return err + } + + if len(labels) <= maxBackportLabels { + return nil + } + + // Sort labels by version (descending) + sort.Slice(labels, func(i, j int) bool { + majI, minI, err := parseBackportVersion(labels[i]) + if err != nil { + return false + } + majJ, minJ, err := parseBackportVersion(labels[j]) + if err != nil { + return false + } + + if majI != majJ { + return majI > majJ + } + return minI > minJ + }) + + // Delete labels beyond the max count + for _, label := range labels[maxBackportLabels:] { + if err := client.DeleteLabel(ctx, label); err != nil { + return err + } + fmt.Printf("🗑️ Deleted old label: %s\n", label) + } + + return nil +} + +// parseBackportVersion extracts the major and minor version numbers from a backport label. +// e.g., "backport/v1.9" -> (1, 9), "backport/v2.10" -> (2, 10) +func parseBackportVersion(label string) (major int, minor int, err error) { + // Remove "backport/" prefix to get "vX.Y", append ".0" to make valid semver + v := strings.TrimPrefix(label, "backport/") + ".0" + + // Use version package to extract major.minor string via semver + mm, err := version.MajorMinor(v) + if err != nil { + return 0, 0, err + } + + if _, err := fmt.Sscanf(mm, "%d.%d", &major, &minor); err != nil { + return 0, 0, fmt.Errorf("parsing major.minor from %s: %w", mm, err) + } + return major, minor, nil +} diff --git a/tools/release/enrich-release-notes/main.go b/tools/release/enrich-release-notes/main.go new file mode 100644 index 0000000000..f15d5d27d3 --- /dev/null +++ b/tools/release/enrich-release-notes/main.go @@ -0,0 +1,145 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "regexp" + "strings" + + gh "github.com/grafana/alloy/tools/release/internal/github" + "github.com/grafana/alloy/tools/release/internal/version" +) + +func main() { + var ( + tag string + dryRun bool + ) + flag.StringVar(&tag, "tag", "", "Release tag to enrich (e.g., v1.15.0)") + flag.BoolVar(&dryRun, "dry-run", false, "Dry run (do not update release)") + flag.Parse() + + if tag == "" { + log.Fatal("Release tag is required (use --tag flag)") + } + + ctx := context.Background() + + client, err := gh.NewClientFromEnv(ctx) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("📝 Enriching release notes for %s\n", tag) + + // Get the release by tag + release, err := client.GetReleaseByTag(ctx, tag) + if err != nil { + log.Fatalf("Failed to get release: %v", err) + } + + releaseBody := release.GetBody() + newBody := releaseBody + + // Add contributor information to each PR line + newBody = addContributorInfo(ctx, client, newBody) + + // Append the release notes footer + newBody, err = appendFooter(newBody, tag) + if err != nil { + log.Fatalf("Failed to append footer: %v", err) + } + + if dryRun { + fmt.Println("\n🏃 DRY RUN - No changes made") + fmt.Println("\n--- Updated release notes ---") + fmt.Println(newBody) + return + } + + // Update the release + if err := client.UpdateReleaseBody(ctx, release.GetID(), newBody); err != nil { + log.Fatalf("Failed to update release: %v", err) + } + + fmt.Println("✅ Release notes updated successfully") +} + +// addContributorInfo adds contributor usernames to PR references in the release notes. +func addContributorInfo(ctx context.Context, client *gh.Client, body string) string { + lines := strings.Split(body, "\n") + prPattern := regexp.MustCompile(`\[#(\d+)\]\([^)]+\)`) + + for i, line := range lines { + matches := prPattern.FindStringSubmatch(line) + if matches == nil { + continue + } + + prNumber := 0 + if _, err := fmt.Sscanf(matches[1], "%d", &prNumber); err != nil { + continue + } + + pr, err := client.GetPR(ctx, prNumber) + if err != nil { + fmt.Printf("⚠️ Failed to get PR #%d: %v\n", prNumber, err) + continue + } + + username := pr.GetUser().GetLogin() + fmt.Printf(" PR #%d authored by @%s\n", prNumber, username) + + // Append username to the line if not already present + attribution := fmt.Sprintf("(@%s)", username) + if !strings.Contains(line, attribution) { + lines[i] = line + " " + attribution + } + } + + return strings.Join(lines, "\n") +} + +// appendFooter reads the release notes footer template and appends it with version substitution. +func appendFooter(body, tag string) (string, error) { + // Read footer template - path is relative to tools directory (where go run is executed from) + footerPath := "release/release-notes-footer.md" + footer, err := os.ReadFile(footerPath) + if err != nil { + return "", fmt.Errorf("reading footer template: %w", err) + } + + // Derive RELEASE_DOC_TAG from tag (e.g., v1.2.3 -> v1.2) + releaseDocTag, err := deriveDocTag(tag) + if err != nil { + return "", fmt.Errorf("deriving doc tag: %w", err) + } + + // Replace ${RELEASE_DOC_TAG} placeholder + footerStr := strings.ReplaceAll(string(footer), "${RELEASE_DOC_TAG}", releaseDocTag) + + // Append footer to body + return body + "\n\n" + footerStr, nil +} + +// deriveDocTag derives the documentation tag from a release tag. +// e.g., "v1.2.3" -> "v1.2", "v1.2.3-rc.0" -> "v1.2" +func deriveDocTag(tag string) (string, error) { + // Strip any prerelease suffix first (e.g., -rc.0) + baseTag := tag + if idx := strings.Index(tag, "-"); idx != -1 { + baseTag = tag[:idx] + } + + // Use the version package to get major.minor + mm, err := version.MajorMinor(baseTag) + if err != nil { + return "", err + } + + // Return with v prefix + return "v" + mm, nil +} diff --git a/tools/release/forwardport-release-to-main/main.go b/tools/release/forwardport-release-to-main/main.go new file mode 100644 index 0000000000..5ea638c900 --- /dev/null +++ b/tools/release/forwardport-release-to-main/main.go @@ -0,0 +1,92 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "strings" + + gh "github.com/grafana/alloy/tools/release/internal/github" +) + +func main() { + var ( + prNumber int + dryRun bool + ) + flag.IntVar(&prNumber, "pr", 0, "Release-please PR number that was merged") + flag.BoolVar(&dryRun, "dry-run", false, "Dry run (do not merge)") + flag.Parse() + + if prNumber == 0 { + log.Fatal("PR number is required (use --pr flag)") + } + + ctx := context.Background() + + client, err := gh.NewClientFromEnv(ctx) + if err != nil { + log.Fatal(err) + } + + // Get the original release-please PR details + originalPR, err := client.GetPR(ctx, prNumber) + if err != nil { + log.Fatalf("Failed to get PR #%d: %v", prNumber, err) + } + + if originalPR.GetMergedAt().IsZero() { + log.Fatalf("PR #%d is not merged", prNumber) + } + + // The base branch should be a release branch (e.g., release/v1.15) + releaseBranch := originalPR.GetBase().GetRef() + if !strings.HasPrefix(releaseBranch, "release/") { + log.Fatalf("PR #%d base branch %s is not a release branch", prNumber, releaseBranch) + } + + // Extract version from release branch (release/v1.15 -> v1.15) + version := strings.TrimPrefix(releaseBranch, "release/") + + fmt.Printf("🔀 Merging release branch to main after release-please PR #%d\n", prNumber) + fmt.Printf(" Release branch: %s\n", releaseBranch) + fmt.Printf(" Version: %s\n", version) + + // Check if the release branch is already fully merged into main + alreadyMerged, err := client.IsBranchMergedInto(ctx, releaseBranch, "main") + if err != nil { + log.Fatalf("Failed to check if branch is merged: %v", err) + } + if alreadyMerged { + fmt.Printf("ℹ️ Release branch %s is already merged into main\n", releaseBranch) + return + } + + if dryRun { + fmt.Println("\n🏃 DRY RUN - No changes made") + fmt.Printf("Would merge: %s → main\n", releaseBranch) + return + } + + // Merge the release branch directly into main + commitMessage := fmt.Sprintf("chore: forwardport %s to main\n\nForwardports the %s branch to main after the %s release.\n\nTriggered by release-please PR #%d: %s\n\nThis brings all release commits (changelog updates, version bumps, tags, etc.) from the release branch into main.", + releaseBranch, + releaseBranch, + version, + originalPR.GetNumber(), + originalPR.GetTitle(), + ) + + commit, err := client.MergeBranch(ctx, gh.MergeBranchParams{ + Base: "main", + Head: releaseBranch, + CommitMessage: commitMessage, + }) + if err != nil { + log.Fatalf("Failed to merge %s into main: %v", releaseBranch, err) + } + + fmt.Printf("✅ Merged %s into main\n", releaseBranch) + fmt.Printf(" Commit: %s\n", commit.GetSHA()) +} diff --git a/tools/release/internal/git/git.go b/tools/release/internal/git/git.go new file mode 100644 index 0000000000..f2f9a420f1 --- /dev/null +++ b/tools/release/internal/git/git.go @@ -0,0 +1,172 @@ +// Package git provides shared git CLI operations for release tools. +package git + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "regexp" + "strings" +) + +// validBranchName matches safe git branch names (no leading dash, no special chars that could cause +// issues). +var validBranchName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._/-]*$`) + +// validSHA matches a git SHA (hex string, 7-40 chars). +var validSHA = regexp.MustCompile(`^[0-9a-f]{7,40}$`) + +// validateBranchName ensures a branch name is safe to use in git commands by preventing things like +// directory traversal and dangerous patterns. +func validateBranchName(name string) error { + if !validBranchName.MatchString(name) { + return fmt.Errorf("invalid branch name: %q", name) + } + // Prevent directory traversal and dangerous patterns + if strings.Contains(name, "..") { + return fmt.Errorf("branch name must not contain '..': %q", name) + } + if strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") { + return fmt.Errorf("branch name must not start or end with '/': %q", name) + } + if strings.Contains(name, "//") { + return fmt.Errorf("branch name must not contain consecutive slashes: %q", name) + } + return nil +} + +// validateSHA ensures a string looks like a git SHA. +func validateSHA(sha string) error { + if !validSHA.MatchString(sha) { + return fmt.Errorf("invalid SHA: %q", sha) + } + return nil +} + +// run executes a command with stdout/stderr connected to the terminal. +func run(args ...string) error { + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// runOutput executes a command and returns its stdout. +// On error, stderr is included in the error message for better diagnostics. +func runOutput(args ...string) (string, error) { + cmd := exec.Command(args[0], args[1:]...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + if stderr.Len() > 0 { + return "", fmt.Errorf("%w: %s", err, strings.TrimSpace(stderr.String())) + } + return "", err + } + return strings.TrimSpace(stdout.String()), nil +} + +// ConfigureUser configures git with the given user identity for commit authorship. +func ConfigureUser(name, email string) error { + if err := run("git", "config", "user.name", name); err != nil { + return fmt.Errorf("setting user.name: %w", err) + } + if err := run("git", "config", "user.email", email); err != nil { + return fmt.Errorf("setting user.email: %w", err) + } + return nil +} + +// BranchExistsOnRemote checks if a branch exists on the remote using git ls-remote. +func BranchExistsOnRemote(branch string) (bool, error) { + if err := validateBranchName(branch); err != nil { + return false, err + } + out, err := runOutput("git", "ls-remote", "--heads", "origin", branch) + if err != nil { + return false, fmt.Errorf("checking remote branch %s: %w", branch, err) + } + return out != "", nil +} + +// Fetch fetches a branch from origin. +func Fetch(branch string) error { + if err := validateBranchName(branch); err != nil { + return err + } + if err := run("git", "fetch", "origin", branch); err != nil { + return fmt.Errorf("fetching branch %s: %w", branch, err) + } + return nil +} + +// CreateBranchFrom creates a new branch from a base ref and checks it out. +func CreateBranchFrom(branch, base string) error { + if err := validateBranchName(branch); err != nil { + return err + } + // Base can be "origin/branch" so validate the branch part after any "origin/" prefix + baseBranch := strings.TrimPrefix(base, "origin/") + if err := validateBranchName(baseBranch); err != nil { + return fmt.Errorf("invalid base: %w", err) + } + if err := run("git", "checkout", "-b", branch, base); err != nil { + return fmt.Errorf("creating branch %s from %s: %w", branch, base, err) + } + return nil +} + +// CherryPick cherry-picks a commit, adding a "(cherry picked from commit ...)" reference. +func CherryPick(sha string) error { + if err := validateSHA(sha); err != nil { + return err + } + if err := run("git", "cherry-pick", "-x", sha); err != nil { + return fmt.Errorf("cherry-picking commit %s: %w", sha, err) + } + return nil +} + +// Push pushes a branch to origin. +func Push(branch string) error { + if err := validateBranchName(branch); err != nil { + return err + } + if err := run("git", "push", "origin", branch); err != nil { + return fmt.Errorf("pushing branch %s: %w", branch, err) + } + return nil +} + +// Checkout checks out an existing branch. +func Checkout(branch string) error { + if err := validateBranchName(branch); err != nil { + return err + } + if err := run("git", "checkout", branch); err != nil { + return fmt.Errorf("checking out branch %s: %w", branch, err) + } + return nil +} + +// Merge merges a branch into the current branch with a merge commit. +// The message is used for the merge commit. +func Merge(branch, message string) error { + if err := validateBranchName(branch); err != nil { + return err + } + if err := run("git", "merge", "--no-ff", "-m", message, branch); err != nil { + return fmt.Errorf("merging branch %s: %w", branch, err) + } + return nil +} + +// PushBranch pushes the current branch to origin with tracking. +func PushBranch() error { + if err := run("git", "push", "-u", "origin", "HEAD"); err != nil { + return fmt.Errorf("pushing current branch: %w", err) + } + return nil +} diff --git a/tools/release/internal/github/client.go b/tools/release/internal/github/client.go new file mode 100644 index 0000000000..0dfcc125bf --- /dev/null +++ b/tools/release/internal/github/client.go @@ -0,0 +1,413 @@ +// Package github provides shared GitHub client utilities for release tools. +package github + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "strings" + + "github.com/google/go-github/v57/github" + "golang.org/x/oauth2" +) + +// Client wraps the GitHub API client with repository context. +type Client struct { + api *github.Client + owner string + repo string +} + +// ClientConfig holds configuration for creating a new Client. +type ClientConfig struct { + Token string + Owner string + Repo string +} + +// AppIdentity represents a GitHub App's git identity for commits. +type AppIdentity struct { + Name string // e.g., "my-app[bot]" + Email string // e.g., "12345+my-app[bot]@users.noreply.github.com" +} + +// CreateBranchParams holds parameters for CreateBranch. +type CreateBranchParams struct { + Branch string + SHA string +} + +// CreatePRParams holds parameters for CreatePR. +type CreatePRParams struct { + Title string + Head string + Base string + Body string +} + +// FindCommitParams holds parameters for FindCommitWithPattern and CommitExistsWithPattern. +type FindCommitParams struct { + Branch string + Pattern string +} + +// MergeBranchParams holds parameters for MergeBranch. +type MergeBranchParams struct { + Base string // Target branch to merge into + Head string // Source branch to merge from + CommitMessage string // Commit message for the merge +} + +// CreateLabelParams holds parameters for CreateLabel. +type CreateLabelParams struct { + Name string // Label name + Color string // Hex color without '#' prefix (e.g., "ff0000") + Description string // Optional description +} + +// ErrCommitNotFound is returned when a commit matching the search criteria is not found. +var ErrCommitNotFound = errors.New("commit not found") + +// NewClientFromEnv creates a new Client from environment variables. +// Reads GITHUB_TOKEN and GITHUB_REPOSITORY (format: owner/repo). +func NewClientFromEnv(ctx context.Context) (*Client, error) { + token := os.Getenv("GITHUB_TOKEN") + if token == "" { + return nil, fmt.Errorf("GITHUB_TOKEN environment variable is required") + } + + var owner, repo string + if ghRepo := os.Getenv("GITHUB_REPOSITORY"); ghRepo != "" { + parts := strings.SplitN(ghRepo, "/", 2) + if len(parts) == 2 { + owner = parts[0] + repo = parts[1] + } + } + + if owner == "" || repo == "" { + return nil, fmt.Errorf("GITHUB_REPOSITORY environment variable is required (format: owner/repo)") + } + + return NewClient(ctx, ClientConfig{ + Token: token, + Owner: owner, + Repo: repo, + }), nil +} + +// NewClient creates a new Client with the given configuration. +func NewClient(ctx context.Context, cfg ClientConfig) *Client { + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: cfg.Token}) + tc := oauth2.NewClient(ctx, ts) + + return &Client{ + api: github.NewClient(tc), + owner: cfg.Owner, + repo: cfg.Repo, + } +} + +// API returns the underlying go-github client for advanced usage. +func (c *Client) API() *github.Client { + return c.api +} + +// Owner returns the repository owner. +func (c *Client) Owner() string { + return c.owner +} + +// Repo returns the repository name. +func (c *Client) Repo() string { + return c.repo +} + +// BranchExists checks if a branch exists in the repository. +func (c *Client) BranchExists(ctx context.Context, branch string) (bool, error) { + _, resp, err := c.api.Repositories.GetBranch(ctx, c.owner, c.repo, branch, 0) + if err != nil { + if resp != nil && resp.StatusCode == http.StatusNotFound { + return false, nil + } + var errResp *github.ErrorResponse + if errors.As(err, &errResp) && errResp.Response.StatusCode == http.StatusNotFound { + return false, nil + } + return false, err + } + return true, nil +} + +// GetRefSHA resolves a ref (branch, tag, or commit SHA) to its SHA. +func (c *Client) GetRefSHA(ctx context.Context, ref string) (string, error) { + // Try as a branch first + branch, _, err := c.api.Repositories.GetBranch(ctx, c.owner, c.repo, ref, 0) + if err == nil { + return branch.GetCommit().GetSHA(), nil + } + + // Try as a tag + tagRef, _, err := c.api.Git.GetRef(ctx, c.owner, c.repo, "tags/"+ref) + if err == nil { + return tagRef.GetObject().GetSHA(), nil + } + + // Try as a commit SHA + commit, _, err := c.api.Git.GetCommit(ctx, c.owner, c.repo, ref) + if err == nil { + return commit.GetSHA(), nil + } + + return "", fmt.Errorf("could not resolve ref: %s", ref) +} + +// CreateBranch creates a new branch from the given SHA. +func (c *Client) CreateBranch(ctx context.Context, p CreateBranchParams) error { + ref := &github.Reference{ + Ref: github.String("refs/heads/" + p.Branch), + Object: &github.GitObject{ + SHA: github.String(p.SHA), + }, + } + + _, _, err := c.api.Git.CreateRef(ctx, c.owner, c.repo, ref) + if err != nil { + return fmt.Errorf("creating branch ref: %w", err) + } + + return nil +} + +// ReadManifest reads the release-please manifest from the repository. +func (c *Client) ReadManifest(ctx context.Context, ref string) (map[string]string, error) { + fileContent, _, _, err := c.api.Repositories.GetContents( + ctx, c.owner, c.repo, + ".release-please-manifest.json", + &github.RepositoryContentGetOptions{Ref: ref}, + ) + if err != nil { + return nil, fmt.Errorf("getting manifest file: %w", err) + } + + content, err := fileContent.GetContent() + if err != nil { + return nil, fmt.Errorf("decoding manifest content: %w", err) + } + + var manifest map[string]string + if err := json.Unmarshal([]byte(content), &manifest); err != nil { + return nil, fmt.Errorf("parsing manifest JSON: %w", err) + } + + return manifest, nil +} + +// GetAppIdentity returns the GitHub App's identity for use in git commits. +// It checks for APP_SLUG environment variable and fetches the app ID from the public API. +// Falls back to the authenticated Apps API if APP_SLUG is not set (requires JWT authentication). +func (c *Client) GetAppIdentity(ctx context.Context) (AppIdentity, error) { + // Prefer APP_SLUG env var - fetch ID from public API (works with installation tokens) + appSlug := os.Getenv("APP_SLUG") + if appSlug != "" { + app, _, err := c.api.Apps.Get(ctx, appSlug) + if err != nil { + return AppIdentity{}, fmt.Errorf("getting app info for slug %q: %w", appSlug, err) + } + return AppIdentity{ + Name: fmt.Sprintf("%s[bot]", appSlug), + Email: fmt.Sprintf("%d+%s[bot]@users.noreply.github.com", app.GetID(), appSlug), + }, nil + } + + // Fall back to API call (requires JWT authentication, not installation token) + app, _, err := c.api.Apps.Get(ctx, "") + if err != nil { + return AppIdentity{}, fmt.Errorf("getting app info: %w (hint: set APP_SLUG env var when using installation tokens)", err) + } + + slug := app.GetSlug() + id := app.GetID() + + return AppIdentity{ + Name: fmt.Sprintf("%s[bot]", slug), + Email: fmt.Sprintf("%d+%s[bot]@users.noreply.github.com", id, slug), + }, nil +} + +// GetPR fetches a pull request by number. +func (c *Client) GetPR(ctx context.Context, number int) (*github.PullRequest, error) { + pr, _, err := c.api.PullRequests.Get(ctx, c.owner, c.repo, number) + if err != nil { + return nil, fmt.Errorf("getting PR #%d: %w", number, err) + } + return pr, nil +} + +// CreatePR creates a new pull request. +func (c *Client) CreatePR(ctx context.Context, p CreatePRParams) (*github.PullRequest, error) { + newPR := &github.NewPullRequest{ + Title: github.String(p.Title), + Head: github.String(p.Head), + Base: github.String(p.Base), + Body: github.String(p.Body), + } + + pr, _, err := c.api.PullRequests.Create(ctx, c.owner, c.repo, newPR) + if err != nil { + return nil, fmt.Errorf("creating pull request: %w", err) + } + + return pr, nil +} + +// FindCommitWithPattern searches the commit history of a branch for a commit whose title contains the pattern. +// Returns the commit SHA if found, or an error if not found. +func (c *Client) FindCommitWithPattern(ctx context.Context, p FindCommitParams) (string, error) { + opts := &github.CommitsListOptions{ + SHA: p.Branch, + ListOptions: github.ListOptions{ + PerPage: 100, + }, + } + + // Search through recent commits (up to 500) + for range 5 { + commits, resp, err := c.api.Repositories.ListCommits(ctx, c.owner, c.repo, opts) + if err != nil { + return "", fmt.Errorf("listing commits: %w", err) + } + + for _, commit := range commits { + message := commit.GetCommit().GetMessage() + title := strings.Split(message, "\n")[0] + if strings.Contains(title, p.Pattern) { + return commit.GetSHA(), nil + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + return "", fmt.Errorf("%w with pattern %q in branch %s", ErrCommitNotFound, p.Pattern, p.Branch) +} + +// CommitExistsWithPattern checks if any commit in the branch history contains the pattern in its title. +func (c *Client) CommitExistsWithPattern(ctx context.Context, p FindCommitParams) (bool, error) { + _, err := c.FindCommitWithPattern(ctx, p) + if err != nil { + if errors.Is(err, ErrCommitNotFound) { + return false, nil + } + return false, err + } + return true, nil +} + +// IsBranchMergedInto checks if the source branch is fully merged into the target branch. +// Returns true if target contains all commits from source (i.e., source is behind or equal to target). +func (c *Client) IsBranchMergedInto(ctx context.Context, source, target string) (bool, error) { + comparison, _, err := c.api.Repositories.CompareCommits(ctx, c.owner, c.repo, target, source, nil) + if err != nil { + return false, fmt.Errorf("comparing branches: %w", err) + } + + // If source is "behind" or "identical" to target, it means target already has all of source's commits + status := comparison.GetStatus() + return status == "behind" || status == "identical", nil +} + +// MergeBranch merges the head branch into the base branch directly (no PR). +// This uses GitHub's merge API to create a merge commit. +func (c *Client) MergeBranch(ctx context.Context, p MergeBranchParams) (*github.RepositoryCommit, error) { + req := &github.RepositoryMergeRequest{ + Base: github.String(p.Base), + Head: github.String(p.Head), + CommitMessage: github.String(p.CommitMessage), + } + + commit, _, err := c.api.Repositories.Merge(ctx, c.owner, c.repo, req) + if err != nil { + return nil, fmt.Errorf("merging %s into %s: %w", p.Head, p.Base, err) + } + + return commit, nil +} + +// CreateLabel creates a new label in the repository. +func (c *Client) CreateLabel(ctx context.Context, p CreateLabelParams) error { + label := &github.Label{ + Name: github.String(p.Name), + Color: github.String(p.Color), + Description: github.String(p.Description), + } + + _, _, err := c.api.Issues.CreateLabel(ctx, c.owner, c.repo, label) + if err != nil { + return fmt.Errorf("creating label %q: %w", p.Name, err) + } + + return nil +} + +// ListLabelsWithPrefix returns all labels that start with the given prefix. +func (c *Client) ListLabelsWithPrefix(ctx context.Context, prefix string) ([]string, error) { + var result []string + opts := &github.ListOptions{PerPage: 100} + + for { + labels, resp, err := c.api.Issues.ListLabels(ctx, c.owner, c.repo, opts) + if err != nil { + return nil, fmt.Errorf("listing labels: %w", err) + } + + for _, label := range labels { + name := label.GetName() + if strings.HasPrefix(name, prefix) { + result = append(result, name) + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + return result, nil +} + +// DeleteLabel deletes a label from the repository. +func (c *Client) DeleteLabel(ctx context.Context, name string) error { + _, err := c.api.Issues.DeleteLabel(ctx, c.owner, c.repo, name) + if err != nil { + return fmt.Errorf("deleting label %q: %w", name, err) + } + return nil +} + +// GetReleaseByTag fetches a release by its tag name. +func (c *Client) GetReleaseByTag(ctx context.Context, tag string) (*github.RepositoryRelease, error) { + release, _, err := c.api.Repositories.GetReleaseByTag(ctx, c.owner, c.repo, tag) + if err != nil { + return nil, fmt.Errorf("getting release for tag %s: %w", tag, err) + } + return release, nil +} + +// UpdateReleaseBody updates only the body of a release. +func (c *Client) UpdateReleaseBody(ctx context.Context, releaseID int64, body string) error { + _, _, err := c.api.Repositories.EditRelease(ctx, c.owner, c.repo, releaseID, &github.RepositoryRelease{ + Body: github.String(body), + }) + if err != nil { + return fmt.Errorf("updating release %d body: %w", releaseID, err) + } + return nil +} diff --git a/tools/release/internal/version/version.go b/tools/release/internal/version/version.go new file mode 100644 index 0000000000..0edd5508fa --- /dev/null +++ b/tools/release/internal/version/version.go @@ -0,0 +1,94 @@ +// Package version provides semantic version utilities for release tools. +package version + +import ( + "fmt" + "strings" + + "golang.org/x/mod/semver" +) + +// EnsureVPrefix adds a "v" prefix if not present. +func EnsureVPrefix(v string) string { + if !strings.HasPrefix(v, "v") { + return "v" + v + } + return v +} + +// StripVPrefix removes the "v" prefix if present. +func StripVPrefix(v string) string { + return strings.TrimPrefix(v, "v") +} + +// MajorMinor returns the major.minor portion of a version (without v prefix). +// e.g., "v1.15.0" -> "1.15", "1.15.0" -> "1.15" +func MajorMinor(v string) (string, error) { + v = EnsureVPrefix(v) + if !semver.IsValid(v) { + return "", fmt.Errorf("invalid semver: %s", v) + } + // semver.MajorMinor returns "vX.Y", strip the v + return StripVPrefix(semver.MajorMinor(v)), nil +} + +// Major returns the major portion of a version (without v prefix). +// e.g., "v1.15.0" -> "1" +func Major(v string) (string, error) { + v = EnsureVPrefix(v) + if !semver.IsValid(v) { + return "", fmt.Errorf("invalid semver: %s", v) + } + // semver.Major returns "vX", strip the v + return StripVPrefix(semver.Major(v)), nil +} + +// NextMinor increments the minor version and returns major.minor (without v prefix). +// e.g., "v1.14.0" -> "1.15", "1.14.0" -> "1.15" +func NextMinor(v string) (string, error) { + v = EnsureVPrefix(v) + if !semver.IsValid(v) { + return "", fmt.Errorf("invalid semver: %s", v) + } + + // Parse major and minor from MajorMinor result + mm := semver.MajorMinor(v) // "v1.14" + mm = StripVPrefix(mm) // "1.14" + + var major, minor int + _, err := fmt.Sscanf(mm, "%d.%d", &major, &minor) + if err != nil { + return "", fmt.Errorf("parsing major.minor from %s: %w", mm, err) + } + + return fmt.Sprintf("%d.%d", major, minor+1), nil +} + +// IsValid checks if a version string is valid semver. +func IsValid(v string) bool { + return semver.IsValid(EnsureVPrefix(v)) +} + +// ReleaseBranchName returns the release branch name for a version. +// e.g., "v1.15.0" -> "release/v1.15", "1.15" -> "release/v1.15" +func ReleaseBranchName(v string) (string, error) { + mm, err := MajorMinor(v) + if err != nil { + // If it's already just major.minor without patch, try directly + v = EnsureVPrefix(v + ".0") + mm, err = MajorMinor(v) + if err != nil { + return "", err + } + } + return fmt.Sprintf("release/v%s", mm), nil +} + +// ParseReleaseBranch extracts the major.minor version from a release branch name. +// e.g., "release/v1.15" -> "1.15" +func ParseReleaseBranch(branch string) (string, error) { + if !strings.HasPrefix(branch, "release/v") { + return "", fmt.Errorf("invalid release branch format: %s (expected release/vX.Y)", branch) + } + return strings.TrimPrefix(branch, "release/v"), nil +} diff --git a/tools/release-note.md b/tools/release/release-notes-footer.md similarity index 67% rename from tools/release-note.md rename to tools/release/release-notes-footer.md index d2ece64134..ef848b3adc 100644 --- a/tools/release-note.md +++ b/tools/release/release-notes-footer.md @@ -1,16 +1,10 @@ -This is release `${VERSION}` of Grafana Alloy. - -### Upgrading +## Upgrading Read the [release notes] for specific instructions on upgrading from older versions: [release notes]: https://grafana.com/docs/alloy/${RELEASE_DOC_TAG}/release-notes/ -### Notable changes: - -:warning: **ADD ENTRIES FROM CHANGELOG HERE** :warning: - -### Installation +## Installation Refer to our [installation guide] for how to install Grafana Alloy. diff --git a/tools/release/release-please-runner/.nvmrc b/tools/release/release-please-runner/.nvmrc new file mode 100644 index 0000000000..a45fd52cc5 --- /dev/null +++ b/tools/release/release-please-runner/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/tools/release/release-please-runner/index.js b/tools/release/release-please-runner/index.js new file mode 100644 index 0000000000..a5783cd370 --- /dev/null +++ b/tools/release/release-please-runner/index.js @@ -0,0 +1,118 @@ +#!/usr/bin/env node + +/** + * Based on https://github.com/googleapis/release-please-action/blob/main/src/index.ts + * Adapted for CLI usage with custom versioning strategy. + */ + +import { GitHub, Manifest, VERSION } from 'release-please'; +import { registerVersioningStrategy } from 'release-please/build/src/factories/versioning-strategy-factory.js'; +import { MinorBreakingVersioningStrategy } from './minor-breaking-versioning.js'; + +// Register the custom versioning strategy +registerVersioningStrategy('minor-breaking', (options) => new MinorBreakingVersioningStrategy(options)); + +const DEFAULT_CONFIG_FILE = 'release-please-config.json'; +const DEFAULT_MANIFEST_FILE = '.release-please-manifest.json'; + +function parseInputs() { + const token = process.env.GITHUB_TOKEN; + if (!token) { + throw new Error('GITHUB_TOKEN environment variable is required'); + } + + const repoUrl = process.env.REPO_URL || process.env.GITHUB_REPOSITORY || ''; + if (!repoUrl) { + throw new Error('REPO_URL or GITHUB_REPOSITORY environment variable is required'); + } + + return { + token, + repoUrl, + targetBranch: process.env.TARGET_BRANCH || undefined, + configFile: process.env.CONFIG_FILE || DEFAULT_CONFIG_FILE, + manifestFile: process.env.MANIFEST_FILE || DEFAULT_MANIFEST_FILE, + skipGitHubRelease: process.env.SKIP_GITHUB_RELEASE === 'true', + skipGitHubPullRequest: process.env.SKIP_GITHUB_PULL_REQUEST === 'true', + }; +} + +function loadManifest(github, inputs) { + console.log('Loading manifest from config file'); + return Manifest.fromManifest( + github, + inputs.targetBranch || github.repository.defaultBranch, + inputs.configFile, + inputs.manifestFile + ); +} + +async function main() { + console.log(`Running release-please version: ${VERSION}`); + const inputs = parseInputs(); + const github = await getGitHubInstance(inputs); + + if (!inputs.skipGitHubRelease) { + const manifest = await loadManifest(github, inputs); + console.log('Creating releases'); + outputReleases(await manifest.createReleases()); + } + + if (!inputs.skipGitHubPullRequest) { + const manifest = await loadManifest(github, inputs); + console.log('Creating pull requests'); + outputPRs(await manifest.createPullRequests()); + } +} + +function getGitHubInstance(inputs) { + const [owner, repo] = inputs.repoUrl.split('/'); + return GitHub.create({ + owner, + repo, + token: inputs.token, + defaultBranch: inputs.targetBranch, + }); +} + +function outputReleases(releases) { + releases = releases.filter(release => release !== undefined); + const pathsReleased = []; + console.log(`releases_created=${releases.length > 0}`); + if (releases.length) { + for (const release of releases) { + if (!release) { + continue; + } + const path = release.path || '.'; + if (path) { + pathsReleased.push(path); + } + console.log(`Created release: ${release.tagName}`); + for (const [rawKey, value] of Object.entries(release)) { + let key = rawKey; + if (key === 'tagName') key = 'tag_name'; + if (key === 'uploadUrl') key = 'upload_url'; + if (key === 'notes') key = 'body'; + if (key === 'url') key = 'html_url'; + console.log(` ${key}=${value}`); + } + } + } + console.log(`paths_released=${JSON.stringify(pathsReleased)}`); +} + +function outputPRs(prs) { + prs = prs.filter(pr => pr !== undefined); + console.log(`prs_created=${prs.length > 0}`); + if (prs.length) { + for (const pr of prs) { + console.log(`Created/updated PR #${pr.number}: ${pr.title}`); + } + } +} + +main().catch(err => { + console.error(`release-please failed: ${err.message}`); + process.exit(1); +}); diff --git a/tools/release/release-please-runner/minor-breaking-versioning.js b/tools/release/release-please-runner/minor-breaking-versioning.js new file mode 100644 index 0000000000..a0318b6fd8 --- /dev/null +++ b/tools/release/release-please-runner/minor-breaking-versioning.js @@ -0,0 +1,29 @@ +/** + * Custom versioning strategy that bumps minor version for breaking changes + * instead of major version. Extends the default strategy and swaps + * MajorVersionUpdate for MinorVersionUpdate. + */ + +import { DefaultVersioningStrategy } from 'release-please/build/src/versioning-strategies/default.js'; +import { MajorVersionUpdate, MinorVersionUpdate } from 'release-please/build/src/versioning-strategy.js'; + +export class MinorBreakingVersioningStrategy extends DefaultVersioningStrategy { + /** + * Override to return MinorVersionUpdate instead of MajorVersionUpdate + * when there are breaking changes. + */ + determineReleaseType(version, commits) { + const releaseType = super.determineReleaseType(version, commits); + + // If the default strategy would do a major bump, do a minor bump instead + if ( + releaseType instanceof MajorVersionUpdate || + releaseType.constructor.name === 'MajorVersionUpdate' + ) { + console.log('Breaking changes detected - bumping minor version instead of major'); + return new MinorVersionUpdate(); + } + + return releaseType; + } +} diff --git a/tools/release/release-please-runner/package-lock.json b/tools/release/release-please-runner/package-lock.json new file mode 100644 index 0000000000..b868ba72bb --- /dev/null +++ b/tools/release/release-please-runner/package-lock.json @@ -0,0 +1,1950 @@ +{ + "name": "@grafana/alloy-release-please-runner", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@grafana/alloy-release-please-runner", + "version": "0.1.0", + "dependencies": { + "release-please": "^17.1.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@conventional-commits/parser": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@conventional-commits/parser/-/parser-0.4.1.tgz", + "integrity": "sha512-H2ZmUVt6q+KBccXfMBhbBF14NlANeqHTXL4qCL6QGbMzrc4HDXyzWuxPxPNbz71f/5UkR5DrycP5VO9u7crahg==", + "license": "ISC", + "dependencies": { + "unist-util-visit": "^2.0.3", + "unist-util-visit-parents": "^3.1.1" + } + }, + "node_modules/@google-automations/git-file-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-automations/git-file-utils/-/git-file-utils-3.0.0.tgz", + "integrity": "sha512-e+WLoKR0TchIhKsSDOnd/su171eXKAAdLpP2tS825UAloTgfYus53kW8uKoVj9MAsMjXGXsJ2s1ASgjq81xVdA==", + "license": "Apache-2.0", + "dependencies": { + "@octokit/rest": "^20.1.1", + "@octokit/types": "^13.0.0", + "minimatch": "^5.1.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@iarna/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==", + "license": "ISC" + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.4.4-cjs.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz", + "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", + "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.3.2-cjs.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz", + "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.8.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz", + "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "license": "MIT" + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "license": "MIT" + }, + "node_modules/@types/npm-package-arg": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/npm-package-arg/-/npm-package-arg-6.1.4.tgz", + "integrity": "sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "16.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.11.tgz", + "integrity": "sha512-sbtvk8wDN+JvEdabmZExoW/HNr1cB7D/j4LT08rMiuikfA7m/JNJg7ATQcgzs34zHnoScDkY0ZRSl29Fkmk36g==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "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/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/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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "license": "MIT" + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "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/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "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_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "license": "MIT", + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/code-suggester": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/code-suggester/-/code-suggester-5.0.0.tgz", + "integrity": "sha512-/xyGfSM/hMYxl12kqoYoOwUm0D1uuVT2nWcMiTq2Fn5MLi+BlWkHq5AUvtniDJwVSdI3jgbK4AOzGws+v/dFPQ==", + "license": "Apache-2.0", + "dependencies": { + "@octokit/rest": "^20.1.1", + "@types/yargs": "^16.0.0", + "async-retry": "^1.3.1", + "diff": "^5.0.0", + "glob": "^7.1.6", + "parse-diff": "^0.11.0", + "yargs": "^16.0.0" + }, + "bin": { + "code-suggester": "build/src/bin/code-suggester.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/code-suggester/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/code-suggester/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/code-suggester/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz", + "integrity": "sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==", + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-6.0.1.tgz", + "integrity": "sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==", + "license": "MIT", + "dependencies": { + "conventional-commits-filter": "^3.0.0", + "dateformat": "^3.0.3", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "meow": "^8.1.2", + "semver": "^7.0.0", + "split": "^1.0.1" + }, + "bin": { + "conventional-changelog-writer": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/conventional-commits-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-3.0.0.tgz", + "integrity": "sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==", + "license": "MIT", + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "license": "MIT", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsonpath-plus": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "license": "MIT", + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "license": "MIT", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/node-html-parser": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-diff": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/parse-diff/-/parse-diff-0.11.1.tgz", + "integrity": "sha512-Oq4j8LAOPOcssanQkIjxosjATBIEJhCxMCxPhMu+Ci4wdNmAEdx0O+a7gzbR2PyKXgKPvRLIN5g224+dJAsKHA==", + "license": "MIT" + }, + "node_modules/parse-github-repo-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha512-bSWyzBKqcSL4RrncTpGsEKoJ7H8a4L3++ifTAbTFeMHyq2wRV+42DGmQcHIrJIvdcacjIOxEuKH/w4tthF17gg==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/release-please": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/release-please/-/release-please-17.1.3.tgz", + "integrity": "sha512-fs4v5318Z3CLqyqw7EueXzjErtL8oXCv6Kc1mMYi72TAyReyBtOuuMB0lwoC2+LtGmvUYoG+RBXnDVU0DsKUIA==", + "license": "Apache-2.0", + "dependencies": { + "@conventional-commits/parser": "^0.4.1", + "@google-automations/git-file-utils": "^3.0.0", + "@iarna/toml": "^3.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/rest": "^20.1.1", + "@types/npm-package-arg": "^6.1.0", + "@xmldom/xmldom": "^0.8.4", + "chalk": "^4.0.0", + "code-suggester": "^5.0.0", + "conventional-changelog-conventionalcommits": "^6.0.0", + "conventional-changelog-writer": "^6.0.0", + "conventional-commits-filter": "^3.0.0", + "detect-indent": "^6.1.0", + "diff": "^7.0.0", + "figures": "^3.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.0.0", + "jsonpath-plus": "^10.0.0", + "node-html-parser": "^6.0.0", + "parse-github-repo-url": "^1.4.1", + "semver": "^7.5.3", + "type-fest": "^3.0.0", + "typescript": "^4.6.4", + "unist-util-visit": "^2.0.3", + "unist-util-visit-parents": "^3.1.1", + "xpath": "^0.0.34", + "yaml": "^2.2.2", + "yargs": "^17.0.0" + }, + "bin": { + "release-please": "build/src/bin/release-please.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "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.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/semver": { + "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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "license": "CC0-1.0" + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "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/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-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, + "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", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, + "node_modules/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xpath": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", + "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==", + "license": "MIT", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/tools/release/release-please-runner/package.json b/tools/release/release-please-runner/package.json new file mode 100644 index 0000000000..a363a9dd20 --- /dev/null +++ b/tools/release/release-please-runner/package.json @@ -0,0 +1,13 @@ +{ + "name": "@grafana/alloy-release-please-runner", + "version": "0.1.0", + "description": "Custom release-please runner with minor-only versioning strategy", + "type": "module", + "main": "index.js", + "scripts": { + "release-please": "node index.js" + }, + "dependencies": { + "release-please": "^17.1.3" + } +}