Skip to content
Merged
83 changes: 83 additions & 0 deletions .github/workflows/downstream-release-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Downstream Release Dispatch

on:
release:
types: [published]

concurrency:
group: downstream-release-dispatch-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

env:
CONSUMER_REPOS: |
microsoft/vscode-isort
microsoft/vscode-flake8
microsoft/vscode-pylint
microsoft/vscode-mypy
microsoft/vscode-black-formatter
NPM_DEPENDENCY: "@vscode/common-python-lsp"
PIP_DEPENDENCY: "vscode-common-python-lsp"

jobs:
dispatch-to-consumers:
name: Dispatch Release Event To Consumer Repos
runs-on: ubuntu-latest
steps:
- name: Send repository_dispatch to each consumer repo
env:
GH_TOKEN: ${{ secrets.DOWNSTREAM_REPO_TOKEN }}
SOURCE_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_URL: ${{ github.event.release.html_url }}
RELEASE_NAME: ${{ github.event.release.name || '' }}
run: |
set -euo pipefail

Comment thread
edvilme marked this conversation as resolved.
if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::DOWNSTREAM_REPO_TOKEN is required for cross-repo dispatch"
exit 1
fi

failures=""

for repo in $CONSUMER_REPOS; do
echo "Dispatching shared-package-release event to $repo"

payload=$(jq -n \
--arg source_repo "$SOURCE_REPO" \
--arg release_tag "$RELEASE_TAG" \
--arg release_url "$RELEASE_URL" \
--arg release_name "$RELEASE_NAME" \
--arg npm_dependency "$NPM_DEPENDENCY" \
--arg pip_dependency "$PIP_DEPENDENCY" \
'{
event_type: "shared-package-release",
client_payload: {
source_repo: $source_repo,
release_tag: $release_tag,
release_url: $release_url,
release_name: $release_name,
npm_dependency: $npm_dependency,
pip_dependency: $pip_dependency
}
}')

if ! gh api "/repos/$repo/dispatches" \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--input - <<< "$payload"; then
echo "Dispatch failed for $repo"
failures+="$repo "$'\n'
continue
fi
done

if [ -n "$failures" ]; then
echo "Dispatch failed for the following repositories:"
echo "$failures"
exit 1
fi
120 changes: 120 additions & 0 deletions docs/consumer-repo-dispatch-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Consumer Repo Workflow Template

Use this workflow in each consumer repository to handle `repository_dispatch` events sent by:
- [.github/workflows/downstream-release-dispatch.yml](../.github/workflows/downstream-release-dispatch.yml)

```yaml
name: Shared Package Release Handler

on:
repository_dispatch:
types: [shared-package-release]

permissions:
contents: write
pull-requests: write

jobs:
update-shared-packages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
node-version: "20"

- uses: actions/setup-python@v6
with:
python-version: "3.x"

- name: Create branch for this release
env:
RELEASE_TAG: ${{ github.event.client_payload.release_tag }}
run: |
set -euo pipefail
BRANCH="shared-package-v${RELEASE_TAG#v}"
git fetch origin main
git checkout -B "$BRANCH" origin/main

- name: Normalize release tag
id: release_version
env:
RELEASE_TAG: ${{ github.event.client_payload.release_tag }}
run: |
set -euo pipefail
VERSION="${RELEASE_TAG#v}"
echo "value=$VERSION" >> "$GITHUB_OUTPUT"

- name: Update npm dependency
env:
NPM_DEP: ${{ github.event.client_payload.npm_dependency }}
RELEASE_VERSION: ${{ steps.release_version.outputs.value }}
run: |
Comment thread
edvilme marked this conversation as resolved.
set -euo pipefail
npm install "$NPM_DEP@$RELEASE_VERSION"

Comment thread
edvilme marked this conversation as resolved.
- name: Update pip dependency reference (example: requirements file)
env:
PIP_DEP: ${{ github.event.client_payload.pip_dependency }}
RELEASE_VERSION: ${{ steps.release_version.outputs.value }}
run: |
set -euo pipefail
python - <<'PY'
from pathlib import Path
import os
import re

dep = os.environ["PIP_DEP"]
version = os.environ["RELEASE_VERSION"]
pinned = f"{dep}=={version}"
req = Path("requirements.txt")
if req.exists():
txt = req.read_text(encoding="utf-8")
updated = re.sub(rf"^{re.escape(dep)}([<>=!~].*)?$", pinned, txt, flags=re.MULTILINE)
Comment thread
edvilme marked this conversation as resolved.
req.write_text(updated, encoding="utf-8")
PY
Comment thread
edvilme marked this conversation as resolved.

Comment thread
edvilme marked this conversation as resolved.
- name: Commit and open PR if changed
env:
RELEASE_TAG: ${{ github.event.client_payload.release_tag }}
RELEASE_URL: ${{ github.event.client_payload.release_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

if git diff --quiet; then
echo "No changes detected."
exit 0
fi

BRANCH="shared-package-v${RELEASE_TAG#v}"
git add -A
Comment thread
edvilme marked this conversation as resolved.
git commit -m "Upgrade shared package to ${RELEASE_TAG}"
git push --set-upstream origin "$BRANCH"

gh pr create \
--base main \
--head "$BRANCH" \
Comment thread
edvilme marked this conversation as resolved.
--title "[Shared Package] Upgrade to ${RELEASE_TAG}" \
--body "Automated update from ${RELEASE_URL}"
```

## Dispatch payload fields
Comment thread
edvilme marked this conversation as resolved.

Comment thread
edvilme marked this conversation as resolved.
The dispatcher sends these `client_payload` fields:
- `source_repo`
- `release_tag`
- `release_url`
- `release_name`
- `npm_dependency`
- `pip_dependency`

## Template notes

- This is a starter template. Each consumer repo should adjust file paths and update commands to match its actual layout.
- The `Normalize release tag` step strips a single leading `v` (for example, `v1.2.3` -> `1.2.3`) and the npm/pip steps pin to that normalized version.
- The pip step must update tracked files (for example `requirements.txt`, `pyproject.toml`, or lock files), not just install locally.