Publish SDK packages #70
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish SDK packages | |
| env: | |
| HUSKY: 0 | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| dist-tag: | |
| description: "Tag to publish under" | |
| type: choice | |
| required: true | |
| default: "prerelease" | |
| options: | |
| - latest | |
| - prerelease | |
| - unstable | |
| version: | |
| description: "Version override (optional, e.g., 1.0.0). If empty, auto-increments." | |
| type: string | |
| required: false | |
| permissions: | |
| contents: write | |
| id-token: write # Required for OIDC | |
| actions: write # Required to trigger changelog workflow | |
| concurrency: | |
| group: publish | |
| cancel-in-progress: false | |
| jobs: | |
| # Shared job to calculate version once for all publish jobs | |
| version: | |
| name: Calculate Version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.VERSION }} | |
| current: ${{ steps.version.outputs.CURRENT }} | |
| current-prerelease: ${{ steps.version.outputs.CURRENT_PRERELEASE }} | |
| defaults: | |
| run: | |
| working-directory: ./nodejs | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22.x" | |
| - run: npm ci --ignore-scripts | |
| - name: Get version | |
| id: version | |
| run: | | |
| CURRENT="$(node scripts/get-version.js current)" | |
| echo "CURRENT=$CURRENT" >> $GITHUB_OUTPUT | |
| echo "Current latest version: $CURRENT" >> $GITHUB_STEP_SUMMARY | |
| CURRENT_PRERELEASE="$(node scripts/get-version.js current-prerelease)" | |
| echo "CURRENT_PRERELEASE=$CURRENT_PRERELEASE" >> $GITHUB_OUTPUT | |
| echo "Current prerelease version: $CURRENT_PRERELEASE" >> $GITHUB_STEP_SUMMARY | |
| if [ -n "${{ github.event.inputs.version }}" ]; then | |
| VERSION="${{ github.event.inputs.version }}" | |
| # Validate version format matches dist-tag | |
| # TEMPORARY: skips validation for "latest" so prerelease versions | |
| # can be published under that tag. To ship stable 1.0.0, revert the | |
| # commit that introduced this temporary change. | |
| if [ "${{ github.event.inputs.dist-tag }}" != "latest" ]; then | |
| if [[ "$VERSION" != *-* ]]; then | |
| echo "❌ Error: Version '$VERSION' has no prerelease suffix but dist-tag is '${{ github.event.inputs.dist-tag }}'" >> $GITHUB_STEP_SUMMARY | |
| echo "Use a version with suffix (e.g., '1.0.0-preview.0') for prerelease/unstable" | |
| exit 1 | |
| fi | |
| fi | |
| echo "Using manual version override: $VERSION" >> $GITHUB_STEP_SUMMARY | |
| else | |
| VERSION="$(node scripts/get-version.js ${{ github.event.inputs.dist-tag }})" | |
| echo "Auto-incremented version: $VERSION" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "VERSION=$VERSION" >> $GITHUB_OUTPUT | |
| publish-nodejs: | |
| name: Publish Node.js SDK | |
| needs: version | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: ./nodejs | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22.x" | |
| - name: Update npm for OIDC support | |
| run: npm i -g "npm@11.6.3" | |
| - run: npm ci --ignore-scripts | |
| - name: Set version | |
| run: node scripts/set-version.js | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| - name: Build | |
| run: npm run build | |
| - name: Pack | |
| run: npm pack | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7.0.0 | |
| with: | |
| name: nodejs-package | |
| path: nodejs/*.tgz | |
| - name: Publish to npm | |
| if: github.ref == 'refs/heads/main' || github.event.inputs.dist-tag == 'unstable' | |
| run: npm publish --tag ${{ github.event.inputs.dist-tag }} --access public --registry https://registry.npmjs.org | |
| publish-dotnet: | |
| name: Publish .NET SDK | |
| if: github.event.inputs.dist-tag != 'unstable' | |
| needs: version | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: ./dotnet | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Restore dependencies | |
| run: dotnet restore | |
| - name: Build and pack | |
| run: dotnet pack src/GitHub.Copilot.SDK.csproj -c Release -p:Version=${{ needs.version.outputs.version }} -o ./artifacts | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7.0.0 | |
| with: | |
| name: dotnet-package | |
| path: | | |
| dotnet/artifacts/*.nupkg | |
| dotnet/artifacts/*.snupkg | |
| - name: NuGet login (OIDC) | |
| if: github.ref == 'refs/heads/main' | |
| uses: NuGet/login@v1 | |
| id: nuget-login | |
| with: | |
| # The following must be a username, not an organization name, and that user must have configured Trusted Publishing | |
| # for this owner/repo/workflow combination in their NuGet.org account settings. We could set up a dedicated user for | |
| # this purpose if needed, but then we'd have to manage that account separately. Other GitHub-owned packages on NuGet | |
| # are associated with individual maintainers' accounts too. | |
| user: stevesanderson | |
| - name: Publish to NuGet | |
| if: github.ref == 'refs/heads/main' | |
| run: | | |
| dotnet nuget push ./artifacts/*.nupkg --api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate --no-symbols | |
| dotnet nuget push ./artifacts/*.snupkg --api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate | |
| publish-rust: | |
| name: Publish Rust SDK | |
| if: github.event.inputs.dist-tag != 'unstable' | |
| needs: version | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: ./rust | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: "1.94.0" | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: "rust" | |
| - name: Set version | |
| run: sed -i -E 's/^version = ".*"$/version = "${{ needs.version.outputs.version }}"/' Cargo.toml | |
| - name: Snapshot CLI version + hashes for build.rs | |
| run: bash scripts/snapshot-bundled-cli-version.sh | |
| - name: Verify cli-version.txt exists | |
| run: | | |
| if [[ ! -f cli-version.txt ]]; then | |
| echo "::error::cli-version.txt was not generated. The Snapshot step must run before packaging." | |
| exit 1 | |
| fi | |
| - name: Package (dry run) | |
| run: cargo publish --dry-run --allow-dirty | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7.0.0 | |
| with: | |
| name: rust-package | |
| path: rust/target/package/*.crate | |
| - name: Publish to crates.io | |
| if: github.ref == 'refs/heads/main' | |
| run: cargo publish --allow-dirty | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| publish-python: | |
| name: Publish Python SDK | |
| if: github.event.inputs.dist-tag != 'unstable' | |
| needs: version | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: ./python | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22.x" | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Install Node.js dependencies (for CLI version) | |
| working-directory: ./nodejs | |
| run: npm ci --ignore-scripts | |
| - name: Set version | |
| run: sed -i "s/^version = .*/version = \"${{ needs.version.outputs.version }}\"/" pyproject.toml | |
| - name: Build platform wheels | |
| run: node scripts/build-wheels.mjs --output-dir dist | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7.0.0 | |
| with: | |
| name: python-package | |
| path: python/dist/* | |
| - name: Publish to PyPI | |
| if: github.ref == 'refs/heads/main' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: python/dist/ | |
| github-release: | |
| name: Create GitHub Release | |
| needs: [version, publish-nodejs, publish-dotnet, publish-python, publish-rust] | |
| if: github.ref == 'refs/heads/main' && github.event.inputs.dist-tag != 'unstable' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| # TEMPORARY: both "latest" and "prerelease" create GitHub pre-releases | |
| # since "latest" publishes beta versions. To ship stable 1.0.0, revert | |
| # the commit that introduced this temporary change. | |
| - name: Create GitHub Release | |
| if: github.event.inputs.dist-tag == 'latest' || github.event.inputs.dist-tag == 'prerelease' | |
| run: | | |
| NOTES_FLAG="" | |
| if git rev-parse "v${{ needs.version.outputs.current-prerelease }}" >/dev/null 2>&1; then | |
| NOTES_FLAG="--notes-start-tag v${{ needs.version.outputs.current-prerelease }}" | |
| fi | |
| gh release create "v${{ needs.version.outputs.version }}" \ | |
| --prerelease \ | |
| --title "v${{ needs.version.outputs.version }}" \ | |
| --generate-notes $NOTES_FLAG \ | |
| --target ${{ github.sha }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Trigger changelog generation | |
| run: gh workflow run release-changelog.lock.yml -f tag="v${{ needs.version.outputs.version }}" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Tag Go SDK submodule | |
| if: github.event.inputs.dist-tag == 'latest' || github.event.inputs.dist-tag == 'prerelease' | |
| run: | | |
| set -e | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git fetch --tags | |
| TAG_NAME="go/v${{ needs.version.outputs.version }}" | |
| # Try to create the tag - will fail if it already exists | |
| if git tag "$TAG_NAME" ${{ github.sha }} 2>/dev/null; then | |
| git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git "$TAG_NAME" | |
| echo "Created and pushed tag $TAG_NAME" | |
| else | |
| echo "Tag $TAG_NAME already exists, skipping" | |
| fi | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Tag Rust SDK and create Rust GitHub Release | |
| # Rust gets its own version-scoped GitHub Release with notes | |
| # derived from PR titles since the previous Rust tag. The | |
| # cross-language `vX.Y.Z` release above still exists; this one | |
| # is the canonical reference for Rust users. | |
| if: github.event.inputs.dist-tag == 'latest' || github.event.inputs.dist-tag == 'prerelease' | |
| run: | | |
| set -e | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git fetch --tags | |
| VERSION="${{ needs.version.outputs.version }}" | |
| TAG_NAME="rust/v${VERSION}" | |
| if git tag "$TAG_NAME" ${{ github.sha }} 2>/dev/null; then | |
| git push https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git "$TAG_NAME" | |
| echo "Created and pushed tag $TAG_NAME" | |
| else | |
| echo "Tag $TAG_NAME already exists, skipping tag push" | |
| fi | |
| # Find the previous Rust tag for note generation. Prefer rust/v*, | |
| # fall back to the historical rust-v* tags from the release-plz era. | |
| PREV_TAG=$(git tag --list 'rust/v*' --sort=-v:refname | grep -vFx "$TAG_NAME" | head -n1) | |
| if [ -z "$PREV_TAG" ]; then | |
| PREV_TAG=$(git tag --list 'rust-v*' --sort=-v:refname | head -n1) | |
| fi | |
| NOTES_FLAG="" | |
| if [ -n "$PREV_TAG" ]; then | |
| NOTES_FLAG="--notes-start-tag $PREV_TAG" | |
| echo "Generating notes from $PREV_TAG..$TAG_NAME" | |
| else | |
| echo "No previous Rust tag found; generating notes from full history" | |
| fi | |
| PRERELEASE_FLAG="" | |
| if [ "${{ github.event.inputs.dist-tag }}" = "prerelease" ]; then | |
| PRERELEASE_FLAG="--prerelease" | |
| fi | |
| gh release create "$TAG_NAME" \ | |
| --title "$TAG_NAME" \ | |
| --generate-notes $NOTES_FLAG $PRERELEASE_FLAG \ | |
| --target ${{ github.sha }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |