Skip to content

fix(release): correct VSIX tombstone identity to AgentOpsToolkit.agen… #24

fix(release): correct VSIX tombstone identity to AgentOpsToolkit.agen…

fix(release): correct VSIX tombstone identity to AgentOpsToolkit.agen… #24

Workflow file for this run

# AgentOps Toolkit — Staging (TestPyPI + VSIX Pre-release)
#
# Workflows:
# 1. ci.yml — Lint + test on every push/PR; VSIX build validation
# 2. _build.yml — Reusable build (test + package), called by staging and release
# 3. staging.yml — Staging: release/* → TestPyPI → verify; VSIX pre-release → Marketplace
# 4. release.yml — Production: v* tag → TestPyPI → verify → PyPI → GH Release; VSIX stable → Marketplace
# 5. cut-release.yml — Manual dispatch: create release branch + PR from develop
#
# Triggered by pushes to release/* branches.
# Calls the reusable _build.yml, publishes to TestPyPI, verifies the
# package installs correctly with a CLI smoke test, and publishes the
# VS Code extension as a pre-release to the Marketplace.
#
# Branch flow:
# develop → release/v0.2.0 → push → this workflow
# → build → TestPyPI → verify install → ✅ ready to merge and tag
# → VSIX pre-release → Marketplace (early access channel)
#
# Versioning:
# Uses setuptools-scm — on a release branch 5 commits after the last tag,
# the version will be something like 0.2.0.dev5 (PEP 440 pre-release).
# VSIX version is managed in plugins/agentops/package.json.
#
# Trusted Publishing (OIDC):
# This workflow uses PyPI Trusted Publishing — no API tokens required.
# Authentication is handled via OpenID Connect (OIDC) between GitHub Actions
# and TestPyPI.
#
# Required GitHub secrets (environment: staging):
# VSCE_PAT — VS Code Marketplace PAT. MUST have "Marketplace: Manage" scope covering
# BOTH the AgentOpsAccelerator and AgentOpsToolkit publishers.
#
# Setup (Trusted Publishing + VSCE):
# 1. https://test.pypi.org/manage/project/agentops-accelerator/settings/publishing/
# → Add publisher: GitHub, owner=Azure, repo=agentops, workflow=staging.yml, environment=staging
# 2. https://test.pypi.org/manage/project/agentops-toolkit/settings/publishing/
# → Add publisher: GitHub, owner=Azure, repo=agentops, workflow=staging.yml, environment=staging
# (Required for the deprecation tombstone re-publish of agentops-toolkit on TestPyPI.)
# 3. GitHub repo → Settings → Environments → Create "staging" environment (optional approval)
# 4. https://dev.azure.com/ → PAT with Marketplace scope on the AzDO account that
# owns BOTH the AgentOpsAccelerator AND AgentOpsToolkit publishers → Create VSCE_PAT.
# Verify scope with: vsce ls-publishers -p $VSCE_PAT (must list BOTH publishers).
# 5. Add VSCE_PAT to staging environment
name: Staging
on:
push:
branches:
- "release/**"
workflow_dispatch:
jobs:
# Reusable build: test + package
build:
uses: ./.github/workflows/_build.yml
# Build the tombstone metapackage (agentops-toolkit → agentops-accelerator redirect).
# Version is hardcoded in tombstones/pypi/pyproject.toml; no setuptools-scm involvement.
build-pypi-tombstone:
runs-on: ubuntu-latest
outputs:
tombstone_version: ${{ steps.tver.outputs.version }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Resolve tombstone version
id: tver
run: |
VERSION=$(python -c "import tomllib, pathlib; print(tomllib.loads(pathlib.Path('tombstones/pypi/pyproject.toml').read_text())['project']['version'])")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Tombstone version resolved: $VERSION"
- name: Install build tooling
run: python -m pip install --upgrade pip build
- name: Build tombstone distribution
run: python -m build tombstones/pypi --outdir dist-pypi-tombstone
- name: Assert tombstone artifact filenames
env:
TOMBSTONE_VERSION: ${{ steps.tver.outputs.version }}
run: |
python - <<'PY'
import os
import sys
from pathlib import Path
version = os.environ["TOMBSTONE_VERSION"]
dist = Path("dist-pypi-tombstone")
expected = {
f"agentops_toolkit-{version}.tar.gz",
f"agentops_toolkit-{version}-py3-none-any.whl",
}
actual = {p.name for p in dist.iterdir() if p.is_file()}
missing = expected - actual
if missing:
print(f"::error::Tombstone build is missing expected artifacts: {sorted(missing)}")
print("Contents of dist-pypi-tombstone/:")
for artifact in sorted(dist.iterdir()):
print(f" - {artifact.name}")
sys.exit(1)
print("Tombstone artifacts:")
for name in sorted(actual):
print(f" - {name}")
PY
- name: Upload tombstone build artifact
uses: actions/upload-artifact@v7
with:
name: dist-pypi-tombstone
path: dist-pypi-tombstone/
# Publish to TestPyPI
publish-testpypi:
needs: build
runs-on: ubuntu-latest
environment: staging
permissions:
id-token: write # Required for PyPI Trusted Publishing (OIDC)
steps:
- name: Download build artifacts
uses: actions/download-artifact@v8
with:
name: dist
path: dist/
- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
verbose: true
skip-existing: true # Allow re-pushes without failure
# Publish the tombstone metapackage to TestPyPI.
# Gated AFTER publish-testpypi so the real package lands first; if the
# tombstone fails, hotfix it later — never the other way around.
publish-tombstone-testpypi:
needs: [build-pypi-tombstone, publish-testpypi]
runs-on: ubuntu-latest
environment: staging
permissions:
id-token: write # Required for PyPI Trusted Publishing (OIDC)
steps:
- name: Download tombstone build artifact
uses: actions/download-artifact@v8
with:
name: dist-pypi-tombstone
path: dist-pypi-tombstone/
- name: Publish tombstone to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
packages-dir: dist-pypi-tombstone/
verbose: true
skip-existing: true
# Install from TestPyPI and smoke-test the CLI
verify-testpypi:
needs: publish-testpypi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Determine expected version
id: version
run: |
pip install setuptools-scm
VERSION=$(python -m setuptools_scm)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Expected version: $VERSION"
- name: Install from TestPyPI
run: |
for i in 1 2 3 4 5; do
echo "Attempt $i: installing agentops-accelerator==${{ steps.version.outputs.version }}"
if pip install \
"agentops-accelerator==${{ steps.version.outputs.version }}" \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/; then
exit 0
fi
if [ "$i" -lt 5 ]; then
echo "Not available yet, waiting 30s..."
sleep 30
fi
done
echo "::error::agentops-accelerator==${{ steps.version.outputs.version }} was not available from TestPyPI after 5 attempts."
exit 1
- name: Smoke test — version and help
run: |
agentops --version
agentops --help
- name: Smoke test — init in temp directory
run: |
TMPDIR=$(mktemp -d)
cd "$TMPDIR"
agentops init --no-prompt --azd-env testenv
test -f agentops.yaml
test -f .agentops/data/smoke.jsonl
test -f .azure/config.json
echo "✅ agentops init succeeded"
# Install the tombstone from TestPyPI and verify it redirects to agentops-accelerator.
#
# Staging-only divergence from release.yml: the main package (agentops-accelerator)
# is built with setuptools-scm and published to TestPyPI as 0.2.3.devN, while the
# tombstone (agentops-toolkit) hardcodes `agentops-accelerator>=0.3.0` in its
# pyproject.toml. In release.yml the two packages publish in lockstep at the
# same 0.3.x tag, so pip resolves the dep cleanly. In staging the dep can never
# resolve via a standard `pip install`, so we install the tombstone with
# `--no-deps` (to validate its wheel metadata) and then install the latest
# pre-release of agentops-accelerator separately (to satisfy `import agentops`).
verify-tombstone-testpypi:
needs: [build-pypi-tombstone, publish-tombstone-testpypi]
runs-on: ubuntu-latest
steps:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install agentops-toolkit==${{ needs.build-pypi-tombstone.outputs.tombstone_version }} from TestPyPI (no deps)
env:
TOMBSTONE_VERSION: ${{ needs.build-pypi-tombstone.outputs.tombstone_version }}
run: |
# --no-deps: in staging the tombstone's `agentops-accelerator>=0.3.0`
# requirement cannot be satisfied (staging publishes 0.2.3.devN). We
# install the wheel for its metadata only; the dep is provided by the
# next step. Retries cover TestPyPI replication lag for the wheel that
# `publish-tombstone-testpypi` just uploaded.
for i in 1 2 3 4 5; do
echo "Attempt $i: installing agentops-toolkit==${TOMBSTONE_VERSION} from TestPyPI (no deps)"
if pip install \
"agentops-toolkit==${TOMBSTONE_VERSION}" \
--no-deps \
--index-url https://test.pypi.org/simple/; then
exit 0
fi
if [ "$i" -lt 5 ]; then
echo "Not available yet, waiting 30s..."
sleep 30
fi
done
echo "::error::agentops-toolkit==${TOMBSTONE_VERSION} was not available from TestPyPI after 5 attempts."
exit 1
- name: Install agentops-accelerator pre-release from TestPyPI
run: |
# Satisfies the tombstone's runtime dep so `import agentops` resolves
# to the real package. `--pre` is required because in staging TestPyPI
# only carries dev versions (0.2.3.devN); without it pip would refuse
# to install them. The extra-index-url lets transitive deps fall back
# to real PyPI. We don't pin a version here because the accelerator
# version is not exposed as a job output; the latest dev release is
# sufficient for an import smoke check.
for i in 1 2 3 4 5; do
echo "Attempt $i: installing agentops-accelerator (pre) from TestPyPI"
if pip install \
--pre \
"agentops-accelerator" \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/; then
exit 0
fi
if [ "$i" -lt 5 ]; then
echo "Not available yet, waiting 30s..."
sleep 30
fi
done
echo "::error::agentops-accelerator was not available from TestPyPI after 5 attempts."
exit 1
- name: Verify tombstone redirects to agentops-accelerator
run: |
# The tombstone declares agentops-accelerator as its sole dependency; pip show must list it under Requires.
if ! pip show agentops-toolkit | grep -E '^Requires:' | grep -q 'agentops-accelerator'; then
echo "::error::agentops-toolkit does not declare agentops-accelerator under Requires."
pip show agentops-toolkit
exit 1
fi
# Importing agentops must resolve via the real package shipped by agentops-accelerator.
python -c "import agentops; print(agentops.__file__)"
echo "✅ tombstone redirect verified"
# ── VSIX Pre-release ─────────────────────────────────────────────────
# Publish the VS Code extension as a pre-release to the Marketplace.
# Runs in parallel with the TestPyPI flow (only needs source checkout).
publish-vsix-prerelease:
needs: build # gate on successful lint + test
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v6
- name: Sync VSIX version from branch name
run: |
# Derive version from the release branch name (e.g. release/v0.1.8 → 0.1.8).
# This avoids the PATCH+1 heuristic that leaked future versions to Marketplace.
BRANCH="${GITHUB_REF_NAME}"
VERSION="${BRANCH#release/v}"
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Could not derive semver from branch '$BRANCH'. Expected release/vX.Y.Z format."
exit 1
fi
jq --arg v "$VERSION" '.version = $v' \
plugins/agentops/package.json > plugins/agentops/package.json.tmp
mv plugins/agentops/package.json.tmp plugins/agentops/package.json
echo "VSIX version set to $VERSION (from branch $BRANCH)"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "22"
- name: Install vsce
run: npm install -g @vscode/vsce
- name: Copy root assets for VSIX
run: |
cp CHANGELOG.md plugins/agentops/CHANGELOG.md
cp icon.png plugins/agentops/icon.png
- name: Package VSIX (pre-release)
working-directory: plugins/agentops
run: vsce package --pre-release -o agentops-skills.vsix
- name: Publish pre-release to VS Code Marketplace
continue-on-error: true # Tolerate "already exists" for pre-release builds
working-directory: plugins/agentops
run: vsce publish --pre-release --packagePath agentops-skills.vsix -p "${{ secrets.VSCE_PAT }}"
- name: Show VSIX info
working-directory: plugins/agentops
run: |
ls -lh agentops-skills.vsix
echo "✅ VSIX pre-release published to Marketplace"
- name: Upload VSIX artifact
uses: actions/upload-artifact@v7
with:
name: vsix
path: plugins/agentops/agentops-skills.vsix
# ── VSIX Pre-release Tombstone ───────────────────────────────────────
# Publish the deprecated AgentOpsToolkit publisher VSIX as pre-release.
# Gated AFTER publish-vsix-prerelease so the replacement is on the
# pre-release channel before the tombstone redirects users to it.
# No dependency on build-pypi-tombstone: this is a separate (VSIX) channel
# and does not consume the PyPI tombstone artifact.
publish-tombstone-vsix-prerelease:
needs: publish-vsix-prerelease
runs-on: ubuntu-latest
environment: staging
env:
VSIX_FILE: agentops-toolkit-tombstone.vsix
steps:
- uses: actions/checkout@v6
- name: Substitute CHANGELOG date placeholder
working-directory: tombstones/vscode
run: |
TODAY=$(date -u +%Y-%m-%d)
sed -i "s/YYYY-MM-DD/$TODAY/g" CHANGELOG.md
if grep -q "YYYY-MM-DD" CHANGELOG.md; then
echo "::error::CHANGELOG.md still contains the YYYY-MM-DD placeholder after substitution."
exit 1
fi
echo "CHANGELOG date set to $TODAY"
- name: Sync tombstone VSIX version from branch name
run: |
# Derive version from the release branch name (e.g. release/v0.1.8 → 0.1.8).
BRANCH="${GITHUB_REF_NAME}"
VERSION="${BRANCH#release/v}"
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Could not derive semver from branch '$BRANCH'. Expected release/vX.Y.Z format."
exit 1
fi
jq --arg v "$VERSION" '.version = $v' \
tombstones/vscode/package.json > tombstones/vscode/package.json.tmp
mv tombstones/vscode/package.json.tmp tombstones/vscode/package.json
echo "Tombstone VSIX version set to $VERSION (from branch $BRANCH)"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "22"
- name: Install vsce
run: npm install -g @vscode/vsce
- name: Install tombstone npm dependencies
working-directory: tombstones/vscode
run: npm install --no-audit --no-fund
- name: Compile tombstone TypeScript
working-directory: tombstones/vscode
run: npm run compile
- name: Package tombstone VSIX (pre-release)
working-directory: tombstones/vscode
run: vsce package --pre-release -o "${VSIX_FILE}"
- name: Publish pre-release tombstone to VS Code Marketplace
continue-on-error: true # Tolerate "already exists" for pre-release builds
working-directory: tombstones/vscode
run: vsce publish --pre-release --packagePath "${VSIX_FILE}" -p "${{ secrets.VSCE_PAT }}"
- name: Show tombstone VSIX info
working-directory: tombstones/vscode
run: |
ls -lh "${VSIX_FILE}"
echo "✅ Tombstone VSIX pre-release published to Marketplace"
- name: Upload tombstone VSIX artifact
uses: actions/upload-artifact@v7
with:
name: vsix-tombstone-prerelease
path: tombstones/vscode/${{ env.VSIX_FILE }}