Release Integration Packages #54
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: Release Integration Packages | |
| # Tag scheme: zep-<framework>-<language>-v<version> | |
| # e.g. zep-adk-python-v0.2.0, zep-mastra-typescript-v0.1.0 | |
| # Go modules are tested, tagged, and released from workflow_dispatch. Their tags match | |
| # the module subpath, integrations/<framework>/go/vX.Y.Z, consumed via | |
| # go get github.com/getzep/zep/integrations/<framework>/go@vX.Y.Z | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| framework: | |
| description: 'Framework key (directory under integrations/, e.g. adk, crewai)' | |
| required: true | |
| language: | |
| description: 'Language' | |
| required: true | |
| type: choice | |
| options: | |
| - python | |
| - typescript | |
| - go | |
| version: | |
| description: 'Go version without the v prefix (required for Go, e.g. 0.1.0)' | |
| required: false | |
| permissions: | |
| contents: read | |
| id-token: write | |
| jobs: | |
| detect: | |
| # zep-ingest is released by release-ingestion.yml. A published ingestion | |
| # release also emits this repository-wide event, so skip it cleanly. | |
| if: github.event_name == 'workflow_dispatch' || !startsWith(github.event.release.tag_name, 'zep-ingest-v') | |
| runs-on: ubuntu-latest | |
| outputs: | |
| framework: ${{ steps.detect.outputs.framework }} | |
| language: ${{ steps.detect.outputs.language }} | |
| version: ${{ steps.detect.outputs.version }} | |
| steps: | |
| - name: Detect framework + language | |
| id: detect | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_FRAMEWORK: ${{ github.event.inputs.framework }} | |
| INPUT_LANGUAGE: ${{ github.event.inputs.language }} | |
| INPUT_VERSION: ${{ github.event.inputs.version }} | |
| RELEASE_TAG: ${{ github.event.release.tag_name }} | |
| run: | | |
| if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
| FRAMEWORK="$INPUT_FRAMEWORK" | |
| LANGUAGE="$INPUT_LANGUAGE" | |
| VERSION="$INPUT_VERSION" | |
| else | |
| TAG="$RELEASE_TAG" | |
| # zep-<framework>-<language>-v<version> | |
| if [[ $TAG =~ ^zep-([a-z0-9][a-z0-9-]*)-(python|typescript)-v(.+)$ ]]; then | |
| FRAMEWORK="${BASH_REMATCH[1]}" | |
| LANGUAGE="${BASH_REMATCH[2]}" | |
| VERSION="${BASH_REMATCH[3]}" | |
| elif [[ $TAG =~ ^integrations/([a-z0-9][a-z0-9-]*)/go/v(.+)$ ]]; then | |
| # Go releases created with GITHUB_TOKEN do not recursively trigger this | |
| # workflow. Recognize externally-created Go releases so they no-op cleanly. | |
| FRAMEWORK="${BASH_REMATCH[1]}" | |
| LANGUAGE="go" | |
| VERSION="${BASH_REMATCH[2]}" | |
| else | |
| echo "Tag '$TAG' does not match a supported integration release tag." | |
| exit 1 | |
| fi | |
| fi | |
| if [[ ! "$FRAMEWORK" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then | |
| echo "Invalid framework key '$FRAMEWORK'." | |
| exit 1 | |
| fi | |
| if [[ ! "$LANGUAGE" =~ ^(python|typescript|go)$ ]]; then | |
| echo "Unsupported integration language '$LANGUAGE'." | |
| exit 1 | |
| fi | |
| { | |
| echo "framework=$FRAMEWORK" | |
| echo "language=$LANGUAGE" | |
| echo "version=$VERSION" | |
| } >> "$GITHUB_OUTPUT" | |
| # ---------------------------------------------- Go → scoped tag + GitHub Release | |
| validate-go: | |
| if: github.event_name == 'workflow_dispatch' && needs.detect.outputs.language == 'go' | |
| needs: detect | |
| runs-on: ubuntu-latest | |
| outputs: | |
| framework: ${{ steps.metadata.outputs.framework }} | |
| module: ${{ steps.metadata.outputs.module }} | |
| package_dir: ${{ steps.metadata.outputs.package_dir }} | |
| tag: ${{ steps.metadata.outputs.tag }} | |
| version: ${{ steps.metadata.outputs.version }} | |
| steps: | |
| - name: Validate release metadata | |
| id: metadata | |
| env: | |
| FRAMEWORK: ${{ needs.detect.outputs.framework }} | |
| VERSION: ${{ needs.detect.outputs.version }} | |
| run: | | |
| if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then | |
| echo "Go releases must be dispatched from main (got '$GITHUB_REF')." | |
| exit 1 | |
| fi | |
| if [[ ! "$FRAMEWORK" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then | |
| echo "Invalid framework key '$FRAMEWORK'." | |
| exit 1 | |
| fi | |
| if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then | |
| echo "Invalid Go semantic version '$VERSION' (expected e.g. 0.1.0 or 1.2.0-rc.1)." | |
| exit 1 | |
| fi | |
| if [[ "$VERSION" == *-* ]]; then | |
| IFS='.' read -ra PRERELEASE_IDENTIFIERS <<< "${VERSION#*-}" | |
| for identifier in "${PRERELEASE_IDENTIFIERS[@]}"; do | |
| if [[ "$identifier" =~ ^[0-9]+$ && "$identifier" == 0* && "$identifier" != "0" ]]; then | |
| echo "Invalid numeric prerelease identifier '$identifier': leading zeroes are not allowed." | |
| exit 1 | |
| fi | |
| done | |
| fi | |
| PACKAGE_DIR="integrations/$FRAMEWORK/go" | |
| MODULE="github.com/$GITHUB_REPOSITORY/$PACKAGE_DIR" | |
| MAJOR="${VERSION%%.*}" | |
| if (( MAJOR >= 2 )); then | |
| MODULE="$MODULE/v$MAJOR" | |
| fi | |
| { | |
| echo "framework=$FRAMEWORK" | |
| echo "module=$MODULE" | |
| echo "package_dir=$PACKAGE_DIR" | |
| echo "tag=$PACKAGE_DIR/v$VERSION" | |
| echo "version=v$VERSION" | |
| } >> "$GITHUB_OUTPUT" | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.sha }} | |
| - name: Validate Go module path | |
| env: | |
| EXPECTED_MODULE: ${{ steps.metadata.outputs.module }} | |
| PACKAGE_DIR: ${{ steps.metadata.outputs.package_dir }} | |
| run: | | |
| if [[ ! -f "$PACKAGE_DIR/go.mod" ]]; then | |
| echo "Go module $PACKAGE_DIR/go.mod does not exist." | |
| exit 1 | |
| fi | |
| ACTUAL_MODULE="$(awk '$1 == "module" { print $2; exit }' "$PACKAGE_DIR/go.mod")" | |
| if [[ "$ACTUAL_MODULE" != "$EXPECTED_MODULE" ]]; then | |
| echo "go.mod declares '$ACTUAL_MODULE'; expected '$EXPECTED_MODULE'." | |
| exit 1 | |
| fi | |
| - name: Test Go integration | |
| uses: ./.github/actions/test-go | |
| with: | |
| package: ${{ steps.metadata.outputs.framework }} | |
| go-version: '1.26' | |
| publish-go: | |
| if: github.event_name == 'workflow_dispatch' && needs.detect.outputs.language == 'go' | |
| needs: [detect, validate-go] | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: release | |
| url: https://github.com/${{ github.repository }}/releases | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.sha }} | |
| - name: Create scoped tag and GitHub Release | |
| env: | |
| FRAMEWORK: ${{ needs.validate-go.outputs.framework }} | |
| GH_TOKEN: ${{ github.token }} | |
| MODULE: ${{ needs.validate-go.outputs.module }} | |
| PACKAGE_DIR: ${{ needs.validate-go.outputs.package_dir }} | |
| TAG: ${{ needs.validate-go.outputs.tag }} | |
| VERSION: ${{ needs.validate-go.outputs.version }} | |
| run: | | |
| TAG_EXISTS=false | |
| TAG_REF="$(git ls-remote --tags origin "refs/tags/$TAG")" | |
| if [[ -n "$TAG_REF" ]]; then | |
| TAG_EXISTS=true | |
| git fetch origin "refs/tags/$TAG:refs/tags/$TAG" | |
| TAG_SHA="$(git rev-list -n 1 "$TAG")" | |
| if [[ "$TAG_SHA" != "$GITHUB_SHA" ]]; then | |
| echo "Tag '$TAG' already points to $TAG_SHA, not $GITHUB_SHA." | |
| exit 1 | |
| fi | |
| fi | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| echo "Release '$TAG' already exists at the validated commit; continuing verification." | |
| exit 0 | |
| fi | |
| NOTES_FILE="$(mktemp)" | |
| printf '%s\n\n' "Go module: \`$MODULE\`" > "$NOTES_FILE" | |
| # shellcheck disable=SC2016 # Markdown fences are intentionally literal. | |
| printf 'Install with:\n\n```bash\ngo get %s@%s\n```\n\n' "$MODULE" "$VERSION" >> "$NOTES_FILE" | |
| printf '[Changelog](https://github.com/%s/blob/%s/%s/CHANGELOG.md)\n' \ | |
| "$GITHUB_REPOSITORY" "$TAG" "$PACKAGE_DIR" >> "$NOTES_FILE" | |
| RELEASE_ARGS=( | |
| "$TAG" | |
| --title "Zep $FRAMEWORK Go integration $VERSION" | |
| --notes-file "$NOTES_FILE" | |
| --latest=false | |
| ) | |
| if [[ "$TAG_EXISTS" == "true" ]]; then | |
| RELEASE_ARGS+=(--verify-tag) | |
| else | |
| RELEASE_ARGS+=(--target "$GITHUB_SHA") | |
| fi | |
| gh release create "${RELEASE_ARGS[@]}" | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.26' | |
| - name: Register and verify module with the Go proxy | |
| env: | |
| MODULE: ${{ needs.validate-go.outputs.module }} | |
| VERSION: ${{ needs.validate-go.outputs.version }} | |
| run: | | |
| for attempt in {1..6}; do | |
| if GOPROXY=https://proxy.golang.org GO111MODULE=on go list -m "$MODULE@$VERSION"; then | |
| exit 0 | |
| fi | |
| if (( attempt < 6 )); then | |
| echo "Go proxy has not resolved $MODULE@$VERSION yet; retrying in 10 seconds." | |
| sleep 10 | |
| fi | |
| done | |
| echo "Go proxy did not resolve $MODULE@$VERSION after 6 attempts." | |
| exit 1 | |
| # ---------------------------------------------------------------- Python → PyPI | |
| test-python: | |
| if: needs.detect.outputs.language == 'python' | |
| needs: detect | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| # All integrations require Python >=3.11. | |
| python-version: ["3.11", "3.12", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Check package exists | |
| run: | | |
| DIR="integrations/${{ needs.detect.outputs.framework }}/python" | |
| if [ ! -d "$DIR" ]; then | |
| echo "Package directory $DIR does not exist" | |
| exit 1 | |
| fi | |
| - name: Test Python integration | |
| uses: ./.github/actions/test-python | |
| with: | |
| package: ${{ needs.detect.outputs.framework }} | |
| python-version: ${{ matrix.python-version }} | |
| build-python: | |
| if: needs.detect.outputs.language == 'python' | |
| needs: [detect, test-python] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3 | |
| with: | |
| version: "latest" | |
| - name: Set up Python | |
| run: uv python install 3.11 | |
| - name: Build package | |
| working-directory: integrations/${{ needs.detect.outputs.framework }}/python | |
| run: uv build | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ needs.detect.outputs.framework }}-python-dist | |
| path: integrations/${{ needs.detect.outputs.framework }}/python/dist/ | |
| publish-pypi: | |
| if: needs.detect.outputs.language == 'python' | |
| needs: [detect, test-python, build-python] | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: release | |
| url: https://pypi.org/p/zep-${{ needs.detect.outputs.framework }} | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ${{ needs.detect.outputs.framework }}-python-dist | |
| path: dist/ | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 | |
| with: | |
| packages-dir: dist/ | |
| # ------------------------------------------------------------ TypeScript → npm | |
| publish-npm: | |
| if: needs.detect.outputs.language == 'typescript' | |
| needs: detect | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: release | |
| url: https://www.npmjs.com/package/@getzep/zep-${{ needs.detect.outputs.framework }} | |
| defaults: | |
| run: | |
| working-directory: integrations/${{ needs.detect.outputs.framework }}/typescript | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Install npm with OIDC support | |
| run: npm install -g npm@latest | |
| - name: Check package exists | |
| run: | | |
| if [ ! -f package.json ]; then | |
| echo "package.json not found for ${{ needs.detect.outputs.framework }}/typescript" | |
| exit 1 | |
| fi | |
| - name: Install, build, test | |
| run: | | |
| npm ci | |
| npm run build --if-present | |
| npm test | |
| - name: Publish to npm | |
| run: npm publish --access public |