chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
name: Build CLI Binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [next]
|
||||
paths:
|
||||
- 'ts/packages/cli/**'
|
||||
- 'ts/packages/cli-local-tools/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/scripts/cli-release/**'
|
||||
- '.github/workflows/build-cli-binaries.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'What to do'
|
||||
type: choice
|
||||
options:
|
||||
- build-beta
|
||||
- promote-stable
|
||||
default: build-beta
|
||||
beta_tag:
|
||||
description: 'Existing beta release tag to promote (only for promote-stable, e.g. @composio/cli@0.2.20-beta.42)'
|
||||
required: false
|
||||
|
||||
# Least privilege by default: only the release job creates/edits releases and uploads assets.
|
||||
# `prepare` reads releases (gh release list) and `build` uploads workflow artifacts — both work
|
||||
# with read access. The release job opts up to `contents: write` for itself.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Print Turborepo telemetry events to the logs instead of sending them.
|
||||
env:
|
||||
TURBO_TELEMETRY_DEBUG: 1
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: Resolve Release Metadata
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
checkout_ref: ${{ steps.resolve.outputs.checkout_ref }}
|
||||
release_name: ${{ steps.resolve.outputs.release_name }}
|
||||
release_tag: ${{ steps.resolve.outputs.release_tag }}
|
||||
release_version: ${{ steps.resolve.outputs.release_version }}
|
||||
prerelease: ${{ steps.resolve.outputs.prerelease }}
|
||||
make_latest: ${{ steps.resolve.outputs.make_latest }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Resolve release target
|
||||
id: resolve
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
ACTION_INPUT: ${{ inputs.action }}
|
||||
BETA_TAG_INPUT: ${{ inputs.beta_tag }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
RUN_NUMBER: ${{ github.run_number }}
|
||||
COMMIT_SHA: ${{ github.sha }}
|
||||
run: bash .github/scripts/cli-release/resolve-release-target.sh
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.artifact }}
|
||||
needs: prepare
|
||||
if: needs.prepare.result == 'success'
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
# Never publish a partial platform set: with fail-fast disabled, the build job's
|
||||
# result is `success` only when EVERY matrix leg passes, so a single failure makes
|
||||
# `needs.build.result != 'success'` and the release job is skipped. It also surfaces
|
||||
# all platform failures at once instead of cancelling siblings on the first error.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: bun-linux-x64
|
||||
artifact: composio-linux-x64
|
||||
runner: ubuntu-latest
|
||||
local_tools_target: ''
|
||||
- target: bun-linux-arm64
|
||||
artifact: composio-linux-aarch64
|
||||
runner: ubuntu-latest
|
||||
local_tools_target: ''
|
||||
- target: bun-darwin-x64
|
||||
artifact: composio-darwin-x64
|
||||
runner: macos-15-intel
|
||||
local_tools_target: ''
|
||||
- target: bun-darwin-arm64
|
||||
artifact: composio-darwin-aarch64
|
||||
runner: macos-15
|
||||
local_tools_target: darwin-arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.checkout_ref }}
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup Swift 6.2 for local-tool sidecars
|
||||
if: matrix.local_tools_target != ''
|
||||
uses: swift-actions/setup-swift@364295d9c23900ce04d4e5cc708387921b4e50f9 # v3
|
||||
with:
|
||||
swift-version: '6.2'
|
||||
|
||||
- name: Verify Swift toolchain
|
||||
if: matrix.local_tools_target != ''
|
||||
run: swift --version
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm build:packages
|
||||
|
||||
- name: Build local-tool sidecar binaries
|
||||
if: matrix.local_tools_target != ''
|
||||
run: pnpm --filter @composio/cli-local-tools build:local-tool-binaries -- --target ${{ matrix.local_tools_target }}
|
||||
|
||||
- name: Verify local-tool sidecars
|
||||
if: matrix.local_tools_target != ''
|
||||
run: |
|
||||
test -x ts/packages/cli-local-tools/local-tools-binaries/beeper-imessage/${{ matrix.local_tools_target }}/imessage-cli
|
||||
test -x ts/packages/cli-local-tools/local-tools-binaries/peekaboo/${{ matrix.local_tools_target }}/peekaboo
|
||||
test -x ts/packages/cli-local-tools/local-tools-binaries/composio-native-ui/${{ matrix.local_tools_target }}/composio-native-ui
|
||||
|
||||
- name: Cross-compile CLI binary
|
||||
working-directory: ts/packages/cli
|
||||
run: pnpm build:binary:cross --target ${{ matrix.target }}
|
||||
|
||||
- name: Verify binary
|
||||
working-directory: ts/packages/cli
|
||||
run: |
|
||||
ls -la dist/binaries/${{ matrix.artifact }}
|
||||
file dist/binaries/${{ matrix.artifact }}
|
||||
|
||||
- name: Test binary
|
||||
if: matrix.target == 'bun-linux-x64'
|
||||
working-directory: ts/packages/cli
|
||||
env:
|
||||
COMPOSIO_USER_API_KEY: test-dummy-api-key-for-ci
|
||||
run: |
|
||||
./dist/binaries/${{ matrix.artifact }} --version
|
||||
./dist/binaries/${{ matrix.artifact }} --help | head -5
|
||||
|
||||
- name: Create archive
|
||||
working-directory: ts/packages/cli
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
|
||||
run: pnpm build:binary:package
|
||||
|
||||
- name: Verify release metadata in archive
|
||||
working-directory: ts/packages/cli
|
||||
run: unzip -p dist/binaries/${{ matrix.artifact }}.zip ${{ matrix.artifact }}/release-tag.txt | grep -Fx '${{ needs.prepare.outputs.release_tag }}'
|
||||
|
||||
- name: Verify local-tool sidecars in archive
|
||||
if: matrix.local_tools_target != ''
|
||||
working-directory: ts/packages/cli
|
||||
run: |
|
||||
unzip -Z1 dist/binaries/${{ matrix.artifact }}.zip | grep -F "local-tools-binaries/beeper-imessage/${{ matrix.local_tools_target }}/imessage-cli"
|
||||
unzip -Z1 dist/binaries/${{ matrix.artifact }}.zip | grep -F "local-tools-binaries/peekaboo/${{ matrix.local_tools_target }}/peekaboo"
|
||||
unzip -Z1 dist/binaries/${{ matrix.artifact }}.zip | grep -F "local-tools-binaries/composio-native-ui/${{ matrix.local_tools_target }}/composio-native-ui"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: ts/packages/cli/dist/binaries/${{ matrix.artifact }}.zip
|
||||
retention-days: 7
|
||||
|
||||
release:
|
||||
name: Release (draft → verify → publish)
|
||||
needs: [prepare, build]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # create/edit the GitHub Release and upload assets
|
||||
# fail-fast: false on the build matrix ⇒ this is `success` only if ALL platforms built,
|
||||
# so a partial set can never reach the release path.
|
||||
if: needs.build.result == 'success'
|
||||
# Serialize runs that target the SAME release tag (e.g. two quick pushes, or a re-run
|
||||
# racing the original) so they cannot interleave uploads. Keyed on the resolved tag so
|
||||
# unrelated beta builds are not serialized behind each other. Job-level concurrency may
|
||||
# read `needs.*` outputs (workflow-level cannot). The serialized loser fails loudly at the
|
||||
# "already published" guard in create-or-resume-draft.sh — that red ❌ is by design.
|
||||
concurrency:
|
||||
group: cli-release-${{ needs.prepare.outputs.release_tag }}
|
||||
cancel-in-progress: false
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
|
||||
RELEASE_NAME: ${{ needs.prepare.outputs.release_name }}
|
||||
PRERELEASE: ${{ needs.prepare.outputs.prerelease }}
|
||||
MAKE_LATEST: ${{ needs.prepare.outputs.make_latest }}
|
||||
CHECKOUT_REF: ${{ needs.prepare.outputs.checkout_ref }}
|
||||
RELEASE_HELPERS_DIR: ${{ github.workspace }}/.release-workflow/.github/scripts/cli-release
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.checkout_ref }}
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: ts/packages/cli/dist/binaries
|
||||
merge-multiple: true
|
||||
|
||||
# Package skills BEFORE generating checksums so composio-skill.zip is included in
|
||||
# checksums.txt alongside the platform binaries (generate-checksums hashes every .zip
|
||||
# in dist/binaries).
|
||||
- name: Package skill files
|
||||
working-directory: ts/packages/cli
|
||||
env:
|
||||
PRERELEASE: ${{ needs.prepare.outputs.prerelease }}
|
||||
run: |
|
||||
pnpm run validate:skills
|
||||
if [[ "$PRERELEASE" == "true" ]]; then
|
||||
channel=beta
|
||||
else
|
||||
channel=stable
|
||||
fi
|
||||
pnpm run build:skills -- --channel "$channel" --output-dir ./dist/skills
|
||||
cd dist/skills && zip -r ../binaries/composio-skill.zip composio-cli/
|
||||
|
||||
- name: Generate checksums
|
||||
working-directory: ts/packages/cli
|
||||
run: bun run ./scripts/generate-checksums.ts
|
||||
|
||||
- name: Display checksums
|
||||
run: cat ts/packages/cli/dist/binaries/checksums.txt
|
||||
|
||||
- name: Checkout workflow release helpers
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.workflow_sha }}
|
||||
path: .release-workflow
|
||||
sparse-checkout: |
|
||||
.github/scripts/cli-release
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
# Build the release as a DRAFT first. Drafts fire no `release: published` event and are
|
||||
# excluded from the `/releases/latest` redirect, so no anonymous consumer (install.sh,
|
||||
# Homebrew, the redirect) can ever observe a release before its assets are attached and
|
||||
# verified. The release is only flipped to published as the final step below.
|
||||
- name: Create draft release with all assets
|
||||
run: bash "$RELEASE_HELPERS_DIR/create-or-resume-draft.sh"
|
||||
|
||||
# Loud failure gate: assert the full canonical asset set is attached AND fully uploaded.
|
||||
# An asset can appear in the list while still processing (`state != "uploaded"`), which is
|
||||
# exactly how a release ends up serving 404s, so we require state == "uploaded".
|
||||
- name: Verify all release assets are present and uploaded
|
||||
run: bash "$RELEASE_HELPERS_DIR/verify-assets.sh"
|
||||
|
||||
# Publishing is the ONLY step that exposes the release. Do not edit the body here: a known
|
||||
# GitHub PATCH race drops body changes made in the same call as the draft flip, and the
|
||||
# notes were already generated at draft creation. Prerelease status was set on the draft.
|
||||
- name: Publish release (flip draft → published)
|
||||
run: gh release edit "$RELEASE_TAG" --draft=false --latest="$MAKE_LATEST"
|
||||
|
||||
test-installation:
|
||||
name: Test Installation
|
||||
needs: [prepare, release]
|
||||
if: always() && !cancelled() && needs.release.result == 'success'
|
||||
uses: ./.github/workflows/cli.test-installation.yml
|
||||
with:
|
||||
version: ${{ needs.prepare.outputs.release_tag }}
|
||||
|
||||
create-install-instructions:
|
||||
name: Create Install Instructions
|
||||
needs: [prepare, release]
|
||||
runs-on: ubuntu-latest
|
||||
if: always() && !cancelled() && needs.release.result == 'success'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Create installation README
|
||||
run: |
|
||||
RELEASE_TAG="${{ needs.prepare.outputs.release_tag }}"
|
||||
cat > INSTALL.md << 'EOF'
|
||||
# Composio CLI Installation
|
||||
|
||||
## Quick Install (Recommended)
|
||||
|
||||
### Linux and macOS
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/ComposioHQ/composio/next/install.sh | bash
|
||||
```
|
||||
|
||||
### Install specific version
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/ComposioHQ/composio/next/install.sh | bash -s -- RELEASE_TAG_PLACEHOLDER
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
|
||||
1. Download the appropriate binary for your platform from the [releases page](https://github.com/ComposioHQ/composio/releases)
|
||||
2. Extract the binary
|
||||
3. Move it to a directory in your PATH (e.g., `/usr/local/bin`)
|
||||
4. Make it executable: `chmod +x composio`
|
||||
|
||||
## Alternative: npm/pnpm Installation
|
||||
|
||||
```bash
|
||||
npm install -g @composio/cli
|
||||
# or
|
||||
pnpm add -g @composio/cli
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
composio --help
|
||||
composio login
|
||||
composio generate
|
||||
```
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
- Linux x64
|
||||
- Linux ARM64
|
||||
- macOS x64 (Intel)
|
||||
- macOS ARM64 (Apple Silicon)
|
||||
|
||||
Windows support coming soon!
|
||||
EOF
|
||||
sed -i.bak "s|RELEASE_TAG_PLACEHOLDER|$RELEASE_TAG|g" INSTALL.md && rm -f INSTALL.md.bak
|
||||
|
||||
- name: Upload install instructions
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: install-instructions
|
||||
path: INSTALL.md
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Claude Docs Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize ]
|
||||
# Run on docs changes (content, components, styles)
|
||||
paths:
|
||||
- "docs/content/**/*.mdx"
|
||||
- "docs/components/**/*.tsx"
|
||||
- "docs/app/**/*.tsx"
|
||||
- "docs/app/**/*.css"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
claude-review:
|
||||
# Optional: Filter by PR author
|
||||
# if: |
|
||||
# github.event.pull_request.user.login == 'external-contributor' ||
|
||||
# github.event.pull_request.user.login == 'new-developer' ||
|
||||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code Review
|
||||
id: claude-review
|
||||
uses: anthropics/claude-code-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # v1.0.170
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
track_progress: true # Forces tag mode with tracking comments
|
||||
allowed_bots: "devin-ai-integration[bot],decimal-pr-bot[bot],github-actions[bot],dependabot[bot],zen-agent,sdkrelease,sdkrelease[bot]"
|
||||
|
||||
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
||||
# model: "claude-opus-4-20250514"
|
||||
|
||||
# Review instructions in docs/agent-guidance/agents/docs-reviewer.md
|
||||
prompt: |
|
||||
Review this PR using the guidelines in docs/agent-guidance/agents/docs-reviewer.md
|
||||
|
||||
1. Read docs/agent-guidance/agents/docs-reviewer.md for full review criteria
|
||||
2. Run `git diff origin/${{ github.event.pull_request.base.ref }}...HEAD` to see changes
|
||||
3. Apply the review checklist to changed files
|
||||
4. If issues: Comment with specific fixes. If none: Approve with "Looks good"
|
||||
# Optional: Customize review based on file types
|
||||
# direct_prompt: |
|
||||
# Review this PR focusing on:
|
||||
# - For TypeScript files: Type safety and proper interface usage
|
||||
# - For API endpoints: Security, input validation, and error handling
|
||||
# - For React components: Performance, accessibility, and best practices
|
||||
# - For tests: Coverage, edge cases, and test quality
|
||||
|
||||
# Optional: Different prompts for different authors
|
||||
# direct_prompt: |
|
||||
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
|
||||
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
|
||||
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
|
||||
|
||||
# https://code.claude.com/docs/en/cli-reference#cli-flags
|
||||
# -- model: "claude-opus-4-20250514"
|
||||
claude_args: |
|
||||
--allowedTools "Bash(*)"
|
||||
|
||||
# Optional: Skip review for certain conditions
|
||||
# if: |
|
||||
# !contains(github.event.pull_request.title, '[skip-review]') &&
|
||||
# !contains(github.event.pull_request.title, '[WIP]')
|
||||
@@ -0,0 +1,66 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 1
|
||||
submodules: true
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # v1.0.170
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
allowed_bots: "devin-ai-integration[bot],decimal-pr-bot[bot],github-actions[bot],dependabot[bot],zen-agent"
|
||||
|
||||
# Optional: Custom settings and environment variables for Claude
|
||||
# settings: |
|
||||
# {
|
||||
# "env": {
|
||||
# "NODE_ENV": "test",
|
||||
# "CI": "true"
|
||||
# }
|
||||
# }
|
||||
|
||||
# https://code.claude.com/docs/en/cli-reference#cli-flags
|
||||
# -- model: "claude-opus-4-20250514"
|
||||
# claude_args: |
|
||||
# --allowedTools "Bash(pnpm install),Bash(pnpm build:packages),Bash(pnpm build:binary),Bash(pnpm test:*)"
|
||||
# --model: "claude-opus-4-20250514"
|
||||
# --append-system-prompt: |
|
||||
# Follow our coding standards
|
||||
# Ensure all new code has tests
|
||||
# Use TypeScript for new files
|
||||
|
||||
# Optional: Customize the trigger phrase (default: @claude)
|
||||
# trigger_phrase: "/claude"
|
||||
|
||||
# Optional: Trigger when specific user is assigned to an issue
|
||||
# assignee_trigger: "claude-bot"
|
||||
@@ -0,0 +1,120 @@
|
||||
name: Bump Homebrew Tap
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to sync (e.g. @composio/cli@0.2.28)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: bump-homebrew-tap
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
if: github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '@composio/cli@')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Resolve release tag and version
|
||||
id: meta
|
||||
env:
|
||||
TAG_INPUT: ${{ inputs.tag }}
|
||||
TAG_RELEASE: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="${TAG_INPUT:-$TAG_RELEASE}"
|
||||
if [[ -z "$tag" ]]; then
|
||||
echo "::error::No tag resolved"; exit 1
|
||||
fi
|
||||
if [[ ! "$tag" =~ ^@composio/cli@ ]]; then
|
||||
echo "::notice::Tag $tag is not a CLI release, skipping."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
# Skip beta/prerelease tags — only stable bumps the formula.
|
||||
if [[ "$tag" == *"-beta."* || "$tag" == *"-rc."* || "$tag" == *"-alpha."* ]]; then
|
||||
echo "::notice::Tag $tag is a prerelease, skipping."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
version="${tag#@composio/cli@}"
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download checksums
|
||||
if: steps.meta.outputs.skip != 'true'
|
||||
id: sha
|
||||
env:
|
||||
TAG: ${{ steps.meta.outputs.tag }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release download "$TAG" \
|
||||
--repo "${{ github.repository }}" \
|
||||
--pattern checksums.txt \
|
||||
--output checksums.txt
|
||||
extract() {
|
||||
grep " $1\$" checksums.txt | awk '{print $1}' || true
|
||||
}
|
||||
{
|
||||
echo "darwin_arm=$(extract composio-darwin-aarch64.zip)"
|
||||
echo "darwin_x86=$(extract composio-darwin-x64.zip)"
|
||||
echo "linux_arm=$(extract composio-linux-aarch64.zip)"
|
||||
echo "linux_x86=$(extract composio-linux-x64.zip)"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout source repo (for bump script)
|
||||
if: steps.meta.outputs.skip != 'true'
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Checkout homebrew tap
|
||||
if: steps.meta.outputs.skip != 'true'
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: ComposioHQ/homebrew-tap
|
||||
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||
path: tap
|
||||
|
||||
- name: Update formula
|
||||
if: steps.meta.outputs.skip != 'true'
|
||||
env:
|
||||
VERSION: ${{ steps.meta.outputs.version }}
|
||||
TAG: ${{ steps.meta.outputs.tag }}
|
||||
DARWIN_ARM: ${{ steps.sha.outputs.darwin_arm }}
|
||||
DARWIN_X86: ${{ steps.sha.outputs.darwin_x86 }}
|
||||
LINUX_ARM: ${{ steps.sha.outputs.linux_arm }}
|
||||
LINUX_X86: ${{ steps.sha.outputs.linux_x86 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
formula="tap/Formula/composio.rb"
|
||||
[[ -f "$formula" ]] || { echo "::error::$formula not found"; exit 1; }
|
||||
|
||||
.github/scripts/bump-homebrew-formula.py "$formula"
|
||||
|
||||
echo "--- updated formula ---"
|
||||
cat "$formula"
|
||||
|
||||
- name: Commit and push
|
||||
if: steps.meta.outputs.skip != 'true'
|
||||
working-directory: tap
|
||||
env:
|
||||
VERSION: ${{ steps.meta.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "composio-release-bot"
|
||||
git config user.email "composio-release-bot@users.noreply.github.com"
|
||||
if git diff --quiet; then
|
||||
echo "Formula already up to date for $VERSION; nothing to commit."
|
||||
exit 0
|
||||
fi
|
||||
git add Formula/composio.rb
|
||||
git commit -m "composio $VERSION"
|
||||
git push origin HEAD
|
||||
@@ -0,0 +1,75 @@
|
||||
name: CLI Install Health Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read # gh release list reads the published releases
|
||||
|
||||
jobs:
|
||||
health-check:
|
||||
name: Test CLI Installation
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
# Resolve the newest PUBLISHED stable @composio/cli release — exactly what install.sh and
|
||||
# the /releases/latest redirect (Homebrew, humans) resolve and download from.
|
||||
#
|
||||
# Deliberately NOT `npm view @composio/cli version`: npm's latest dist-tag is bumped by
|
||||
# changesets minutes before the binary workflow finishes the draft build and publishes the
|
||||
# GitHub release. Pinning to the npm version would 404 during that healthy publish gap and
|
||||
# false-page hourly. Drafts are excluded here, so this only ever resolves a fully-published
|
||||
# release — the in-flight one stays invisible until its assets are verified and attached.
|
||||
- name: Resolve newest published release
|
||||
id: resolve
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag=$(gh release list --repo "${{ github.repository }}" --limit 1000 --exclude-drafts --exclude-pre-releases --json tagName \
|
||||
--jq '.[] | select(.tagName | startswith("@composio/cli@")) | .tagName' \
|
||||
| sed -n '1p')
|
||||
if [[ -z "$tag" ]]; then
|
||||
echo "::error::No published @composio/cli release found"
|
||||
exit 1
|
||||
fi
|
||||
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
||||
echo "Latest published release: ${tag}"
|
||||
|
||||
# Install the PINNED newest tag. This is the path that 404s when a release ships without
|
||||
# assets. The no-arg `curl | bash` below is asset-aware and silently falls back to the
|
||||
# previous good release, so it would stay green even when the newest release is broken —
|
||||
# which is exactly why the previous canary never caught the recurring outage.
|
||||
- name: Install newest stable (pinned — fails on a release with missing assets)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -fsSL https://composio.dev/install | bash -s -- "${{ steps.resolve.outputs.tag }}"
|
||||
echo "$HOME/.composio" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Verify pinned installation
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ls -la "$HOME/.composio" || true
|
||||
which composio
|
||||
composio --version
|
||||
|
||||
# Secondary: the default no-arg flow most users run. Asset-aware and self-healing, so it
|
||||
# is a coverage net rather than the primary signal.
|
||||
- name: Install via default no-arg flow
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf "$HOME/.composio"
|
||||
curl -fsSL https://composio.dev/install | bash
|
||||
"$HOME/.composio/composio" --version
|
||||
|
||||
- name: Send Slack Notification (Failure)
|
||||
if: failure()
|
||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_RELEASE_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
{
|
||||
"text": "⚠️ CLI Install Health Check Failed!\n*Repository:* ${{ github.repository }}\n*Workflow:* ${{ github.workflow }}\n*Run:* ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Commit:* ${{ github.sha }}"
|
||||
}
|
||||
@@ -0,0 +1,638 @@
|
||||
name: Test Installation
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
# Temporary compatibility window: support both legacy v* and package-scoped CLI tags.
|
||||
- 'v*'
|
||||
- '@composio/cli@*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to test (e.g., 1.0.0 or @composio/cli@1.0.0)'
|
||||
required: true
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release tag or semver version to test'
|
||||
required: false
|
||||
default: 'latest'
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
toolchain-versions:
|
||||
name: Read toolchain test matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
node_install_compat: ${{ steps.versions.outputs.node_install_compat }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Read versions
|
||||
id: versions
|
||||
run: echo "node_install_compat=$(jq -c '.node_install_compat' toolchain-versions.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
install-script-unit-tests:
|
||||
name: Install Script Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Test release resolution fallback
|
||||
run: bash test/install-sh-release-resolution.test.sh
|
||||
|
||||
test-install-script:
|
||||
name: Test Install Script
|
||||
needs: install-script-unit-tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Ubuntu x64 versions
|
||||
- os: ubuntu-22.04
|
||||
name: 'Ubuntu 22.04 x64'
|
||||
shell: bash
|
||||
- os: ubuntu-22.04
|
||||
name: 'Ubuntu 22.04 x64 (zsh)'
|
||||
shell: zsh
|
||||
- os: ubuntu-latest
|
||||
name: 'Ubuntu Latest x64'
|
||||
shell: bash
|
||||
|
||||
# Ubuntu ARM64 versions (using Depot)
|
||||
- os: depot-ubuntu-24.04-arm
|
||||
name: 'Ubuntu 24.04 ARM64'
|
||||
shell: bash
|
||||
- os: depot-ubuntu-22.04-arm
|
||||
name: 'Ubuntu 22.04 ARM64'
|
||||
shell: bash
|
||||
- os: depot-ubuntu-22.04-arm
|
||||
name: 'Ubuntu 22.04 ARM64 (zsh)'
|
||||
shell: zsh
|
||||
|
||||
# macOS versions
|
||||
- os: macos-15-intel
|
||||
name: 'macOS 15 (Intel)'
|
||||
shell: bash
|
||||
- os: macos-15-intel
|
||||
name: 'macOS 15 (Intel, zsh)'
|
||||
shell: zsh
|
||||
- os: macos-14
|
||||
name: 'macOS 14 (Sonoma)'
|
||||
shell: bash
|
||||
- os: macos-15
|
||||
name: 'macOS 15 (Apple Silicon)'
|
||||
shell: bash
|
||||
- os: macos-15
|
||||
name: 'macOS 15 (Apple Silicon, zsh)'
|
||||
shell: zsh
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Resolve and validate target release
|
||||
id: release_target
|
||||
shell: bash
|
||||
env:
|
||||
VERSION_INPUT: ${{ inputs.version }}
|
||||
run: |
|
||||
raw_input="$VERSION_INPUT"
|
||||
if [[ -z "$raw_input" || "$raw_input" == "latest" ]]; then
|
||||
echo "use_latest=true" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# For manual runs, accept a full package tag or semver and normalize to a package tag.
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
if [[ "$raw_input" =~ ^@composio/cli@ ]]; then
|
||||
echo "use_latest=false" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=$raw_input" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
elif [[ "$raw_input" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "use_latest=false" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=@composio/cli@$raw_input" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "Invalid version: '$raw_input'"
|
||||
echo "Expected format like: 1.2.3, 1.2.3-beta.1, 1.2.3+build.7, or @composio/cli@1.2.3"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# For workflow_call / push, accept a full package tag, semver, or legacy v* tag.
|
||||
if [[ "$raw_input" =~ ^@composio/cli@ ]]; then
|
||||
echo "use_latest=false" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=$raw_input" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "$raw_input" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "use_latest=false" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=@composio/cli@$raw_input" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "use_latest=false" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=$raw_input" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
- name: Setup shell environment
|
||||
run: |
|
||||
if [[ "${{ matrix.shell }}" == "zsh" ]]; then
|
||||
# Install zsh if not present
|
||||
if ! command -v zsh &> /dev/null; then
|
||||
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y zsh
|
||||
elif [[ "${{ runner.os }}" == "macOS" ]]; then
|
||||
# zsh is default on macOS
|
||||
echo "zsh already available"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create .zshrc if it doesn't exist
|
||||
touch ~/.zshrc
|
||||
|
||||
# Set zsh as the shell for this session
|
||||
export SHELL=$(which zsh)
|
||||
echo "SHELL=$(which zsh)" >> $GITHUB_ENV
|
||||
else
|
||||
# Ensure bash is available and create .bashrc
|
||||
touch ~/.bashrc
|
||||
export SHELL=$(which bash)
|
||||
echo "SHELL=$(which bash)" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Test install script (latest version)
|
||||
if: steps.release_target.outputs.use_latest == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Testing installation with auto-detected latest CLI version..."
|
||||
|
||||
# Test the install script without version (it will auto-detect latest CLI release)
|
||||
bash install.sh
|
||||
|
||||
# Verify installation
|
||||
source ~/.bashrc 2>/dev/null || source ~/.zshrc 2>/dev/null || true
|
||||
export PATH="$HOME/.composio:$PATH"
|
||||
|
||||
# Check if binary exists
|
||||
if [[ -f "$HOME/.composio/composio" ]]; then
|
||||
echo "✅ Binary installed successfully"
|
||||
ls -la "$HOME/.composio/composio"
|
||||
else
|
||||
echo "❌ Binary not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test binary execution
|
||||
if "$HOME/.composio/composio" --version; then
|
||||
echo "✅ Binary executes successfully"
|
||||
else
|
||||
echo "❌ Binary execution failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test install script (specific version)
|
||||
if: steps.release_target.outputs.use_latest != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_TAG: ${{ steps.release_target.outputs.release_tag }}
|
||||
run: |
|
||||
echo "Testing installation with version: ${RELEASE_TAG}"
|
||||
|
||||
# Test the install script with specific version
|
||||
bash install.sh "${RELEASE_TAG}"
|
||||
|
||||
# Verify installation
|
||||
source ~/.bashrc 2>/dev/null || source ~/.zshrc 2>/dev/null || true
|
||||
export PATH="$HOME/.composio:$PATH"
|
||||
|
||||
# Check if binary exists
|
||||
if [[ -f "$HOME/.composio/composio" ]]; then
|
||||
echo "✅ Binary installed successfully"
|
||||
ls -la "$HOME/.composio/composio"
|
||||
else
|
||||
echo "❌ Binary not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test binary execution
|
||||
if "$HOME/.composio/composio" --version; then
|
||||
echo "✅ Binary executes successfully"
|
||||
else
|
||||
echo "❌ Binary execution failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test PATH integration
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Testing PATH integration with ${{ matrix.shell }}..."
|
||||
|
||||
# Debug: Check what files exist and their contents
|
||||
echo "=== DEBUGGING ==="
|
||||
echo "Checking shell config files:"
|
||||
ls -la ~/ | grep -E '\.(bash|zsh)' || echo "No shell config files found"
|
||||
|
||||
if [[ -f ~/.bashrc ]]; then
|
||||
echo "=== ~/.bashrc contents ==="
|
||||
cat ~/.bashrc
|
||||
fi
|
||||
|
||||
if [[ -f ~/.zshrc ]]; then
|
||||
echo "=== ~/.zshrc contents ==="
|
||||
cat ~/.zshrc
|
||||
fi
|
||||
|
||||
echo "=== Installation directory ==="
|
||||
ls -la ~/.composio/ || echo "~/.composio/ not found"
|
||||
|
||||
echo "=== Current PATH before sourcing ==="
|
||||
echo "$PATH"
|
||||
|
||||
if [[ "${{ matrix.shell }}" == "zsh" ]]; then
|
||||
if [[ -f ~/.zshrc ]]; then
|
||||
echo "Testing interactive zsh startup..."
|
||||
if zsh -ic 'echo "PATH after startup: $PATH"; command -v composio; composio --version; composio --help | head -5'; then
|
||||
echo "✅ composio found in PATH via zsh"
|
||||
else
|
||||
echo "❌ composio not found in PATH via zsh"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ ~/.zshrc not found - install script failed to create it"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [[ -f ~/.bashrc ]]; then
|
||||
echo "Testing interactive bash startup..."
|
||||
if bash -ic 'echo "PATH after startup: $PATH"; command -v composio; composio --version; composio --help | head -5'; then
|
||||
echo "✅ composio found in PATH via bash"
|
||||
else
|
||||
echo "❌ composio not found in PATH via bash"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ ~/.bashrc not found - install script failed to create it"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Test bundled support files
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Checking bundled support files..."
|
||||
|
||||
install_dir="${COMPOSIO_INSTALL_DIR:-$HOME/.composio}"
|
||||
release_tag=$(cat "$install_dir/release-tag.txt")
|
||||
|
||||
platform=$(uname -ms)
|
||||
case $platform in
|
||||
'Darwin x86_64') target=darwin-x64 ;;
|
||||
'Darwin arm64') target=darwin-aarch64 ;;
|
||||
'Linux aarch64' | 'Linux arm64')
|
||||
target=linux-aarch64 ;;
|
||||
'Linux x86_64') target=linux-x64 ;;
|
||||
*) echo "❌ Unsupported platform: $platform"; exit 1 ;;
|
||||
esac
|
||||
|
||||
if [[ $target = darwin-x64 ]]; then
|
||||
if [[ $(sysctl -n sysctl.proc_translated 2>/dev/null) = 1 ]]; then
|
||||
target=darwin-aarch64
|
||||
fi
|
||||
fi
|
||||
|
||||
archive_name="composio-$target.zip"
|
||||
archive_url="https://github.com/ComposioHQ/composio/releases/download/$release_tag/$archive_name"
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
|
||||
curl --fail --silent --location --output "$tmpdir/$archive_name" "$archive_url"
|
||||
|
||||
expected_paths="$(
|
||||
unzip -Z1 "$tmpdir/$archive_name" \
|
||||
| grep -v '/$' \
|
||||
| sed "s#^composio-$target/##" \
|
||||
| grep -v '^composio$' \
|
||||
| sort
|
||||
)"
|
||||
|
||||
if [ -z "$expected_paths" ]; then
|
||||
echo "❌ No bundled support files found in $archive_name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
missing=""
|
||||
echo "$expected_paths" | while IFS= read -r relative_path; do
|
||||
if [ -f "$install_dir/$relative_path" ]; then
|
||||
echo "✅ Found $relative_path"
|
||||
else
|
||||
echo "MISSING:$relative_path"
|
||||
fi
|
||||
done > "$tmpdir/check_output"
|
||||
|
||||
cat "$tmpdir/check_output" | grep -v '^MISSING:' || true
|
||||
|
||||
missing="$(grep '^MISSING:' "$tmpdir/check_output" | sed 's/^MISSING://' || true)"
|
||||
if [ -n "$missing" ]; then
|
||||
printf '❌ Missing bundled support files after install:\n' >&2
|
||||
echo "$missing" | while IFS= read -r p; do
|
||||
printf ' %s\n' "$p" >&2
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test shell config updates
|
||||
run: |
|
||||
echo "Checking shell configuration updates..."
|
||||
|
||||
if [[ "${{ matrix.shell }}" == "zsh" ]]; then
|
||||
config_file=~/.zshrc
|
||||
else
|
||||
config_file=~/.bashrc
|
||||
fi
|
||||
|
||||
if grep -q "COMPOSIO_INSTALL_DIR" "$config_file"; then
|
||||
echo "✅ $config_file updated with COMPOSIO_INSTALL_DIR"
|
||||
else
|
||||
echo "❌ $config_file not updated with COMPOSIO_INSTALL_DIR"
|
||||
echo "=== $config_file contents ==="
|
||||
cat "$config_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -q 'export PATH=.*COMPOSIO_INSTALL_DIR.*PATH' "$config_file"; then
|
||||
echo "✅ $config_file updated with PATH"
|
||||
else
|
||||
echo "❌ $config_file PATH not updated"
|
||||
echo "=== $config_file contents ==="
|
||||
cat "$config_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test custom install directory
|
||||
shell: bash
|
||||
env:
|
||||
USE_LATEST: ${{ steps.release_target.outputs.use_latest }}
|
||||
RELEASE_TAG: ${{ steps.release_target.outputs.release_tag }}
|
||||
run: |
|
||||
echo "Testing custom install directory..."
|
||||
|
||||
# Clean up previous installation
|
||||
rm -rf ~/.composio
|
||||
|
||||
# Test with custom directory using the same version as the workflow
|
||||
export COMPOSIO_INSTALL_DIR="/tmp/custom-composio"
|
||||
if [[ "$USE_LATEST" == "true" ]]; then
|
||||
bash install.sh
|
||||
else
|
||||
bash install.sh "${RELEASE_TAG}"
|
||||
fi
|
||||
|
||||
# Verify custom installation
|
||||
if [[ -f "/tmp/custom-composio/composio" ]]; then
|
||||
echo "✅ Custom directory installation successful"
|
||||
ls -la "/tmp/custom-composio/composio"
|
||||
else
|
||||
echo "❌ Custom directory installation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test execution
|
||||
if "/tmp/custom-composio/composio" --version; then
|
||||
echo "✅ Custom installation executes successfully"
|
||||
else
|
||||
echo "❌ Custom installation execution failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test uninstallation
|
||||
run: |
|
||||
echo "Testing uninstallation..."
|
||||
|
||||
# Remove binary
|
||||
rm -rf ~/.composio /tmp/custom-composio
|
||||
|
||||
# Check if binary is gone
|
||||
if [[ ! -f "$HOME/.composio/composio" ]]; then
|
||||
echo "✅ Binary removed successfully"
|
||||
else
|
||||
echo "❌ Binary removal failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test error handling
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Testing error handling..."
|
||||
|
||||
# Test with invalid version
|
||||
if bash install.sh v999.999.999 2>&1 | grep -q "Failed"; then
|
||||
echo "✅ Error handling works for invalid version"
|
||||
else
|
||||
echo "❌ Error handling failed"
|
||||
# Don't exit 1 here as this is expected to fail
|
||||
fi
|
||||
|
||||
test-npm-fallback:
|
||||
name: Test npm Installation Fallback
|
||||
needs: toolchain-versions
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-15]
|
||||
node-version: ${{ fromJSON(needs.toolchain-versions.outputs.node_install_compat) }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Test npm installation
|
||||
run: |
|
||||
echo "Testing npm installation as fallback..."
|
||||
|
||||
# This would normally install from npm registry
|
||||
# For testing, we just verify the command works
|
||||
if command -v npm &> /dev/null; then
|
||||
echo "✅ npm is available"
|
||||
npm --version
|
||||
else
|
||||
echo "❌ npm not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test-architecture-detection:
|
||||
name: Test Architecture Detection
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
arch: x64
|
||||
expected: 'Linux x64'
|
||||
- os: depot-ubuntu-24.04-arm
|
||||
arch: arm64
|
||||
expected: 'Linux ARM64'
|
||||
- os: macos-15-intel
|
||||
arch: x64
|
||||
expected: 'macOS x64'
|
||||
- os: macos-15
|
||||
arch: arm64
|
||||
expected: 'macOS ARM64'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Test architecture detection
|
||||
run: |
|
||||
echo "Testing architecture detection for: ${{ matrix.expected }}"
|
||||
|
||||
platform=$(uname -ms)
|
||||
echo "Detected platform: $platform"
|
||||
|
||||
case $platform in
|
||||
'Darwin x86_64')
|
||||
detected="macOS x64"
|
||||
;;
|
||||
'Darwin arm64')
|
||||
detected="macOS ARM64"
|
||||
;;
|
||||
'Linux x86_64')
|
||||
detected="Linux x64"
|
||||
;;
|
||||
'Linux aarch64'|'Linux arm64')
|
||||
detected="Linux ARM64"
|
||||
;;
|
||||
*)
|
||||
echo "❌ Unknown platform: $platform"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$detected" == "${{ matrix.expected }}" ]]; then
|
||||
echo "✅ Correctly detected: $detected"
|
||||
else
|
||||
echo "❌ Detection mismatch: expected '${{ matrix.expected }}', got '$detected'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test-prerequisites:
|
||||
name: Test Prerequisites
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-15]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Test required tools
|
||||
run: |
|
||||
echo "Testing required tools..."
|
||||
|
||||
# Test curl
|
||||
if command -v curl &> /dev/null; then
|
||||
echo "✅ curl available"
|
||||
curl --version | head -1
|
||||
else
|
||||
echo "❌ curl not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test unzip
|
||||
if command -v unzip &> /dev/null; then
|
||||
echo "✅ unzip available"
|
||||
unzip -v | head -1
|
||||
else
|
||||
echo "❌ unzip not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test without prerequisites
|
||||
run: |
|
||||
echo "Testing error handling when prerequisites are missing..."
|
||||
|
||||
# Create a version of the script that will fail prerequisite check
|
||||
sed 's/command -v curl/command -v nonexistent-tool/' install.sh > test-install.sh
|
||||
|
||||
if bash test-install.sh 2>&1 | grep -q "required to install"; then
|
||||
echo "✅ Prerequisites check works"
|
||||
else
|
||||
echo "❌ Prerequisites check failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm test-install.sh
|
||||
|
||||
summary:
|
||||
name: Installation Test Summary
|
||||
needs:
|
||||
[
|
||||
install-script-unit-tests,
|
||||
test-install-script,
|
||||
test-npm-fallback,
|
||||
test-architecture-detection,
|
||||
test-prerequisites,
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Test Results Summary
|
||||
run: |
|
||||
echo "## Installation Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [[ "${{ needs.install-script-unit-tests.result }}" == "success" ]]; then
|
||||
echo "✅ **Install Script Unit Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Install Script Unit Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.test-install-script.result }}" == "success" ]]; then
|
||||
echo "✅ **Install Script Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Install Script Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.test-npm-fallback.result }}" == "success" ]]; then
|
||||
echo "✅ **npm Fallback Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **npm Fallback Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.test-architecture-detection.result }}" == "success" ]]; then
|
||||
echo "✅ **Architecture Detection**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Architecture Detection**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.test-prerequisites.result }}" == "success" ]]; then
|
||||
echo "✅ **Prerequisites Check**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Prerequisites Check**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Test Matrix Coverage:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Linux x64**: Ubuntu 22.04, Latest" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Linux ARM64**: Ubuntu 22.04, 24.04 (via Depot)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **macOS x64**: macOS 15 (Intel)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **macOS ARM64**: macOS 14, macOS 15 (Apple Silicon)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Shells**: Bash and Zsh" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Node.js**: 18, 20, 22 (npm fallback)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Total platforms**: 4 architectures across 15+ environments" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,107 @@
|
||||
name: Dead Code
|
||||
|
||||
# Surfaces likely-dead code on every PR so orphaned files/exports/deps get
|
||||
# noticed instead of rotting (see the root Dockerfile cleanup, #3783).
|
||||
#
|
||||
# Report-only by design: every job writes findings to the run's Step Summary
|
||||
# and never fails the build. These tools carry false positives (public API
|
||||
# surface, dynamic imports, import-map targets), so a red X here would train
|
||||
# people to ignore it. Tighten a job to blocking only once its config is
|
||||
# refined enough that a clean run is the steady state.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- 'python/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/**'
|
||||
- '.github/scripts/check-orphan-ci.sh'
|
||||
- 'knip.json'
|
||||
- 'package.json'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
pull_request:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- 'python/**'
|
||||
- '.github/actions/**'
|
||||
- '.github/workflows/**'
|
||||
- '.github/scripts/check-orphan-ci.sh'
|
||||
- 'knip.json'
|
||||
- 'package.json'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
knip:
|
||||
name: TypeScript (knip)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run knip (report-only)
|
||||
run: |
|
||||
{
|
||||
echo '## Knip — TypeScript dead code'
|
||||
echo ''
|
||||
echo 'Unused files, exports, types and dependencies. False positives'
|
||||
echo 'usually mean a missing `entry` in `knip.json`; vet before deleting.'
|
||||
echo ''
|
||||
echo '```'
|
||||
pnpm dlx knip@5 --no-exit-code --no-progress 2>&1 || true
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
vulture:
|
||||
name: Python (vulture)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Python with UV
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
|
||||
- name: Run vulture (report-only)
|
||||
run: |
|
||||
{
|
||||
echo '## Vulture — Python dead code'
|
||||
echo ''
|
||||
echo 'Likely-unused functions, classes and variables. Suppress'
|
||||
echo 'confirmed false positives in `python/config/vulture_allowlist.py`.'
|
||||
echo ''
|
||||
echo '```'
|
||||
(cd python && uv run nox -s dead_code) 2>&1 || true
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
orphaned-ci:
|
||||
name: GitHub Actions (orphan check)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Check for orphaned workflows and composite actions
|
||||
run: bash .github/scripts/check-orphan-ci.sh >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -0,0 +1,45 @@
|
||||
name: Docs - Check Links
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/docs-check-links.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-links:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./docs
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Cache bun dependencies
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.bun/install/cache
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Validate links
|
||||
run: bun run lint:links
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Docs - Sync Algolia Search
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/docs-search-sync.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync-search:
|
||||
runs-on: ubuntu-latest
|
||||
# The Algolia admin key is stored as an environment secret, not a repo
|
||||
# secret. Declaring the environment is what exposes `secrets.*` (and any
|
||||
# env-scoped `vars.*`) to this job — without it the key resolves empty and
|
||||
# the sync silently skips.
|
||||
environment: Production
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./docs
|
||||
env:
|
||||
ALGOLIA_APP_ID: ${{ vars.ALGOLIA_APP_ID || '62HI9PQZ1L' }}
|
||||
ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }}
|
||||
ALGOLIA_INDEX_NAME: ${{ vars.ALGOLIA_INDEX_NAME || 'docs_composio' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Cache bun dependencies
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.bun/install/cache
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Validate search index generation
|
||||
run: bun run sync:search --dry-run
|
||||
|
||||
- name: Sync Algolia search index
|
||||
if: env.ALGOLIA_APP_ID != '' && env.ALGOLIA_ADMIN_API_KEY != ''
|
||||
run: bun run sync:search
|
||||
|
||||
- name: Skip sync without Algolia secrets
|
||||
if: env.ALGOLIA_APP_ID == '' || env.ALGOLIA_ADMIN_API_KEY == ''
|
||||
run: echo "Skipping Algolia sync because ALGOLIA_APP_ID or ALGOLIA_ADMIN_API_KEY is not configured."
|
||||
@@ -0,0 +1,75 @@
|
||||
name: Docs - Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/docs-tests.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./docs
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Cache bun dependencies
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.bun/install/cache
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Run static tests
|
||||
run: bun run test
|
||||
|
||||
- name: Build
|
||||
run: bun run build
|
||||
|
||||
- name: Start server and run integration tests
|
||||
run: |
|
||||
bun run start &
|
||||
SERVER_PID=$!
|
||||
|
||||
echo "Waiting for server to start..."
|
||||
for i in $(seq 1 30); do
|
||||
if curl -s http://localhost:3000 > /dev/null 2>&1; then
|
||||
echo "Server is ready!"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 30 ]; then
|
||||
echo "Server failed to start within 30 seconds"
|
||||
kill $SERVER_PID 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
set +e
|
||||
bun run test:integration
|
||||
TEST_EXIT=$?
|
||||
set -e
|
||||
|
||||
kill $SERVER_PID 2>/dev/null || true
|
||||
exit $TEST_EXIT
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Docs - TypeScript Code Validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/docs-typescript-check.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
typescript-check:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./docs
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Cache bun dependencies
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.bun/install/cache
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: TypeScript type check
|
||||
run: bun run types:check
|
||||
|
||||
- name: Build (validates Twoslash TypeScript code blocks)
|
||||
run: bun run build
|
||||
@@ -0,0 +1,156 @@
|
||||
name: Docs - Update Data
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */5 * * *'
|
||||
repository_dispatch:
|
||||
types: [apollo-production-deploy]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update-data:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./docs
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
env:
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
|
||||
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Log trigger source
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
HERMES_COMMIT: ${{ github.event.client_payload.hermes_commit }}
|
||||
DEPLOY_TIMESTAMP: ${{ github.event.client_payload.timestamp }}
|
||||
run: |
|
||||
echo "Workflow triggered by: $EVENT_NAME"
|
||||
if [ "$EVENT_NAME" = "repository_dispatch" ]; then
|
||||
echo "Triggered by Apollo production deployment"
|
||||
echo "Hermes commit: $HERMES_COMMIT"
|
||||
echo "Timestamp: $DEPLOY_TIMESTAMP"
|
||||
fi
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Configure staging API endpoints
|
||||
env:
|
||||
COMPOSIO_BASE_URL_STAGING: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
|
||||
run: |
|
||||
staging_base="${COMPOSIO_BASE_URL_STAGING%/}"
|
||||
|
||||
if [ -z "$staging_base" ]; then
|
||||
echo "::error::COMPOSIO_BASE_URL_STAGING is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$staging_base" == "https://backend.composio.dev" || "$staging_base" == "https://backend.composio.dev/"* ]]; then
|
||||
echo "::error::COMPOSIO_BASE_URL_STAGING points at production"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
staging_origin="${staging_base%/api/v3.1}"
|
||||
staging_origin="${staging_origin%/api/v3}"
|
||||
staging_v3_base="$staging_origin/api/v3"
|
||||
staging_v31_base="$staging_origin/api/v3.1"
|
||||
|
||||
{
|
||||
echo "COMPOSIO_API_BASE=$staging_v3_base"
|
||||
echo "OPENAPI_SPEC_URL=$staging_v3_base/openapi.json"
|
||||
echo "OPENAPI_V31_SPEC_URL=$staging_v31_base/openapi.json"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
echo "Configured staging API endpoints"
|
||||
|
||||
- name: Cache bun dependencies
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.bun/install/cache
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate toolkits data
|
||||
run: bun run generate:toolkits
|
||||
|
||||
- name: Fetch OpenAPI spec
|
||||
run: bun run scripts/fetch-openapi.mjs
|
||||
|
||||
- name: Generate API index pages
|
||||
run: bun run generate:api-index
|
||||
|
||||
- name: Generate meta tools reference
|
||||
run: bun run generate:meta-tools
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
cd ..
|
||||
git add -N docs/public/data/ docs/public/openapi.json docs/public/openapi-v3.json docs/content/reference/api-reference/ docs/content/reference/v3/api-reference/ docs/content/toolkits/meta-tools/ 2>/dev/null || true
|
||||
if git diff --quiet docs/public/data/ docs/public/openapi.json docs/public/openapi-v3.json docs/content/reference/api-reference/ docs/content/reference/v3/api-reference/ docs/content/toolkits/meta-tools/ 2>/dev/null; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No changes detected"
|
||||
else
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Changes detected:"
|
||||
git diff --stat docs/public/data/ docs/public/openapi.json docs/public/openapi-v3.json docs/content/reference/api-reference/ docs/content/reference/v3/api-reference/ docs/content/toolkits/meta-tools/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pr
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
commit-message: 'docs: update toolkits and API data'
|
||||
title: 'docs: update toolkits, API spec, and meta tools data'
|
||||
body: |
|
||||
## Summary
|
||||
Automated sync of backend data into the docs site. Triggered by: `${{ github.event_name }}`${{ github.event_name == 'repository_dispatch' && format(' (Hermes commit: {0})', github.event.client_payload.hermes_commit) || '' }}.
|
||||
|
||||
## What changed
|
||||
- **Toolkit catalog** (`docs/public/data/toolkits.json`, `toolkits-list.json`) — refreshed list of available toolkits, auth schemes, and tools from the backend API
|
||||
- **OpenAPI specs** (`docs/public/openapi.json`, `docs/public/openapi-v3.json`) — latest v3.1 and v3.0 API specifications fetched from staging
|
||||
- **API reference pages** (`docs/content/reference/api-reference/`, `docs/content/reference/v3/api-reference/`) — regenerated index pages for both API versions
|
||||
- **Meta tools reference** (`docs/public/data/meta-tools.json`, `docs/content/toolkits/meta-tools/*.mdx`) — updated meta tool schemas and reference docs
|
||||
branch: docs/auto-update-data
|
||||
base: next
|
||||
add-paths: |
|
||||
docs/public/data/
|
||||
docs/public/openapi.json
|
||||
docs/public/openapi-v3.json
|
||||
docs/content/reference/api-reference/
|
||||
docs/content/reference/v3/api-reference/
|
||||
docs/content/toolkits/meta-tools/
|
||||
|
||||
- name: Request review from trigger actor
|
||||
if: steps.create-pr.outputs.pull-request-number
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
|
||||
AUTHOR: ${{ github.actor }}
|
||||
run: |
|
||||
gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR" || \
|
||||
gh pr edit "$PR_NUMBER" --add-reviewer "Sushmithamallesh"
|
||||
@@ -0,0 +1,110 @@
|
||||
name: Changelog Notification
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [next]
|
||||
paths:
|
||||
- 'docs/content/changelog/**.mdx'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
notify-changelog:
|
||||
name: Send Changelog Notification
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0 # Fetch full history to detect changes across all commits in push
|
||||
|
||||
- name: Get Changed Changelog Files
|
||||
id: changed-files
|
||||
run: |
|
||||
# Get the commit range for this push
|
||||
BEFORE="${{ github.event.before }}"
|
||||
AFTER="${{ github.event.after }}"
|
||||
|
||||
echo "Push event details:"
|
||||
echo " Before: $BEFORE"
|
||||
echo " After: $AFTER"
|
||||
echo " Commits in push: $(git rev-list --count "$BEFORE".."$AFTER" 2>/dev/null || echo "N/A")"
|
||||
|
||||
# Handle first push to branch (before SHA is all zeros)
|
||||
if [[ "$BEFORE" =~ ^0+$ ]]; then
|
||||
echo "First push to branch detected, checking only the latest commit"
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=AM HEAD^..HEAD | grep '^docs/content/changelog/.*\.mdx$' || echo "")
|
||||
else
|
||||
echo "Checking all commits in push range: $BEFORE..$AFTER"
|
||||
# Get the list of added/modified changelog files across all commits in this push
|
||||
CHANGED_FILES=$(git diff --name-only --diff-filter=AM "$BEFORE".."$AFTER" | grep '^docs/content/changelog/.*\.mdx$' || echo "")
|
||||
fi
|
||||
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
echo "No changelog files changed in this push"
|
||||
echo "has_changelog=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Changelog files found:"
|
||||
echo "$CHANGED_FILES"
|
||||
# Get the first changelog file (in case multiple were added)
|
||||
CHANGELOG_FILE=$(echo "$CHANGED_FILES" | head -n 1)
|
||||
echo ""
|
||||
echo "Processing changelog: $CHANGELOG_FILE"
|
||||
echo "changelog_file=$CHANGELOG_FILE" >> $GITHUB_OUTPUT
|
||||
echo "has_changelog=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Extract Changelog Metadata
|
||||
if: steps.changed-files.outputs.has_changelog == 'true'
|
||||
id: extract-metadata
|
||||
run: |
|
||||
CHANGELOG_FILE="${{ steps.changed-files.outputs.changelog_file }}"
|
||||
|
||||
# Extract title from frontmatter
|
||||
TITLE=$(grep "^title:" "$CHANGELOG_FILE" | sed "s/^title: *['\"]*//" | sed "s/['\"]* *$//")
|
||||
|
||||
# Extract date from frontmatter
|
||||
DATE=$(grep "^date:" "$CHANGELOG_FILE" | sed "s/^date: *['\"]*//" | sed "s/['\"]* *$//")
|
||||
|
||||
# Format date for URL (YYYY-MM-DD to YYYY/MM/DD)
|
||||
URL_DATE=$(echo "$DATE" | sed 's/-/\//g')
|
||||
|
||||
# Build the changelog URL
|
||||
CHANGELOG_URL="https://docs.composio.dev/docs/changelog/${URL_DATE}"
|
||||
|
||||
# Get the commit author who added/modified this changelog
|
||||
AUTHOR=$(git log -1 --format='%an' -- "$CHANGELOG_FILE")
|
||||
AUTHOR_EMAIL=$(git log -1 --format='%ae' -- "$CHANGELOG_FILE")
|
||||
|
||||
# Build properly escaped JSON payload using jq
|
||||
SLACK_PAYLOAD=$(jq -n \
|
||||
--arg title "$TITLE" \
|
||||
--arg date "$DATE" \
|
||||
--arg url "$CHANGELOG_URL" \
|
||||
--arg author "$AUTHOR" \
|
||||
'{text: "*New Changelog Published*\n\n*\($title)*\n\nDate: \($date)\n<\($url)|View Changelog>\nAuthor: \($author)"}')
|
||||
|
||||
echo "title=$TITLE" >> $GITHUB_OUTPUT
|
||||
echo "date=$DATE" >> $GITHUB_OUTPUT
|
||||
echo "url=$CHANGELOG_URL" >> $GITHUB_OUTPUT
|
||||
echo "author=$AUTHOR" >> $GITHUB_OUTPUT
|
||||
echo "author_email=$AUTHOR_EMAIL" >> $GITHUB_OUTPUT
|
||||
|
||||
# Save the JSON payload for the next step
|
||||
echo "slack_payload<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$SLACK_PAYLOAD" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "📝 Changelog Title: $TITLE"
|
||||
echo "📅 Date: $DATE"
|
||||
echo "🔗 URL: $CHANGELOG_URL"
|
||||
echo "👤 Author: $AUTHOR ($AUTHOR_EMAIL)"
|
||||
|
||||
- name: Send Slack Notification
|
||||
if: steps.changed-files.outputs.has_changelog == 'true'
|
||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_CUSTOMER_COMMS_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: ${{ steps.extract-metadata.outputs.slack_payload }}
|
||||
@@ -0,0 +1,93 @@
|
||||
name: Changelog → Docs PR
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [next]
|
||||
paths:
|
||||
- 'docs/content/changelog/**.mdx'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
suggest-docs-updates:
|
||||
name: Suggest Documentation Updates
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect new changelog files
|
||||
id: detect
|
||||
run: |
|
||||
BEFORE="${{ github.event.before }}"
|
||||
AFTER="${{ github.event.after }}"
|
||||
|
||||
if [[ "$BEFORE" =~ ^0+$ ]]; then
|
||||
FILES=$(git diff --name-only --diff-filter=AM HEAD^..HEAD | grep '^docs/content/changelog/.*\.mdx$' || echo "")
|
||||
else
|
||||
FILES=$(git diff --name-only --diff-filter=AM "$BEFORE".."$AFTER" | grep '^docs/content/changelog/.*\.mdx$' || echo "")
|
||||
fi
|
||||
|
||||
if [ -z "$FILES" ]; then
|
||||
echo "found=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "found=true" >> $GITHUB_OUTPUT
|
||||
echo "files<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$FILES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Codex updates docs
|
||||
if: steps.detect.outputs.found == 'true'
|
||||
uses: openai/codex-action@52fe01ec70a42f454c9d2ebd47598f9fd6893d56 # v1.11
|
||||
with:
|
||||
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
|
||||
sandbox: workspace-write
|
||||
prompt: |
|
||||
New changelog entries were merged to next. Read the agent instructions and update documentation accordingly.
|
||||
|
||||
1. Read docs/agent-guidance/agents/changelog-docs-updater.md for full instructions
|
||||
2. Read each of these changelog files:
|
||||
${{ steps.detect.outputs.files }}
|
||||
3. For each change, search docs/content/docs/ for pages that need updating
|
||||
4. Make the documentation updates following the agent instructions
|
||||
5. If no docs changes are needed, make no file changes and explain why
|
||||
|
||||
- name: Create PR if docs changed
|
||||
if: steps.detect.outputs.found == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
echo "No documentation changes needed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BRANCH="docs/changelog-update-${GITHUB_SHA::7}"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git checkout -b "$BRANCH"
|
||||
git add -A
|
||||
git commit -m "docs: update documentation for new changelog entries"
|
||||
git push -u origin "$BRANCH"
|
||||
|
||||
gh pr create \
|
||||
--base next \
|
||||
--head "$BRANCH" \
|
||||
--title "docs: update documentation for new changelog entries" \
|
||||
--reviewer "$GITHUB_ACTOR" \
|
||||
--body "$(cat <<'EOF'
|
||||
## Summary
|
||||
Automated documentation updates triggered by new changelog entries merged to next.
|
||||
|
||||
Generated by Codex via GitHub Actions.
|
||||
EOF
|
||||
)"
|
||||
@@ -0,0 +1,122 @@
|
||||
name: Docs Health Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *' # Every hour
|
||||
workflow_dispatch: # Manual trigger
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
health-check:
|
||||
name: Check docs accessibility
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check endpoints
|
||||
id: check
|
||||
run: |
|
||||
BASE_URL="https://docs.composio.dev"
|
||||
FAIL_FILE=$(mktemp)
|
||||
CHECKED=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
check_url() {
|
||||
local label="$1"
|
||||
local url="$2"
|
||||
shift 2
|
||||
CHECKED=$((CHECKED + 1))
|
||||
local attempt=0
|
||||
local STATUS="000"
|
||||
while [ "$attempt" -lt 2 ]; do
|
||||
if STATUS=$(curl -sL -o /dev/null -w "%{http_code}" --max-time 15 "$@" "$url" 2>/dev/null); then
|
||||
:
|
||||
else
|
||||
STATUS="000"
|
||||
fi
|
||||
if [ "$STATUS" -ge 400 2>/dev/null ] || [ "$STATUS" = "000" ]; then
|
||||
attempt=$((attempt + 1))
|
||||
[ "$attempt" -lt 2 ] && sleep 3
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$STATUS" -ge 400 2>/dev/null ] || [ "$STATUS" = "000" ]; then
|
||||
echo "FAIL: ${label} → ${STATUS}"
|
||||
echo "${label} → ${STATUS}" >> "$FAIL_FILE"
|
||||
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||
else
|
||||
echo " OK: ${label} → ${STATUS}"
|
||||
fi
|
||||
}
|
||||
|
||||
# LLM/AI agent entry points
|
||||
check_url "/llms.txt" "${BASE_URL}/llms.txt"
|
||||
check_url "/llms-full.txt" "${BASE_URL}/llms-full.txt"
|
||||
check_url "/docs/quickstart.md" "${BASE_URL}/docs/quickstart.md"
|
||||
check_url "/docs/tools-and-toolkits.md" "${BASE_URL}/docs/tools-and-toolkits.md"
|
||||
|
||||
# Main pages
|
||||
check_url "/docs" "${BASE_URL}/docs"
|
||||
check_url "/docs/quickstart" "${BASE_URL}/docs/quickstart"
|
||||
check_url "/docs/authentication" "${BASE_URL}/docs/authentication"
|
||||
check_url "/docs/tools-and-toolkits" "${BASE_URL}/docs/tools-and-toolkits"
|
||||
check_url "/docs/users-and-sessions" "${BASE_URL}/docs/users-and-sessions"
|
||||
check_url "/docs/configuring-sessions" "${BASE_URL}/docs/configuring-sessions"
|
||||
check_url "/cookbooks" "${BASE_URL}/cookbooks"
|
||||
check_url "/toolkits" "${BASE_URL}/toolkits"
|
||||
check_url "/reference" "${BASE_URL}/reference"
|
||||
|
||||
# Provider pages
|
||||
check_url "/docs/providers/openai" "${BASE_URL}/docs/providers/openai"
|
||||
check_url "/docs/providers/vercel" "${BASE_URL}/docs/providers/vercel"
|
||||
check_url "/docs/providers/anthropic" "${BASE_URL}/docs/providers/anthropic"
|
||||
|
||||
# Key sub-pages
|
||||
check_url "/docs/tools-direct/fetching-tools" "${BASE_URL}/docs/tools-direct/fetching-tools"
|
||||
check_url "/docs/tools-direct/executing-tools" "${BASE_URL}/docs/tools-direct/executing-tools"
|
||||
check_url "/docs/authenticating-users/in-chat-authentication" "${BASE_URL}/docs/authenticating-users/in-chat-authentication"
|
||||
check_url "/docs/common-faq" "${BASE_URL}/docs/common-faq"
|
||||
|
||||
# Markdown content negotiation
|
||||
check_url "/docs/quickstart (markdown)" "${BASE_URL}/docs/quickstart" -H "Accept: text/markdown"
|
||||
check_url "/docs/tools-and-toolkits (markdown)" "${BASE_URL}/docs/tools-and-toolkits" -H "Accept: text/markdown"
|
||||
check_url "/docs/authentication (markdown)" "${BASE_URL}/docs/authentication" -H "Accept: text/markdown"
|
||||
check_url "/docs/providers/openai (markdown)" "${BASE_URL}/docs/providers/openai" -H "Accept: text/markdown"
|
||||
check_url "/cookbooks (markdown)" "${BASE_URL}/cookbooks" -H "Accept: text/markdown"
|
||||
|
||||
if [ "$FAIL_COUNT" -gt 0 ]; then
|
||||
echo "has_failures=true" >> $GITHUB_OUTPUT
|
||||
|
||||
# Build the Slack message with real newlines
|
||||
FAIL_LIST=$(sed 's/^/• /' "$FAIL_FILE")
|
||||
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg fails "$FAIL_LIST" \
|
||||
--arg count "$FAIL_COUNT" \
|
||||
--arg total "$CHECKED" \
|
||||
--arg url "$RUN_URL" \
|
||||
'{text: ("🚨 *Docs Health Check*\n\n" + $count + "/" + $total + " endpoints failing:\n" + $fails + "\n\n<" + $url + "|View logs>")}')
|
||||
|
||||
echo "slack_payload<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$PAYLOAD" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_failures=false" >> $GITHUB_OUTPUT
|
||||
echo "All $CHECKED endpoints healthy"
|
||||
fi
|
||||
|
||||
rm -f "$FAIL_FILE"
|
||||
|
||||
- name: Notify Slack
|
||||
if: steps.check.outputs.has_failures == 'true'
|
||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_POD_DX_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: ${{ steps.check.outputs.slack_payload }}
|
||||
|
||||
- name: Fail on unhealthy endpoints
|
||||
if: steps.check.outputs.has_failures == 'true'
|
||||
run: exit 1
|
||||
@@ -0,0 +1,175 @@
|
||||
name: Docs - Sync guides on SDK changes
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [next]
|
||||
paths:
|
||||
- 'ts/packages/core/src/**'
|
||||
- 'ts/packages/cli/src/**'
|
||||
- 'ts/packages/providers/*/src/**'
|
||||
- 'python/composio/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync-docs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
|
||||
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get SDK diff
|
||||
id: diff
|
||||
env:
|
||||
BEFORE: ${{ github.event.before }}
|
||||
AFTER: ${{ github.event.after }}
|
||||
run: |
|
||||
if [ -z "$AFTER" ] || ! git cat-file -e "$AFTER" 2>/dev/null; then
|
||||
AFTER=$(git rev-parse HEAD)
|
||||
fi
|
||||
|
||||
if [ -z "$BEFORE" ] || [[ "$BEFORE" =~ ^0+$ ]] || ! git cat-file -e "$BEFORE" 2>/dev/null; then
|
||||
echo "BEFORE ref unavailable — falling back to HEAD~1"
|
||||
BEFORE=$(git rev-parse HEAD~1)
|
||||
fi
|
||||
|
||||
git diff "$BEFORE".."$AFTER" -- \
|
||||
ts/packages/core/src/ \
|
||||
ts/packages/cli/src/ \
|
||||
ts/packages/providers/*/src/ \
|
||||
python/composio/ > /tmp/sdk-diff.patch
|
||||
|
||||
if [ ! -s /tmp/sdk-diff.patch ]; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
echo "$(wc -l < /tmp/sdk-diff.patch) lines of SDK changes"
|
||||
fi
|
||||
|
||||
- name: Check docs for staleness
|
||||
if: steps.diff.outputs.has_changes == 'true'
|
||||
uses: anthropics/claude-code-action/base-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # v1.0.170
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
claude_args: |
|
||||
--allowedTools "Read,Write,Edit,Glob,Grep"
|
||||
prompt: |
|
||||
SDK source code was just updated. Check if any documentation needs to change.
|
||||
|
||||
1. Read the SDK diff at /tmp/sdk-diff.patch to understand what changed
|
||||
2. Focus on user-facing changes: new/renamed/removed methods, changed parameters, new features, changed behavior. Skip internal refactors, test changes, and type-only changes.
|
||||
3. If there are user-facing changes, search docs/content/docs/ for pages that reference the affected APIs, patterns, or features
|
||||
4. Update any docs that would be stale or incorrect after this SDK change — guides, FAQs, examples, quickstart, etc.
|
||||
5. Do NOT touch docs/content/reference/ (those are auto-generated separately)
|
||||
6. Do NOT touch docs/content/changelog/ (changelogs are written separately)
|
||||
7. Do NOT edit docs/package.json or docs/bun.lock — the `@composio/*` version pins are managed automatically by the dependency-realignment step below, which runs after you and overwrites any changes you make to them
|
||||
8. If no docs changes are needed, make no file changes
|
||||
9. Do NOT add type assertions or casts to force a snippet to compile against an API that is missing from the published `@composio/*` package. If a feature isn't released yet, document the released behavior and let the Twoslash build flag the gap.
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
if: steps.diff.outputs.has_changes == 'true'
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- name: Align docs SDK deps with latest published releases
|
||||
if: steps.diff.outputs.has_changes == 'true'
|
||||
working-directory: ./docs
|
||||
run: |
|
||||
# Docs is a standalone Bun app outside the pnpm workspace: it pins
|
||||
# published @composio/* versions instead of linking workspace source,
|
||||
# so its Twoslash snippets and example apps only ever demonstrate APIs
|
||||
# that users can actually install. Realign those pins to the latest
|
||||
# released versions on every sync so they never drift behind the SDK
|
||||
# (the drift that previously forced workaround casts into snippets).
|
||||
# This runs AFTER the content pass so the committed pins are always
|
||||
# authoritative — even if that pass edited package.json/bun.lock, this
|
||||
# step resets the @composio/* pins and regenerates the lockfile.
|
||||
# The PR's Twoslash build (docs-typescript-check.yml) gates the result:
|
||||
# if a documented feature isn't published yet, the build fails loudly
|
||||
# instead of the gap being papered over.
|
||||
set -euo pipefail
|
||||
# Discard any edits the content pass made to these managed files so the
|
||||
# realignment is the sole source of truth for the dependency pins.
|
||||
git checkout -- package.json bun.lock
|
||||
changed=0
|
||||
for section in dependencies devDependencies; do
|
||||
pkgs=$(jq -r --arg s "$section" '.[$s] // {} | keys[] | select(startswith("@composio/"))' package.json)
|
||||
for pkg in $pkgs; do
|
||||
latest=$(npm view "$pkg" version 2>/dev/null || true)
|
||||
if [ -z "$latest" ]; then
|
||||
echo "::warning::could not resolve a published version for $pkg; leaving its pin unchanged"
|
||||
continue
|
||||
fi
|
||||
current=$(jq -r --arg s "$section" --arg p "$pkg" '.[$s][$p]' package.json)
|
||||
want="^$latest"
|
||||
if [ "$current" != "$want" ]; then
|
||||
echo "$section.$pkg: $current -> $want"
|
||||
npm pkg set "$section.$pkg=$want"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
done
|
||||
if [ "$changed" -eq 1 ]; then
|
||||
bun install
|
||||
else
|
||||
echo "All @composio/* pins already match the latest published releases."
|
||||
fi
|
||||
|
||||
- name: Create PR if changed
|
||||
id: create-pr
|
||||
if: steps.diff.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
commit-message: 'docs: update guides for SDK changes'
|
||||
title: 'docs: update guides for SDK changes'
|
||||
body: |
|
||||
## Summary
|
||||
Automated docs update triggered by SDK source changes on `next`.
|
||||
|
||||
- Claude reviewed the SDK diff and updated guides, FAQs, or examples
|
||||
that reference changed APIs or features.
|
||||
- The docs `@composio/*` dependencies were realigned to their latest
|
||||
published releases so Twoslash snippets and example apps validate
|
||||
against versions users can actually install.
|
||||
|
||||
## Review checklist
|
||||
- [ ] Changes accurately reflect the new SDK behavior
|
||||
- [ ] No unrelated docs were modified
|
||||
- [ ] Code examples are correct and complete
|
||||
- [ ] If a documented feature is not published yet, the Twoslash build
|
||||
will fail — wait for the release instead of working around it
|
||||
|
||||
Generated by Claude Code via GitHub Actions.
|
||||
branch: docs/auto-sdk-sync
|
||||
base: next
|
||||
add-paths: |
|
||||
docs/content/docs/
|
||||
docs/package.json
|
||||
docs/bun.lock
|
||||
|
||||
- name: Request review from pusher
|
||||
if: steps.create-pr.outputs.pull-request-number
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
|
||||
AUTHOR: ${{ github.actor }}
|
||||
run: gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR"
|
||||
@@ -0,0 +1,101 @@
|
||||
name: Docs - Sync Connect Clients
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [dashboard-production-deploy]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync-clients:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: next
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Get client definitions
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
CLIENT_DEFINITIONS: ${{ github.event.client_payload.client_definitions }}
|
||||
DASHBOARD_TAG: ${{ github.event.client_payload.dashboard_tag }}
|
||||
GH_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
if [ "$EVENT_NAME" = "repository_dispatch" ]; then
|
||||
if [ -z "$CLIENT_DEFINITIONS" ]; then
|
||||
echo "::error::repository_dispatch payload missing client_definitions"
|
||||
exit 1
|
||||
fi
|
||||
echo "$CLIENT_DEFINITIONS" | base64 -d > /tmp/client-definitions.ts
|
||||
echo "Received client-definitions.ts from dashboard deploy (tag: $DASHBOARD_TAG)"
|
||||
else
|
||||
echo "Manual trigger — fetching from dashboard repo"
|
||||
gh api \
|
||||
repos/ComposioHQ/composio_dashboard/contents/src/app/\(connect\)/\[org\]/~/connect/clients/_components/client-definitions.ts?ref=main \
|
||||
--jq '.content' | base64 -d > /tmp/client-definitions.ts
|
||||
fi
|
||||
|
||||
if [ ! -s /tmp/client-definitions.ts ]; then
|
||||
echo "::error::client-definitions.ts is empty"
|
||||
exit 1
|
||||
fi
|
||||
echo "$(wc -l < /tmp/client-definitions.ts) lines"
|
||||
|
||||
- name: Sync Connect clients
|
||||
uses: anthropics/claude-code-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # v1.0.170
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
allowed_tools: 'Read,Write,Edit,Glob,Grep,Bash(curl *)'
|
||||
prompt: |
|
||||
Sync the Composio Connect clients page from the dashboard repo.
|
||||
|
||||
1. Read docs/agent-guidance/agents/connect-clients-sync.md for full instructions
|
||||
2. The client definitions have already been fetched to /tmp/client-definitions.ts — read that file
|
||||
3. Read the current docs/content/docs/composio-connect.mdx
|
||||
4. Compare and update if there are any differences (new clients, changed steps, updated descriptions)
|
||||
5. Download any new client logos to docs/public/images/clients/
|
||||
6. If no changes are needed, make no file changes
|
||||
|
||||
- name: Create PR if changed
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
commit-message: 'docs: sync Connect clients from dashboard'
|
||||
title: 'docs: sync Connect clients from dashboard'
|
||||
body: |
|
||||
## Summary
|
||||
Automated sync of AI client definitions from `ComposioHQ/composio_dashboard`.
|
||||
|
||||
Updates client setup steps, descriptions, and logos on the Composio Connect page.
|
||||
|
||||
## What may have changed
|
||||
- New or removed AI clients
|
||||
- Updated setup steps (deeplinks, config changes)
|
||||
- Category changes (popular/ide/other)
|
||||
- Client logos
|
||||
|
||||
Generated by Claude Code via GitHub Actions.
|
||||
branch: docs/auto-sync-connect-clients
|
||||
base: next
|
||||
add-paths: |
|
||||
docs/content/docs/composio-connect.mdx
|
||||
docs/public/images/clients/
|
||||
|
||||
- name: Request review from trigger actor
|
||||
if: steps.create-pr.outputs.pull-request-number
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
|
||||
AUTHOR: ${{ github.actor }}
|
||||
run: gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR"
|
||||
@@ -0,0 +1,118 @@
|
||||
name: Generate SDK Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [next]
|
||||
paths:
|
||||
- 'ts/packages/core/src/**'
|
||||
- 'ts/packages/core/scripts/generate-docs.ts'
|
||||
- 'python/composio/**'
|
||||
- 'python/scripts/generate-docs.py'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/actions/setup-python-uv/action.yml'
|
||||
- '.github/workflows/generate-sdk-docs.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
generate-ts-docs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
|
||||
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- run: pnpm --filter @composio/core generate:docs
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
commit-message: 'docs: auto-generate TypeScript SDK reference'
|
||||
title: 'docs: update TypeScript SDK reference from source'
|
||||
body: |
|
||||
## Summary
|
||||
Auto-generated TypeScript SDK reference docs from `ts/packages/core/src/`.
|
||||
|
||||
Regenerates pages at `docs/content/reference/sdk-reference/typescript/` to reflect changes in the core package's public API (new methods, updated signatures, changed types).
|
||||
branch: docs/auto-update-ts-sdk-reference
|
||||
base: next
|
||||
add-paths: docs/content/reference/sdk-reference/typescript/
|
||||
|
||||
- name: Request review from pusher
|
||||
if: steps.create-pr.outputs.pull-request-number
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
|
||||
AUTHOR: ${{ github.actor }}
|
||||
run: gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR"
|
||||
|
||||
generate-python-docs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
|
||||
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Setup Python with UV
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
|
||||
- run: cd python && uv run --with griffe python scripts/generate-docs.py
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
commit-message: 'docs: auto-generate Python SDK reference'
|
||||
title: 'docs: update Python SDK reference from source'
|
||||
body: |
|
||||
## Summary
|
||||
Auto-generated Python SDK reference docs from `python/composio/`.
|
||||
|
||||
Regenerates pages at `docs/content/reference/sdk-reference/python/` to reflect changes in the Python package's public API (new methods, updated signatures, changed types).
|
||||
branch: docs/auto-update-python-sdk-reference
|
||||
base: next
|
||||
add-paths: docs/content/reference/sdk-reference/python/
|
||||
|
||||
- name: Request review from pusher
|
||||
if: steps.create-pr.outputs.pull-request-number
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
|
||||
AUTHOR: ${{ github.actor }}
|
||||
run: gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR"
|
||||
@@ -0,0 +1,470 @@
|
||||
name: Issue Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened, edited]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: "Issue number to triage retrospectively"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
classify:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Load issue context for dispatch runs
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
id: issue-context
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
ISSUE_NUMBER: ${{ inputs.issue_number }}
|
||||
with:
|
||||
script: |
|
||||
const issue_number = Number(process.env.ISSUE_NUMBER);
|
||||
if (!Number.isInteger(issue_number) || issue_number <= 0) {
|
||||
core.setFailed(`Invalid issue_number: ${process.env.ISSUE_NUMBER}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number,
|
||||
});
|
||||
|
||||
if (issue.pull_request) {
|
||||
core.setFailed(`Issue #${issue_number} is a pull request, not an issue.`);
|
||||
return;
|
||||
}
|
||||
|
||||
core.setOutput("issue_number", String(issue.number));
|
||||
core.setOutput("issue_title", issue.title || "");
|
||||
core.setOutput("issue_body", issue.body || "");
|
||||
core.setOutput("issue_state", issue.state || "open");
|
||||
|
||||
- name: Write triage schema
|
||||
run: |
|
||||
cat > /tmp/issue-triage-schema.json <<'EOF'
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["docs", "py", "ts", "cli", "tool-request", "feature-request", "support", "other"]
|
||||
},
|
||||
"is_bug": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string"
|
||||
},
|
||||
"should_comment": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"comment_body": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["category", "is_bug", "reason", "should_comment", "comment_body"]
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Install Claude Code CLI
|
||||
run: npm install -g @anthropic-ai/claude-code
|
||||
|
||||
- name: Classify issue with Claude Sonnet
|
||||
id: classify
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ISSUE_TITLE: ${{ github.event_name == 'workflow_dispatch' && steps.issue-context.outputs.issue_title || github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event_name == 'workflow_dispatch' && steps.issue-context.outputs.issue_body || github.event.issue.body }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
|
||||
which claude
|
||||
claude --version || true
|
||||
|
||||
PROMPT=$(cat <<'EOF'
|
||||
Read the issue title and body from the ISSUE_TITLE and ISSUE_BODY environment variables.
|
||||
|
||||
Classify the issue for routing and determine whether it is a bug report.
|
||||
|
||||
Allowed categories:
|
||||
- docs
|
||||
- py
|
||||
- ts
|
||||
- cli
|
||||
- tool-request
|
||||
- feature-request
|
||||
- support
|
||||
- other
|
||||
|
||||
Use semantic meaning, not only keywords.
|
||||
Choose tool-request when the issue is primarily asking for a new integration, toolkit, app, or tool.
|
||||
Choose feature-request when the issue is asking for a product, SDK, docs, or CLI improvement that is not a tool request.
|
||||
Choose support when the issue is primarily a product/support question, account/integration configuration problem, or a hosted Composio platform/tooling problem rather than a code defect in this repository. This includes managed OAuth app configuration, provider app verification or consent-screen blocks, requested scopes for hosted/managed integrations, dashboard/back-office setup, billing/account questions, usage questions where the SDK appears to be behaving as designed, and bugs in existing hosted integrations/tools/toolkits that require backend/tool-catalog/support follow-up rather than SDK code changes.
|
||||
For example, an issue about Google Drive managed OAuth scopes or Google blocking the managed app should be support, not ts and not a bug, unless the issue clearly identifies a defect in SDK code.
|
||||
For example, an issue like GitHub #3291 where an existing hosted tool such as SPOTIFY_GET_PLAYLIST_ITEMS calls a deprecated third-party API endpoint, fails with 403, or needs a backend/tool definition update should be support and a bug, not ts, not py, not cli, and not tool-request.
|
||||
Choose cli only when the issue is specifically about the CLI experience or ts/packages/cli.
|
||||
Choose py only when the issue is primarily about Python SDK code in this repository.
|
||||
Choose ts only when the issue is primarily about TypeScript SDK code or TS packages other than the CLI in this repository.
|
||||
Choose docs only when the issue is primarily about documentation.
|
||||
Never choose tool-request for a broken existing integration or tool. Those are bugs, not tool requests, even if the title/body mentions a tool name or toolkit slug. If the broken existing integration/tool does not identify an SDK, CLI, docs, or Python code defect in this repository, choose support so it is forwarded to Plain.
|
||||
Set is_bug to true when the issue is primarily reporting broken behavior, a defect, an error, a regression, or unexpected behavior, including support-routed hosted integration/tool bugs.
|
||||
Set is_bug to false for feature requests, questions, maintenance, cleanup, and true new-tool requests.
|
||||
Set should_comment to true when the issue appears incorrect, non-actionable, missing the information needed to reproduce or investigate, or when category is support.
|
||||
Set should_comment to false otherwise.
|
||||
|
||||
If should_comment is true, provide comment_body as a short issue comment addressed to the reporter.
|
||||
The comment must:
|
||||
- clearly say it is an automated Claude triage note
|
||||
- clearly say Claude cannot reply back in a conversation
|
||||
- ask for the specific missing details needed to investigate, especially repro steps, expected behavior, actual behavior, environment, logs, or screenshots when relevant
|
||||
- for support issues, say this does not look like an SDK bug and that it is being routed to support
|
||||
- stay concise and professional
|
||||
|
||||
If should_comment is false, set comment_body to an empty string.
|
||||
EOF
|
||||
)
|
||||
|
||||
set +e
|
||||
claude -p "$PROMPT" \
|
||||
--model claude-sonnet-4-6 \
|
||||
--max-turns 10 \
|
||||
--output-format json \
|
||||
--dangerously-skip-permissions \
|
||||
--append-system-prompt "Respond with ONLY a raw JSON object (no markdown fences, no prose) that matches the schema in /tmp/issue-triage-schema.json." \
|
||||
> /tmp/claude-response.json 2> /tmp/claude-stderr.log
|
||||
CLAUDE_EXIT=$?
|
||||
set -e
|
||||
|
||||
echo "--- claude exit code: $CLAUDE_EXIT ---"
|
||||
echo "--- claude stderr ---"
|
||||
cat /tmp/claude-stderr.log || true
|
||||
echo "--- end stderr ---"
|
||||
echo "--- raw claude response ---"
|
||||
cat /tmp/claude-response.json || true
|
||||
echo
|
||||
echo "--- end raw ---"
|
||||
|
||||
if [ "$CLAUDE_EXIT" -ne 0 ]; then
|
||||
echo "Claude CLI failed with exit code $CLAUDE_EXIT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STRUCTURED=$(jq -r '.result' /tmp/claude-response.json)
|
||||
|
||||
# Strip optional ```json fences just in case
|
||||
STRUCTURED=$(echo "$STRUCTURED" | sed -E 's/^```(json)?//; s/```$//' | jq -c .)
|
||||
|
||||
echo "--- parsed structured output ---"
|
||||
echo "$STRUCTURED"
|
||||
echo "--- end parsed ---"
|
||||
|
||||
{
|
||||
echo "structured_output<<CLAUDE_EOF"
|
||||
echo "$STRUCTURED"
|
||||
echo "CLAUDE_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Apply labels, assignees, and tool-request handling
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
STRUCTURED_OUTPUT: ${{ steps.classify.outputs.structured_output }}
|
||||
PLAIN_API_KEY: ${{ secrets.PLAIN_API_KEY }}
|
||||
with:
|
||||
script: |
|
||||
const text = process.env.STRUCTURED_OUTPUT || "";
|
||||
let parsed;
|
||||
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch (error) {
|
||||
core.setFailed(`Could not parse structured_output as JSON: ${text}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const allowed = new Set(["docs", "py", "ts", "cli", "tool-request", "feature-request", "support", "other"]);
|
||||
if (!allowed.has(parsed.category)) {
|
||||
core.setFailed(`Unexpected category: ${parsed.category}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const category = parsed.category;
|
||||
const isBug = Boolean(parsed.is_bug);
|
||||
const reason = parsed.reason || "";
|
||||
const shouldComment = Boolean(parsed.should_comment);
|
||||
const commentBody = parsed.comment_body || "";
|
||||
const issue_number = context.eventName === "workflow_dispatch"
|
||||
? Number("${{ steps.issue-context.outputs.issue_number }}")
|
||||
: context.payload.issue.number;
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const issueState = context.eventName === "workflow_dispatch"
|
||||
? "${{ steps.issue-context.outputs.issue_state }}"
|
||||
: context.payload.issue.state;
|
||||
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
});
|
||||
|
||||
const labels = [];
|
||||
const assignees = [];
|
||||
const suppressToolRequestHandling = isBug && category === "tool-request";
|
||||
const isSupport = category === "support";
|
||||
|
||||
if (suppressToolRequestHandling) {
|
||||
core.info(
|
||||
`Suppressing tool-request handling for issue #${issue_number} because it is classified as a bug. Original reason: ${reason}`
|
||||
);
|
||||
}
|
||||
|
||||
if (isBug && !isSupport) {
|
||||
labels.push("bug");
|
||||
}
|
||||
|
||||
if (category === "docs") {
|
||||
labels.push("docs");
|
||||
assignees.push("shawnesquivel", "Sushmithamallesh");
|
||||
} else if (category === "py") {
|
||||
labels.push("py");
|
||||
assignees.push("jkomyno");
|
||||
} else if (category === "ts") {
|
||||
labels.push("ts");
|
||||
assignees.push("jkomyno");
|
||||
} else if (category === "cli") {
|
||||
labels.push("cli");
|
||||
assignees.push("CryogenicPlanet");
|
||||
} else if (category === "feature-request") {
|
||||
labels.push("feature-request");
|
||||
} else if (isSupport) {
|
||||
labels.push("support");
|
||||
} else if (category === "tool-request" && !suppressToolRequestHandling) {
|
||||
labels.push("tool-request");
|
||||
}
|
||||
|
||||
async function ensureLabel(name, color, description) {
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner, repo, name });
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
await github.rest.issues.createLabel({ owner, repo, name, color, description });
|
||||
}
|
||||
}
|
||||
|
||||
if (isSupport) {
|
||||
await ensureLabel("support", "0e8a16", "Needs support team follow-up");
|
||||
for (const staleLabel of ["bug", "ts"]) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: staleLabel });
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await github.rest.issues.removeAssignees({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
assignees: ["jkomyno"],
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (labels.length > 0) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
labels,
|
||||
});
|
||||
}
|
||||
|
||||
if (assignees.length > 0) {
|
||||
await github.rest.issues.addAssignees({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
assignees,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isSupport &&
|
||||
(category !== "tool-request" || suppressToolRequestHandling) &&
|
||||
shouldComment &&
|
||||
commentBody.trim().length > 0
|
||||
) {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
body: commentBody.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
async function plainGraphql(query, variables) {
|
||||
const apiKey = process.env.PLAIN_API_KEY;
|
||||
if (!apiKey) {
|
||||
core.warning("PLAIN_API_KEY is not configured; skipping Plain support forwarding.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = await fetch("https://core-api.uk.plain.com/graphql/v1", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ query, variables }),
|
||||
});
|
||||
const json = await response.json().catch(() => ({}));
|
||||
if (!response.ok || json.errors) {
|
||||
core.warning(`Plain API request failed: ${JSON.stringify(json)}`);
|
||||
return null;
|
||||
}
|
||||
return json.data;
|
||||
}
|
||||
|
||||
async function getIssueAuthorPlainCustomer() {
|
||||
const login = issue.user?.login || "unknown";
|
||||
let publicEmail = issue.user?.email || null;
|
||||
let displayName = issue.user?.name || login;
|
||||
|
||||
if (login !== "unknown") {
|
||||
try {
|
||||
const { data: user } = await github.rest.users.getByUsername({ username: login });
|
||||
publicEmail = user.email || publicEmail;
|
||||
displayName = user.name || displayName;
|
||||
} catch (error) {
|
||||
core.warning(`Could not fetch GitHub user profile for ${login}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
email: publicEmail || (issue.user?.id ? `${issue.user.id}+${login}@users.noreply.github.com` : `${login}@users.noreply.github.com`),
|
||||
fullName: displayName,
|
||||
shortName: login,
|
||||
externalId: `github:user:${login}`,
|
||||
};
|
||||
}
|
||||
|
||||
async function forwardSupportIssueToPlain() {
|
||||
const plainCustomer = await getIssueAuthorPlainCustomer();
|
||||
const customerData = await plainGraphql(
|
||||
`mutation upsertCustomer($input: UpsertCustomerInput!) {
|
||||
upsertCustomer(input: $input) {
|
||||
result
|
||||
customer { id }
|
||||
error { message type code fields { field message type } }
|
||||
}
|
||||
}`,
|
||||
{
|
||||
input: {
|
||||
identifier: { emailAddress: plainCustomer.email },
|
||||
onCreate: {
|
||||
fullName: plainCustomer.fullName,
|
||||
shortName: plainCustomer.shortName,
|
||||
email: { email: plainCustomer.email, isVerified: Boolean(issue.user?.email) },
|
||||
externalId: plainCustomer.externalId,
|
||||
},
|
||||
onUpdate: {
|
||||
fullName: { value: plainCustomer.fullName },
|
||||
shortName: { value: plainCustomer.shortName },
|
||||
externalId: { value: plainCustomer.externalId },
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
const customer = customerData?.upsertCustomer?.customer;
|
||||
const customerError = customerData?.upsertCustomer?.error;
|
||||
if (!customer?.id) {
|
||||
core.warning(`Could not upsert Plain support customer: ${JSON.stringify(customerError)}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const body = issue.body || "_No issue body provided._";
|
||||
const plainText = [
|
||||
`GitHub issue routed as support: ${issue.html_url}`,
|
||||
`Reporter: @${issue.user?.login || "unknown"}`,
|
||||
`Classification reason: ${reason || "No reason provided."}`,
|
||||
"",
|
||||
"Issue body:",
|
||||
body.length > 8000 ? `${body.slice(0, 8000)}\n\n[truncated]` : body,
|
||||
].join("\n");
|
||||
|
||||
const threadData = await plainGraphql(
|
||||
`mutation createThread($input: CreateThreadInput!) {
|
||||
createThread(input: $input) {
|
||||
thread { id ref }
|
||||
error { message type code fields { field message type } }
|
||||
}
|
||||
}`,
|
||||
{
|
||||
input: {
|
||||
title: `[GitHub #${issue.number}] ${issue.title}`,
|
||||
customerIdentifier: { customerId: customer.id },
|
||||
externalId: `github:${owner}/${repo}:issues/${issue.number}`,
|
||||
components: [{ componentText: { text: plainText } }],
|
||||
},
|
||||
}
|
||||
);
|
||||
const thread = threadData?.createThread?.thread;
|
||||
const threadError = threadData?.createThread?.error;
|
||||
if (!thread?.id) {
|
||||
core.warning(`Could not create Plain support thread: ${JSON.stringify(threadError)}`);
|
||||
return null;
|
||||
}
|
||||
core.info(`Forwarded support issue #${issue.number} to Plain thread ${thread.ref || thread.id}`);
|
||||
return thread;
|
||||
}
|
||||
|
||||
if (isSupport) {
|
||||
const thread = await forwardSupportIssueToPlain();
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
body: thread
|
||||
? `🤖 Automated Claude triage note: this does not look like an SDK bug, so I've labeled it as \`support\` and forwarded it to Plain for the support team. Claude cannot reply back in a conversation.`
|
||||
: `🤖 Automated Claude triage note: this does not look like an SDK bug, so I've labeled it as \`support\` for support team follow-up. Claude cannot reply back in a conversation.`,
|
||||
});
|
||||
}
|
||||
|
||||
if (category === "tool-request" && !suppressToolRequestHandling && issueState === "open") {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
body: [
|
||||
"🤖 beep boop — hey, I think this is a tool request! Please file it at https://request.composio.dev/boards/tool-requests so the team can track and prioritize it.",
|
||||
"",
|
||||
"I've tagged this as `tool-request` and closed it here.",
|
||||
"",
|
||||
"If you think I got this wrong, feel free to reopen the issue.",
|
||||
].join("\n"),
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
state: "closed",
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
name: Checks
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'python/**/*.py'
|
||||
- '.github/actions/setup-python-uv/action.yml'
|
||||
- '.github/workflows/py.check.yaml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
- 'python/**/*.py'
|
||||
- '.github/actions/setup-python-uv/action.yml'
|
||||
- '.github/workflows/py.check.yaml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
common-checks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
- name: Setup Python with UV
|
||||
id: setup-python
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
|
||||
- name: nox cache
|
||||
id: nox-cache
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: python/.nox
|
||||
key: nox-${{ hashFiles('pyproject.toml') }}-py${{ steps.setup-python.outputs.python-version }}
|
||||
restore-keys: |
|
||||
nox-
|
||||
|
||||
- name: Mypy cache
|
||||
id: mypy-cache
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: python/.mypy_cache
|
||||
key: mypy-${{ hashFiles('pyproject.toml') }}-py${{ steps.setup-python.outputs.python-version }}
|
||||
restore-keys: |
|
||||
mypy-
|
||||
|
||||
- name: Ruff cache
|
||||
id: ruff-cache
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: python/.ruff_cache
|
||||
key: ruff-${{ hashFiles('pyproject.toml') }}-py${{ steps.setup-python.outputs.python-version }}
|
||||
restore-keys: |
|
||||
ruff-
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
cd python/
|
||||
make env
|
||||
|
||||
- name: Lint and type checks
|
||||
shell: bash
|
||||
run: |
|
||||
cd python/
|
||||
uv run nox -s chk
|
||||
|
||||
- name: Type inference tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd python/
|
||||
uv run nox -s type_inference
|
||||
env:
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Release Python Packages
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'py@*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_tests:
|
||||
description: 'Skip running tests'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish-core:
|
||||
name: Build and release SDK
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./python
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Python with UV
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
|
||||
- name: Build Artifacts
|
||||
run: |
|
||||
make env
|
||||
make build
|
||||
|
||||
- name: Publish Artifacts
|
||||
if: github.event_name == 'push'
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
packages-dir: ./python/dist
|
||||
user: ${{ secrets.PYPI_USERNAME }}
|
||||
password: ${{ secrets.PYPI_PASSWORD }}
|
||||
skip-existing: true
|
||||
@@ -0,0 +1,265 @@
|
||||
name: Test Python SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'python/**/*.py'
|
||||
- '.github/workflows/py.test.yml'
|
||||
- '.github/actions/setup-python-uv/action.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'toolchain-versions.json'
|
||||
- 'python/pyproject.toml'
|
||||
- 'python/requirements*.txt'
|
||||
pull_request:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'python/**/*.py'
|
||||
- '.github/workflows/py.test.yml'
|
||||
- '.github/actions/setup-python-uv/action.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'toolchain-versions.json'
|
||||
- 'python/pyproject.toml'
|
||||
- 'python/requirements*.txt'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
toolchain-versions:
|
||||
name: Read toolchain test matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
python: ${{ steps.versions.outputs.python }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Read versions
|
||||
id: versions
|
||||
run: echo "python=$(jq -c '.python' toolchain-versions.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
test:
|
||||
name: Test Python SDK
|
||||
needs: toolchain-versions
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ${{ fromJSON(needs.toolchain-versions.outputs.python) }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Cache uv dependencies
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.cache/uv
|
||||
key: uv-${{ matrix.python-version }}-${{ hashFiles('python/pyproject.toml', 'mise.toml') }}
|
||||
restore-keys: |
|
||||
uv-${{ matrix.python-version }}-
|
||||
|
||||
- name: Install UV
|
||||
run: |
|
||||
UV_VERSION="$(sed -n 's/^uv[[:space:]]*=[[:space:]]*"\([^"]*\)"/\1/p' mise.toml)"
|
||||
pip install "uv==${UV_VERSION}"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
cd python/
|
||||
uv venv --python ${{ matrix.python-version }}
|
||||
source .venv/bin/activate
|
||||
uv pip install -e .
|
||||
uv pip install -e providers/langchain
|
||||
uv pip install -e providers/autogen
|
||||
uv pip install pytest pytest-mock
|
||||
|
||||
- name: Run Import Tests
|
||||
run: |
|
||||
cd python/
|
||||
source .venv/bin/activate
|
||||
python -c "from composio import Composio; print('✓ Basic import successful')"
|
||||
# Guards against the autogen provider becoming un-importable on a fresh
|
||||
# install (e.g. a broken framework dependency). A hard import here fails
|
||||
# loudly, unlike the importorskip-guarded unit tests, which would skip.
|
||||
python -c "import composio_autogen; print('✓ Autogen provider import successful')"
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: |
|
||||
cd python/
|
||||
source .venv/bin/activate
|
||||
python -m pytest tests/ -v --tb=short
|
||||
|
||||
- name: Test Package Installation
|
||||
run: |
|
||||
cd python/
|
||||
# Test that the package can be installed and imported in a fresh environment
|
||||
uv venv test-env --python ${{ matrix.python-version }}
|
||||
source test-env/bin/activate
|
||||
uv pip install -e .
|
||||
python -c "
|
||||
from composio import Composio, ToolkitVersionParam
|
||||
from composio.types import Modifiers
|
||||
from composio.core.types import ToolkitVersion
|
||||
print('✓ All critical imports successful')
|
||||
"
|
||||
|
||||
test-circular-imports:
|
||||
name: Test Circular Import Prevention
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Python with UV
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
cd python/
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install -e .
|
||||
|
||||
- name: Test Circular Import Prevention
|
||||
run: |
|
||||
cd python/
|
||||
source .venv/bin/activate
|
||||
python -c "
|
||||
# Test various import combinations that previously caused circular imports
|
||||
import sys
|
||||
|
||||
# Test 1: Basic imports
|
||||
from composio import Composio
|
||||
print('✓ Basic Composio import')
|
||||
|
||||
# Test 2: Import toolkit version types
|
||||
from composio import ToolkitVersionParam
|
||||
from composio.core.types import ToolkitVersion
|
||||
print('✓ Toolkit version types import')
|
||||
|
||||
# Test 3: Import from types module
|
||||
from composio.types import Modifiers
|
||||
print('✓ Types module import')
|
||||
|
||||
# Test 4: Import core models
|
||||
from composio.core.models.tools import Tools
|
||||
print('✓ Core models import')
|
||||
|
||||
# Test 5: Cross-module imports that caused the original circular import
|
||||
from composio.core.models.tools import Modifiers as CoreModifiers
|
||||
from composio.types import Modifiers as TypesModifiers
|
||||
assert CoreModifiers is TypesModifiers
|
||||
print('✓ Cross-module import consistency')
|
||||
|
||||
print('✓ All circular import prevention tests passed!')
|
||||
"
|
||||
|
||||
lint-and-format:
|
||||
name: Lint and Format Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Python with UV
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
cd python/
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install -e .
|
||||
uv pip install ruff
|
||||
|
||||
- name: Run Ruff Linter
|
||||
run: |
|
||||
cd python/
|
||||
source .venv/bin/activate
|
||||
ruff check --config config/ruff.toml composio/ tests/
|
||||
|
||||
- name: Run Ruff Formatter Check
|
||||
run: |
|
||||
cd python/
|
||||
source .venv/bin/activate
|
||||
ruff format --check --config config/ruff.toml composio/ tests/
|
||||
|
||||
integration-tests:
|
||||
name: Integration Tests
|
||||
runs-on: ubuntu-22.04 # Use Ubuntu 22.04 for stability
|
||||
permissions: read-all
|
||||
env:
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install UV
|
||||
id: setup-python
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
|
||||
- name: Cache UV dependencies
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ~/.cache/uv
|
||||
key: uv-${{ hashFiles('mise.lock') }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('python/pyproject.toml') }}
|
||||
restore-keys: |
|
||||
uv-${{ hashFiles('mise.lock') }}-py${{ steps.setup-python.outputs.python-version }}-
|
||||
|
||||
- name: Install Python Dependencies
|
||||
working-directory: ./python
|
||||
run: |
|
||||
# Let UV manage Python version to satisfy workspace requirements
|
||||
uv sync --group dev
|
||||
|
||||
- name: Check API key
|
||||
id: api-key
|
||||
run: |
|
||||
if [ -z "$COMPOSIO_API_KEY" ]; then
|
||||
echo "available=false" >> "$GITHUB_OUTPUT"
|
||||
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ]; then
|
||||
echo "::notice::Skipping secret-backed Python integration tests for Dependabot; missing COMPOSIO_API_KEY."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::COMPOSIO_API_KEY is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "available=true" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ API key is configured"
|
||||
|
||||
- name: Run Integration Tests
|
||||
if: steps.api-key.outputs.available == 'true'
|
||||
working-directory: ./python
|
||||
run: |
|
||||
echo "🧪 Running Integration Tests..."
|
||||
# Run with per-test timeout of 2 minutes and overall timeout
|
||||
# pytest-timeout should be installed via pyproject.toml dev dependencies
|
||||
uv run pytest composio/integration_test/ -v --tb=short --maxfail=3 --timeout=120 --timeout-method=thread
|
||||
timeout-minutes: 10
|
||||
|
||||
- name: Upload Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: integration-test-results
|
||||
path: |
|
||||
python/composio/integration_test/
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Secrets Detection
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
secrets:
|
||||
# The called reusable workflow declares these scopes; a narrower caller
|
||||
# grant clamps its token and the secret-scanning check 403s into a warning.
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
security-events: write
|
||||
uses: ComposioHQ/.github/.github/workflows/secrets-detection.yml@a6c053d4cb1820a7a85bc7837efe47cc0a559e26 # master (#12: SHA-pinned actions)
|
||||
secrets: inherit
|
||||
with:
|
||||
slack_channel: "buzz-security"
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Close Stale Issues and PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run daily at 00:00 UTC
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close Stale Issues
|
||||
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||
with:
|
||||
# General settings
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 60
|
||||
days-before-close: 2
|
||||
ascending: true
|
||||
|
||||
# Issue settings
|
||||
stale-issue-label: "stale"
|
||||
exempt-issue-labels: "pinned,security,bug,needs-triage,roadmap,enhancement,good first issue"
|
||||
stale-issue-message: |
|
||||
beep boop inactive for 60 days, closing in 48 hours
|
||||
close-issue-message: |
|
||||
closed due to inactivity. feel free to reopen if the issue persists.
|
||||
|
||||
# Pull request settings
|
||||
stale-pr-label: "stale"
|
||||
exempt-pr-labels: "pinned,security,needs-review,work-in-progress,dependencies"
|
||||
stale-pr-message: |
|
||||
beep boop inactive for 60 days, closing in 48 hours
|
||||
close-pr-message: |
|
||||
closed due to inactivity. feel free to reopen if still relevant.
|
||||
|
||||
# Optional: Remove stale label when updated
|
||||
remove-stale-when-updated: true
|
||||
|
||||
# Enable debug logs (set to false in production)
|
||||
debug-only: false
|
||||
@@ -0,0 +1,92 @@
|
||||
name: Audit Typescript SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.audit.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
pull_request:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.audit.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
name: Audit
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-caching: 'false'
|
||||
|
||||
- name: Verify mise.lock is fresh
|
||||
run: |
|
||||
mise lock --platform linux-x64,linux-arm64,macos-arm64,macos-x64
|
||||
git diff --exit-code mise.lock
|
||||
|
||||
- name: Run pnpm audit (production dependencies only)
|
||||
id: audit
|
||||
continue-on-error: true
|
||||
run: pnpm audit --prod > audit-output.txt 2>&1
|
||||
|
||||
- name: Comment on PR if audit failed
|
||||
if: steps.audit.outcome == 'failure' && github.event_name == 'pull_request'
|
||||
run: |
|
||||
{
|
||||
echo "⚠️ **Security Audit Warning**"
|
||||
echo ""
|
||||
echo "The \`pnpm audit --prod\` check found security vulnerabilities in production dependencies."
|
||||
echo ""
|
||||
echo "Please review and fix the vulnerabilities. You can try running:"
|
||||
echo "\`\`\`bash"
|
||||
echo "pnpm audit --fix --prod"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
echo "<details>"
|
||||
echo "<summary>Audit output</summary>"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
cat audit-output.txt
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
echo "</details>"
|
||||
} > audit-comment.txt
|
||||
|
||||
- name: Post audit comment to PR
|
||||
if: steps.audit.outcome == 'failure' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
|
||||
with:
|
||||
file-path: audit-comment.txt
|
||||
comment-tag: pnpm-audit-security-warning
|
||||
|
||||
- name: Fail on high/critical advisories
|
||||
run: pnpm audit --prod --audit-level=high
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Build Typescript SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.build.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'turbo.jsonc'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'tsdown.config.base.ts'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.build.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'turbo.jsonc'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'tsdown.config.base.ts'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Print Turborepo telemetry events to the logs instead of sending them.
|
||||
env:
|
||||
TURBO_TELEMETRY_DEBUG: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Typescript SDK
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run Linting
|
||||
run: pnpm lint
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm run build:packages
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Examples Nightly (staging)
|
||||
|
||||
# Executes the provider examples against the STAGING backend with real
|
||||
# credentials. Non-blocking by design: it runs on a schedule (and on demand),
|
||||
# never as a required PR check, so staging/LLM flakiness cannot gate merges.
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * *' # 06:00 UTC nightly
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
# Staging is not a secret; the API keys are.
|
||||
COMPOSIO_BASE_URL: https://staging-backend.composio.dev
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
jobs:
|
||||
examples-staging:
|
||||
name: ${{ matrix.provider }} examples against staging
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
provider: [mastra, openai]
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build SDK packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
# Both secrets must be present for scheduled and manually dispatched
|
||||
# validation. Otherwise smoke can self-skip and the LLM steps can no-op,
|
||||
# leaving the whole regression net green while testing nothing.
|
||||
- name: Require staging credentials
|
||||
if: ${{ env.COMPOSIO_API_KEY == '' || env.OPENAI_API_KEY == '' }}
|
||||
run: |
|
||||
echo "::error::COMPOSIO_API_KEY and OPENAI_API_KEY are required for examples validation."
|
||||
exit 1
|
||||
|
||||
# Deterministic regression net — needs only COMPOSIO_API_KEY, no LLM.
|
||||
# Self-skips when the secret is absent (e.g. fork-triggered dispatch).
|
||||
- name: ${{ matrix.provider }} smoke (wrapping + tool-router)
|
||||
run: pnpm --filter ${{ matrix.provider }}-example --fail-if-no-match run smoke
|
||||
|
||||
# Full agent run — only when an LLM key is configured.
|
||||
- name: ${{ matrix.provider }} direct-tools example
|
||||
if: ${{ env.OPENAI_API_KEY != '' }}
|
||||
run: bun ts/examples/${{ matrix.provider }}/src/index.ts
|
||||
|
||||
- name: ${{ matrix.provider }} tool-router example
|
||||
if: ${{ env.OPENAI_API_KEY != '' }}
|
||||
run: bun ts/examples/${{ matrix.provider }}/src/tool-router.ts
|
||||
@@ -0,0 +1,69 @@
|
||||
name: Examples TypeScript SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/examples/**'
|
||||
- 'ts/packages/core/**'
|
||||
- 'ts/packages/providers/**'
|
||||
- 'eslint.config.mjs'
|
||||
- 'package.json'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.examples.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'turbo.jsonc'
|
||||
pull_request:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/examples/**'
|
||||
- 'ts/packages/core/**'
|
||||
- 'ts/packages/providers/**'
|
||||
- 'eslint.config.mjs'
|
||||
- 'package.json'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.examples.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'turbo.jsonc'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
examples:
|
||||
name: Examples TypeScript SDK
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# Examples depend on the built @composio/* workspace packages for type
|
||||
# resolution and the Workers bundle. turbo typecheck also builds these via
|
||||
# ^build, but building explicitly keeps the wrangler step self-contained.
|
||||
- name: Build SDK packages
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Typecheck examples
|
||||
run: pnpm run typecheck:examples
|
||||
|
||||
- name: Lint examples
|
||||
run: pnpm run lint:examples
|
||||
|
||||
# Prove the Cloudflare Workers entry bundles without secrets or deploying.
|
||||
- name: Cloudflare Workers dry-run build
|
||||
run: pnpm exec turbo cf:dry-run --filter='./ts/examples/*'
|
||||
@@ -0,0 +1,115 @@
|
||||
name: TS SDK Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [next]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Print Turborepo telemetry events to the logs instead of sending them.
|
||||
env:
|
||||
TURBO_TELEMETRY_DEBUG: 1
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release Typescript SDK
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
# Mint a short-lived GitHub App installation token instead of a long-lived
|
||||
# PAT. App tokens don't expire out from under us (the old CI_BOT_TOKEN PAT
|
||||
# did), and — unlike the default GITHUB_TOKEN — events they create (the
|
||||
# "Release: update version" PR and its eventual merge) still trigger
|
||||
# downstream workflows like build-cli-binaries.yml, so publishing fires.
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
|
||||
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0 # Required for git show HEAD^ when comparing published package versions
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
# Pin Node.js 24 for the release job only: the npm it bundles supports
|
||||
# OIDC trusted publishing, which the toolchain's Node 22 (mise.toml)
|
||||
# does not. Intentionally a release-only override, not centralized.
|
||||
node-version: '24.17.0'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run Linting
|
||||
run: pnpm lint
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Create Release Pull Request & Publish packages
|
||||
id: changesets
|
||||
uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0
|
||||
if: github.event_name != 'workflow_dispatch' # only run on master merges
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
GITHUB_ACCESS_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
with:
|
||||
publish: pnpm changeset:release
|
||||
commit: 'Release: update version'
|
||||
title: 'Release: update version'
|
||||
|
||||
- name: Set Version Info
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
env:
|
||||
PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }}
|
||||
run: |
|
||||
SLACK_VERSIONS=""
|
||||
while read -r pkg; do
|
||||
NAME=$(jq -r '.name // empty' <<<"$pkg" 2>/dev/null || echo "")
|
||||
NEW_VERSION=$(jq -r '.version // empty' <<<"$pkg" 2>/dev/null || echo "")
|
||||
if [ -z "$NAME" ] || [ -z "$NEW_VERSION" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
VERSION_LINE="⚡️ $NAME: \`$NEW_VERSION\`"
|
||||
PKG_PATH=$(pnpm list --filter "$NAME" --json 2>/dev/null | jq -r '.[0].path // empty' 2>/dev/null || echo "")
|
||||
if [ -n "$PKG_PATH" ]; then
|
||||
REL_PKG_JSON_PATH="${PKG_PATH#$GITHUB_WORKSPACE/}/package.json"
|
||||
OLD_VERSION=$(git show "HEAD^:$REL_PKG_JSON_PATH" 2>/dev/null | jq -r '.version // empty' 2>/dev/null || echo "")
|
||||
if [ -n "$OLD_VERSION" ] && [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
|
||||
VERSION_LINE="⚡️ $NAME: \`$OLD_VERSION\` -> \`$NEW_VERSION\`"
|
||||
fi
|
||||
fi
|
||||
if [ -n "$SLACK_VERSIONS" ]; then
|
||||
SLACK_VERSIONS="${SLACK_VERSIONS}"$'\n'"${VERSION_LINE}"
|
||||
else
|
||||
SLACK_VERSIONS="$VERSION_LINE"
|
||||
fi
|
||||
done < <(jq -c 'if type == "array" then .[] else empty end' <<<"$PUBLISHED_PACKAGES" 2>/dev/null || true)
|
||||
if [ -z "$SLACK_VERSIONS" ]; then
|
||||
SLACK_VERSIONS="No version info available"
|
||||
fi
|
||||
# Build a single-line JSON payload so multiline version info is safely escaped.
|
||||
SLACK_PAYLOAD=$(jq -nc \
|
||||
--arg repository "$GITHUB_REPOSITORY" \
|
||||
--arg versions "$SLACK_VERSIONS" \
|
||||
--arg sha "$GITHUB_SHA" \
|
||||
'{text: "🚀 New SDK version published!\n*Repository:* \($repository)\n*Versions:*\n\($versions)\n*Commit:* \($sha)"}')
|
||||
echo "SLACK_PAYLOAD=$SLACK_PAYLOAD" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Send Slack Notification
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_RELEASE_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: ${{ env.SLACK_PAYLOAD }}
|
||||
@@ -0,0 +1,364 @@
|
||||
name: E2E Test Typescript SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.test-e2e.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'toolchain-versions.json'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.test-e2e.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'toolchain-versions.json'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Print Turborepo telemetry events to the logs instead of sending them.
|
||||
env:
|
||||
TURBO_TELEMETRY_DEBUG: 1
|
||||
|
||||
jobs:
|
||||
toolchain-versions:
|
||||
name: Read toolchain test matrix
|
||||
runs-on: ubuntu-24.04
|
||||
permissions: read-all
|
||||
outputs:
|
||||
node: ${{ steps.versions.outputs.node }}
|
||||
deno: ${{ steps.versions.outputs.deno }}
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Read versions
|
||||
id: versions
|
||||
run: |
|
||||
echo "node=$(jq -c '.node' toolchain-versions.json)" >> "$GITHUB_OUTPUT"
|
||||
echo "deno=$(jq -c '.deno' toolchain-versions.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Node.js E2E tests, running in Docker to isolate environment
|
||||
# Each test suite defines its own versions.node in e2e.test.ts
|
||||
# Tests that don't support the matrix version are automatically skipped
|
||||
test-e2e-node-docker:
|
||||
name: E2E Test Node.js ${{ matrix.node-version }}
|
||||
needs: toolchain-versions
|
||||
runs-on: ubuntu-24.04
|
||||
permissions: read-all
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: ${{ fromJSON(needs.toolchain-versions.outputs.node) }}
|
||||
env:
|
||||
CI: 1
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
|
||||
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
|
||||
COMPOSIO_E2E_NODE_VERSION: ${{ matrix.node-version }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Normalize Composio API keys
|
||||
run: |
|
||||
{
|
||||
echo "COMPOSIO_USER_API_KEY=${COMPOSIO_USER_API_KEY}"
|
||||
echo "COMPOSIO_API_KEY=${COMPOSIO_API_KEY}"
|
||||
echo "COMPOSIO_BASE_URL=${COMPOSIO_BASE_URL}"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
id: setup-toolchain
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Check API keys
|
||||
id: api-keys
|
||||
env:
|
||||
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
missing=()
|
||||
for name in COMPOSIO_API_KEY OPENAI_API_KEY ANTHROPIC_API_KEY; do
|
||||
if [ -z "${!name}" ]; then
|
||||
missing+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#missing[@]}" -gt 0 ]; then
|
||||
echo "available=false" >> "$GITHUB_OUTPUT"
|
||||
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ] || [ "$IS_FORK_PR" = "true" ]; then
|
||||
echo "::notice::Skipping secret-backed E2E tests; missing ${missing[*]}."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '::error::%s is not set\n' "${missing[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "available=true" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ API keys are configured"
|
||||
|
||||
- name: Pre-build Docker image for e2e tests
|
||||
if: steps.api-keys.outputs.available == 'true'
|
||||
run: |
|
||||
docker build -f ts/e2e-tests/_utils/Dockerfile.node \
|
||||
--build-arg NODE_VERSION=${{ matrix.node-version }} \
|
||||
--label composio.e2e=true \
|
||||
--label composio.runtime=node \
|
||||
--label "composio.node_version=${{ matrix.node-version }}" \
|
||||
-t "composio-e2e-node:${{ matrix.node-version }}" \
|
||||
.
|
||||
|
||||
- name: Run Node.js E2E Tests
|
||||
if: steps.api-keys.outputs.available == 'true'
|
||||
run: pnpm run test:e2e:node
|
||||
|
||||
# Deno E2E tests, running in Docker to isolate environment
|
||||
# Each test suite defines its own versions.deno in e2e.test.ts
|
||||
# Tests that don't support the matrix version are automatically skipped
|
||||
test-e2e-deno-docker:
|
||||
name: E2E Test Deno ${{ matrix.deno-version }}
|
||||
needs: toolchain-versions
|
||||
runs-on: ubuntu-24.04
|
||||
permissions: read-all
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
deno-version: ${{ fromJSON(needs.toolchain-versions.outputs.deno) }}
|
||||
env:
|
||||
CI: 1
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
|
||||
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
|
||||
COMPOSIO_E2E_DENO_VERSION: ${{ matrix.deno-version }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Normalize Composio API keys
|
||||
run: |
|
||||
{
|
||||
echo "COMPOSIO_USER_API_KEY=${COMPOSIO_USER_API_KEY}"
|
||||
echo "COMPOSIO_API_KEY=${COMPOSIO_API_KEY}"
|
||||
echo "COMPOSIO_BASE_URL=${COMPOSIO_BASE_URL}"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Deno
|
||||
uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5
|
||||
with:
|
||||
deno-version: ${{ matrix.deno-version }}
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
id: setup-toolchain
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Check API keys
|
||||
id: api-keys
|
||||
env:
|
||||
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
missing=()
|
||||
for name in COMPOSIO_API_KEY OPENAI_API_KEY; do
|
||||
if [ -z "${!name}" ]; then
|
||||
missing+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#missing[@]}" -gt 0 ]; then
|
||||
echo "available=false" >> "$GITHUB_OUTPUT"
|
||||
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ] || [ "$IS_FORK_PR" = "true" ]; then
|
||||
echo "::notice::Skipping secret-backed E2E tests; missing ${missing[*]}."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '::error::%s is not set\n' "${missing[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "available=true" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ API keys are configured"
|
||||
|
||||
- name: Run Deno E2E Tests
|
||||
if: steps.api-keys.outputs.available == 'true'
|
||||
run: pnpm run test:e2e:deno
|
||||
|
||||
# CLI E2E tests, running in scratch Docker to isolate environment
|
||||
test-e2e-cli-docker:
|
||||
name: E2E Test CLI (scratch)
|
||||
runs-on: ubuntu-24.04
|
||||
permissions: read-all
|
||||
env:
|
||||
CI: 1
|
||||
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Normalize Composio API keys
|
||||
run: |
|
||||
{
|
||||
echo "COMPOSIO_USER_API_KEY=${COMPOSIO_USER_API_KEY}"
|
||||
echo "COMPOSIO_API_KEY=${COMPOSIO_API_KEY}"
|
||||
echo "COMPOSIO_BASE_URL=${COMPOSIO_BASE_URL}"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
id: setup-toolchain
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Check API keys
|
||||
id: api-keys
|
||||
env:
|
||||
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
missing=()
|
||||
for name in COMPOSIO_API_KEY OPENAI_API_KEY COMPOSIO_USER_API_KEY; do
|
||||
if [ -z "${!name}" ]; then
|
||||
missing+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#missing[@]}" -gt 0 ]; then
|
||||
echo "available=false" >> "$GITHUB_OUTPUT"
|
||||
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ] || [ "$IS_FORK_PR" = "true" ]; then
|
||||
echo "::notice::Skipping secret-backed E2E tests; missing ${missing[*]}."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '::error::%s is not set\n' "${missing[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "available=true" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ API keys are configured"
|
||||
|
||||
- name: Pre-build Docker image for CLI e2e tests
|
||||
run: |
|
||||
CLI_VERSION=$(node -p "require('./ts/packages/cli/package.json').version")
|
||||
echo "COMPOSIO_E2E_CLI_VERSION=${CLI_VERSION}" >> "$GITHUB_ENV"
|
||||
docker build -f ts/e2e-tests/_utils/Dockerfile.cli \
|
||||
--build-arg CLI_VERSION="${CLI_VERSION}" \
|
||||
--build-arg NODE_VERSION="${{ steps.setup-toolchain.outputs.node-version }}" \
|
||||
--label composio.e2e=true \
|
||||
--label composio.runtime=cli \
|
||||
--label "composio.cli_version=${CLI_VERSION}" \
|
||||
-t "composio-e2e-cli:${CLI_VERSION}" \
|
||||
.
|
||||
|
||||
- name: Run CLI E2E Tests
|
||||
if: steps.api-keys.outputs.available == 'true'
|
||||
run: pnpm run test:e2e:cli
|
||||
|
||||
# Cloudflare Workerd E2E tests
|
||||
test-e2e-cloudflare:
|
||||
name: E2E Test Cloudflare Workerd
|
||||
runs-on: ubuntu-24.04
|
||||
permissions: read-all
|
||||
env:
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
|
||||
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Normalize Composio API keys
|
||||
run: |
|
||||
{
|
||||
echo "COMPOSIO_USER_API_KEY=${COMPOSIO_USER_API_KEY}"
|
||||
echo "COMPOSIO_API_KEY=${COMPOSIO_API_KEY}"
|
||||
echo "COMPOSIO_BASE_URL=${COMPOSIO_BASE_URL}"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
node-version: '22.22.3'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Check API keys
|
||||
id: api-keys
|
||||
env:
|
||||
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
missing=()
|
||||
for name in COMPOSIO_API_KEY OPENAI_API_KEY; do
|
||||
if [ -z "${!name}" ]; then
|
||||
missing+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#missing[@]}" -gt 0 ]; then
|
||||
echo "available=false" >> "$GITHUB_OUTPUT"
|
||||
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ] || [ "$IS_FORK_PR" = "true" ]; then
|
||||
echo "::notice::Skipping secret-backed E2E tests; missing ${missing[*]}."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '::error::%s is not set\n' "${missing[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "available=true" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ API keys are configured"
|
||||
|
||||
- name: Run Cloudflare Workers E2E Tests
|
||||
if: steps.api-keys.outputs.available == 'true'
|
||||
run: pnpm run test:e2e:cloudflare
|
||||
env:
|
||||
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
|
||||
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
|
||||
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Test Typescript SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.test.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'turbo.jsonc'
|
||||
pull_request:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.test.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'turbo.jsonc'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test Typescript SDK
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Check Python helpers codegen is up to date
|
||||
run: pnpm --filter @composio/experimental run check:python-helpers
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm run test
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Typecheck Typescript SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.typecheck.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'turbo.jsonc'
|
||||
pull_request:
|
||||
branches: [master, next]
|
||||
paths:
|
||||
- 'ts/**'
|
||||
- '.github/actions/setup-node-pnpm-bun/action.yml'
|
||||
- '.github/workflows/ts.typecheck.yml'
|
||||
- 'mise.toml'
|
||||
- 'mise.lock'
|
||||
- 'turbo.jsonc'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
name: Typecheck Typescript SDK
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup Node.js, pnpm, Bun
|
||||
uses: ./.github/actions/setup-node-pnpm-bun
|
||||
with:
|
||||
enable-turbo-cache: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run Typecheck
|
||||
run: pnpm run typecheck
|
||||
Reference in New Issue
Block a user