chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
docs/ @shawnesquivel @Sushmithamallesh
|
||||
ts/packages/cli/ @CryogenicPlanet
|
||||
ts/ @jkomyno
|
||||
python/ @jkomyno
|
||||
@@ -0,0 +1,47 @@
|
||||
# Code of Conduct
|
||||
|
||||
This project and community (including our Discord server) are dedicated to providing a welcoming, safe, and collaborative environment for everyone.
|
||||
|
||||
## Our Pledge
|
||||
We, as maintainers and contributors, pledge to make participation in our community a harassment‑free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio‑economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
Examples of behavior that contributes to a positive environment include:
|
||||
- Being kind, respectful, and considerate
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy toward other community members
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
- Harassment, discrimination, or hate speech
|
||||
- Public or private harassment, including unwelcome sexual attention
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Publishing others' private information ("doxxing") without explicit permission
|
||||
- Advertising, spam, or off‑topic self‑promotion in project channels
|
||||
- Any behavior that would be illegal or violates platform Terms of Service
|
||||
|
||||
## Discord‑Specific Guidelines
|
||||
Our Discord is an extension of this community:
|
||||
- Use appropriate channels; keep topics on‑topic for each channel
|
||||
- No unsolicited DMs for support or promotion; ask before DMing
|
||||
- Respect moderators and follow their directions; do not argue moderation in public channels
|
||||
- Report suspicious links or abuse to moderators immediately
|
||||
- Follow Discord’s Terms of Service and Community Guidelines
|
||||
|
||||
## Enforcement Responsibilities
|
||||
Project maintainers and Discord moderators are responsible for clarifying and enforcing standards. They may take appropriate and fair corrective action in response to any behavior they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
This Code applies within all community spaces—GitHub repositories, issues, pull requests, and our Discord—and when an individual is officially representing the community in public spaces.
|
||||
|
||||
## Reporting
|
||||
If you experience or witness unacceptable behavior, report it to the maintainers at support@composio.dev or via a direct message to the Discord moderators. All good‑faith reports will be reviewed and investigated promptly and fairly.
|
||||
|
||||
## Enforcement Guidelines
|
||||
- Correction: Private, written warning and guidance
|
||||
- Warning: Official warning with consequences for continued behavior
|
||||
- Temporary Ban: Temporary removal from community spaces
|
||||
- Permanent Ban: Permanent removal from community spaces
|
||||
|
||||
## Attribution
|
||||
This Code of Conduct is adapted from common open‑source community standards, including the Contributor Covenant.
|
||||
@@ -0,0 +1,136 @@
|
||||
name: "🐞 Bug Report"
|
||||
description: "Report a bug or unexpected behavior in the Composio TypeScript or Python SDK"
|
||||
title: "[Bug]: <Short description>"
|
||||
labels: ["bug", "needs-triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## 🐞 Bug Report
|
||||
Thanks for taking the time to report a bug! Please provide as much detail as possible to help us investigate and fix it quickly.
|
||||
|
||||
- type: dropdown
|
||||
id: sdk_language
|
||||
attributes:
|
||||
label: "SDK Language"
|
||||
description: "Which Composio SDK are you using?"
|
||||
options:
|
||||
- TypeScript / Node.js SDK (`@composio/core`)
|
||||
- Python SDK (`composio` package)
|
||||
- Both
|
||||
- Not sure
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: sdk_version
|
||||
attributes:
|
||||
label: "SDK Version"
|
||||
description: "Version of the SDK (e.g., @composio/core@0.1.55 or composio==0.1.7)"
|
||||
placeholder: "@composio/core@0.1.55 or composio==0.1.7"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: runtime_env
|
||||
attributes:
|
||||
label: "Runtime Environment"
|
||||
description: "Specify Node.js or Python version, plus platform if relevant"
|
||||
placeholder: |
|
||||
Node.js v20.10.0 on macOS 14.2
|
||||
or
|
||||
Python 3.11 on Ubuntu 22.04
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: environment
|
||||
attributes:
|
||||
label: "Environment"
|
||||
description: "Where did this issue occur?"
|
||||
options:
|
||||
- Local Development
|
||||
- Production Deployment
|
||||
- Cloudflare Worker
|
||||
- Vercel / Serverless Function
|
||||
- Other
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "Describe the Bug"
|
||||
description: "What happened? What did you expect to happen instead?"
|
||||
placeholder: |
|
||||
When executing `composio.tools.execute('GITHUB_GET_REPO')`, it throws a 401 even with a valid token.
|
||||
Expected: repository details to be returned.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction_steps
|
||||
attributes:
|
||||
label: "Steps to Reproduce"
|
||||
description: "Please include exact steps or code to reproduce the issue"
|
||||
placeholder: |
|
||||
1. Initialize Composio
|
||||
2. Authorize GitHub
|
||||
3. Call `composio.tools.execute('GITHUB_GET_REPO', {...})`
|
||||
4. Observe 401 error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: code_snippet
|
||||
attributes:
|
||||
label: "Minimal Reproducible Example"
|
||||
description: "Paste code or commands to reproduce (use correct language block)"
|
||||
placeholder: |
|
||||
```typescript
|
||||
import { Composio } from '@composio/core';
|
||||
|
||||
const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
|
||||
const result = await composio.tools.execute('GITHUB_GET_REPO', {
|
||||
userId: 'default',
|
||||
arguments: { owner: 'composio', repo: 'sdk' },
|
||||
});
|
||||
```
|
||||
```python
|
||||
from composio import Composio
|
||||
|
||||
composio = Composio(api_key="YOUR_API_KEY")
|
||||
result = composio.tools.execute("GITHUB_GET_REPO", user_id="default", arguments={"owner": "composio", "repo": "sdk"})
|
||||
```
|
||||
render: shell
|
||||
|
||||
- type: textarea
|
||||
id: error_logs
|
||||
attributes:
|
||||
label: "Error Output / Stack Trace"
|
||||
description: "Paste the full error output or traceback"
|
||||
render: shell
|
||||
placeholder: |
|
||||
Error: [ComposioError] Unauthorized request
|
||||
File "sdk/tools.py", line 82, in execute
|
||||
raise ExecutionFailed("Unauthorized request")
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: reproducibility
|
||||
attributes:
|
||||
label: "Reproducibility"
|
||||
description: "How consistently does the bug occur?"
|
||||
options:
|
||||
- label: "Always reproducible"
|
||||
- label: "Intermittent / Sometimes"
|
||||
- label: "Happened once, can’t reproduce"
|
||||
|
||||
- type: textarea
|
||||
id: additional_context
|
||||
attributes:
|
||||
label: "Additional Context or Screenshots"
|
||||
description: "Add logs, screenshots, or related issues if available"
|
||||
@@ -0,0 +1,101 @@
|
||||
name: "🚀 Feature Request"
|
||||
description: "Suggest a new feature or improvement for the Composio SDKs, Dashboard, or Integrations"
|
||||
title: "[Feature]: <Short description>"
|
||||
labels: ["feature-request", "enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## 🚀 Feature Request
|
||||
Have an idea that can make Composio better? Please describe it below as clearly as possible.
|
||||
The more context you give, the easier it is for us to prioritize and implement!
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: "Area of Improvement"
|
||||
description: "Which part of Composio does this request relate to?"
|
||||
options:
|
||||
- TypeScript / Node.js SDK
|
||||
- Python SDK
|
||||
- Dashboard (app.composio.dev)
|
||||
- Integrations / Toolkits
|
||||
- MCP Server / Agentic Providers
|
||||
- Other
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: feature_title
|
||||
attributes:
|
||||
label: "Feature Name"
|
||||
description: "Short descriptive name for the feature"
|
||||
placeholder: "Add support for GitLab toolkit"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: feature_description
|
||||
attributes:
|
||||
label: "Describe the Feature"
|
||||
description: "What would you like to see added or improved?"
|
||||
placeholder: |
|
||||
I’d love to see a GitLab toolkit added to Composio SDK so I can list projects, issues, and merge requests directly from my AI agent.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: use_case
|
||||
attributes:
|
||||
label: "Use Case / Motivation"
|
||||
description: "Explain why this feature is valuable. What problem does it solve?"
|
||||
placeholder: |
|
||||
- My AI agent needs to interact with GitLab repositories.
|
||||
- I want to automate PR creation and review workflows.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposed_solution
|
||||
attributes:
|
||||
label: "Proposed Solution or API Design (Optional)"
|
||||
description: "How would you like this to work? Feel free to propose SDK methods or UI ideas."
|
||||
placeholder: |
|
||||
Example (TypeScript):
|
||||
```typescript
|
||||
const gitlabTools = await composio.tools.get('default', { toolkits: ['gitlab'] });
|
||||
await composio.tools.execute('GITLAB_CREATE_ISSUE', { userId, arguments: {...} });
|
||||
```
|
||||
|
||||
- type: checkboxes
|
||||
id: impact_scope
|
||||
attributes:
|
||||
label: "Who does this impact?"
|
||||
options:
|
||||
- label: "Developers using the SDK"
|
||||
- label: "AI Agent frameworks (LangChain, OpenAI, etc.)"
|
||||
- label: "Dashboard users"
|
||||
- label: "End users of connected apps"
|
||||
|
||||
- type: textarea
|
||||
id: related_issues
|
||||
attributes:
|
||||
label: "Related Issues / References"
|
||||
description: "Link any related GitHub issues, docs, or external resources"
|
||||
placeholder: "#42, https://developers.gitlab.com/api/reference"
|
||||
|
||||
- type: checkboxes
|
||||
id: willingness
|
||||
attributes:
|
||||
label: "Would you like to contribute to this feature?"
|
||||
options:
|
||||
- label: "Yes, I can help implement it"
|
||||
- label: "Maybe, I can help test or review"
|
||||
- label: "No, just sharing the idea"
|
||||
|
||||
- type: textarea
|
||||
id: additional_context
|
||||
attributes:
|
||||
label: "Additional Context"
|
||||
description: "Any extra information, mockups, or screenshots"
|
||||
@@ -0,0 +1,35 @@
|
||||
name: "🔧 Tool Request"
|
||||
description: "Request a new toolkit or tool integration"
|
||||
title: "[Tool Request]: <Tool/Integration Name>"
|
||||
labels: ["tool-request", "integrations"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## 🔧 Tool Request
|
||||
|
||||
Thank you for your interest in requesting a new tool or integration!
|
||||
|
||||
**Please submit your tool request on our dedicated request board:**
|
||||
|
||||
👉 **[https://request.composio.dev/boards/tool-requests](https://request.composio.dev/boards/tool-requests)**
|
||||
|
||||
This helps us:
|
||||
- Track all tool requests in one place
|
||||
- Allow the community to upvote popular requests
|
||||
- Keep you updated on the status of your request
|
||||
|
||||
Before submitting, please check existing apps and pending requests to avoid duplicates.
|
||||
|
||||
---
|
||||
|
||||
_This GitHub issue template is for bug reports and feature requests only. Tool requests should be submitted through the request board above._
|
||||
|
||||
- type: checkboxes
|
||||
id: confirmation
|
||||
attributes:
|
||||
label: "Confirmation"
|
||||
options:
|
||||
- label: "I understand that I need to submit tool requests on the request board"
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
## Summary
|
||||
Explain the motivation and context for this change. Link to any related issues.
|
||||
|
||||
Fixes #
|
||||
|
||||
## Changes
|
||||
-
|
||||
-
|
||||
|
||||
## Type of change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Refactor/Chore
|
||||
- [ ] Documentation
|
||||
- [ ] Breaking change
|
||||
|
||||
## How Has This Been Tested?
|
||||
Describe the tests you ran and instructions so reviewers can reproduce. Include any relevant config/versions.
|
||||
|
||||
## Screenshots (if applicable)
|
||||
|
||||
## Checklist
|
||||
- [ ] I have read the Code of Conduct and this PR adheres to it
|
||||
- [ ] I ran linters/tests locally and they passed
|
||||
- [ ] I updated documentation as needed
|
||||
- [ ] I added tests or explain why not applicable
|
||||
- [ ] I added a changeset if this change affects published packages
|
||||
|
||||
## Additional context
|
||||
@@ -0,0 +1,130 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take the security of Composio seriously. If you believe you have found a security vulnerability, please report it to us through GitHub Security Advisories or email us at **security@composio.dev**
|
||||
|
||||
### How to Report
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them using one of the following methods:
|
||||
|
||||
- **GitHub Security Advisory (Preferred)**: Report a vulnerability directly through GitHub by visiting: [https://github.com/composiohq/composio/security/advisories/new](https://github.com/composiohq/composio/security/advisories/new)
|
||||
- **Email**: If you prefer not to use GitHub Security Advisories, you can email security concerns to **security@composio.dev**
|
||||
|
||||
### What to Include
|
||||
|
||||
Please include as much of the following information as possible:
|
||||
|
||||
- Type of vulnerability
|
||||
- Full paths of source file(s) related to the vulnerability
|
||||
- Location of the affected source code (tag/branch/commit or direct URL)
|
||||
- Step-by-step instructions to reproduce the issue
|
||||
- Proof-of-concept or exploit code (if possible)
|
||||
- Impact of the vulnerability, including how an attacker might exploit it
|
||||
|
||||
### Response Timeline
|
||||
|
||||
- We will acknowledge your report within **48 hours**
|
||||
- We will provide a more detailed response within **7 days** indicating the next steps
|
||||
- We will keep you informed of the progress toward resolving the issue
|
||||
- We may ask for additional information or guidance
|
||||
|
||||
## Ground Rules
|
||||
|
||||
### Do:
|
||||
|
||||
- Abide by these Program Terms
|
||||
- Be patient and make a good faith effort to provide clarifications to any questions we may have about your submission
|
||||
- Be respectful when interacting with our team, and our team will do the same
|
||||
- Perform testing only using accounts that are your own personal/test accounts
|
||||
- Exercise caution when testing to avoid negative impact to data or services
|
||||
- Respect privacy and make a good faith effort not to change or destroy Composio or personal data
|
||||
- Stop whenever you are unsure if your test case may cause, or have caused, destructive data or systems damage; report your initial finding(s) and request authorization to continue testing
|
||||
|
||||
### Do NOT:
|
||||
|
||||
- Leave any system in a more vulnerable state than you found it
|
||||
- Use or interact with accounts you do not own
|
||||
- Brute force credentials or guess credentials to gain access to systems or accounts
|
||||
- Change passwords of any account that is not yours or that you do not have explicit permission to change
|
||||
- Perform denial of service (DoS) attacks or related tests that would cause availability interruptions or degradation of our services
|
||||
- Publicly disclose a vulnerability submission without our explicit review and consent
|
||||
- Engage in any form of social engineering of Composio employees, customers, or partners
|
||||
- Engage or target any specific Composio employees, customers, or partners during your testing
|
||||
- Access, extract, or download personal or business information beyond that which is minimally necessary for your Proof-of-Concept purposes
|
||||
- Do anything that would cause destruction of Composio data or systems
|
||||
- Test against production systems without permission
|
||||
- Access or modify user data
|
||||
- Exploit vulnerabilities beyond what is needed for a proof of concept
|
||||
|
||||
## Out-of-Scope Vulnerabilities
|
||||
|
||||
Certain vulnerabilities are considered out-of-scope for the Bug Bounty Program. Those out-of-scope vulnerabilities include, but are not limited to:
|
||||
|
||||
- Vulnerabilities not involving product or coding flaws, but solely relying upon possession of stolen or compromised credentials
|
||||
- Vulnerabilities dependent on Phishing in a DNS domain that is not in one of our primary service domains
|
||||
- Most vulnerabilities that rely on a runtime context within a sandbox, lab, staging, testing or non-production environment
|
||||
- Vulnerabilities involving stolen or compromised credentials
|
||||
- Open redirect resulting in a low security impact
|
||||
- Credential stuffing or physical access to a device
|
||||
- Any vulnerabilities requiring significant and unlikely interaction by the victim
|
||||
- Man-in-the-Middle attacks except in mobile applications
|
||||
- Account enumeration with a pre-defined and known list of UUIDs
|
||||
- Invite/Promo code enumeration
|
||||
- Ability to send push notifications/SMS messages/emails without the ability to change content
|
||||
- Information disclosures related to existence of accounts
|
||||
- Reports stating that a particular software component is vulnerable without an accompanying proof-of-concept
|
||||
- Vulnerabilities only affecting users using outdated, unpatched, or unsupported browsers or software
|
||||
- Stack traces, path disclosure, and directory listings
|
||||
- CSV injection vulnerabilities
|
||||
- Best practices concerns without a demonstrable information assurance issue and proof-of-concept
|
||||
- Ability to take over social media pages
|
||||
- Negligible security severity
|
||||
- Speculative reports about theoretical damage – please always provide a proof-of-concept
|
||||
- Self-XSS or similar vulnerabilities that cannot be used to exploit other users
|
||||
- Vulnerabilities reported by automated scanning tools without additional analysis
|
||||
- Distributed or denial of service attacks (DDoS/DoS) and/or reports on rate limiting issues
|
||||
- Content injection or content spoofing issues
|
||||
- Cross-site Request Forgery (CSRF) with minimal security implications
|
||||
- Missing cookie flags on non-authentication cookies
|
||||
- Submissions that require physical access to a victim's computer/device
|
||||
- SSL/TLS protocol scan reports
|
||||
- Banner grabbing issues
|
||||
- Open ports or services without accompanying proof-of-concept
|
||||
- Physical or social engineering attempts
|
||||
- Exposed login panels without proof-of-concept
|
||||
- Dangling IPs
|
||||
- Subdomain takeovers without proof
|
||||
- Reports on third-party products, services, or applications not owned by Composio
|
||||
- Out-of-scope domains
|
||||
- Leaked API Key or credentials having no impact on Composio assets
|
||||
|
||||
## CVEs and 0-Days
|
||||
|
||||
### CVE Age Requirement
|
||||
|
||||
We will only accept CVEs that meet all of the criteria below:
|
||||
|
||||
- Public for at least **60 days**
|
||||
- Demonstrated impact on our environment (not theoretical)
|
||||
- Reproducible with clear steps or proof-of-impact
|
||||
|
||||
**CVE reports that do not meet the 60-day requirement are out of scope.**
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We release patches for security vulnerabilities. Please ensure you are using the latest version of Composio.
|
||||
|
||||
## Disclosure Policy
|
||||
|
||||
- We follow coordinated disclosure practices
|
||||
- Security advisories will be published after a fix is available
|
||||
- We appreciate responsible disclosure and will acknowledge reporters in the advisory (unless you prefer to remain anonymous)
|
||||
|
||||
---
|
||||
|
||||
**Thank you for helping keep Composio and our users safe!**
|
||||
|
||||
We follow responsible disclosure practices and work with researchers to ensure vulnerabilities are properly addressed before public disclosure.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Support
|
||||
|
||||
Thanks for using Composio! If you need help, use the following channels:
|
||||
|
||||
- Documentation: https://docs.composio.dev
|
||||
- Discord: https://discord.gg/composio (best for community help and quick questions)
|
||||
- Email: support@composio.dev (account issues, private questions, or if you can’t use Discord)
|
||||
|
||||
Please avoid filing support requests as GitHub issues unless you believe you’ve found a bug. For security issues, do not open a public issue—follow `.github/SECURITY.md`.
|
||||
|
||||
## Before asking for help
|
||||
- Check open and closed issues
|
||||
- Search the docs and Discord
|
||||
- Include versions, logs, minimal repro steps, and expected vs. actual behavior
|
||||
|
||||
We do our best to respond within 2–3 business days.
|
||||
@@ -0,0 +1,117 @@
|
||||
name: 'Setup Node.js, pnpm, Bun via mise'
|
||||
description: 'Sets up Node.js, pnpm, and Bun from mise.toml unless explicit matrix overrides are provided'
|
||||
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'Node.js version to use. Defaults to mise.toml'
|
||||
required: false
|
||||
default: ''
|
||||
bun-version:
|
||||
description: 'Bun version to use. Defaults to mise.toml'
|
||||
required: false
|
||||
default: ''
|
||||
enable-caching:
|
||||
description: 'Enable pnpm/tool cache'
|
||||
required: false
|
||||
default: 'true'
|
||||
enable-turbo-cache:
|
||||
description: 'Cache .turbo build outputs'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
node-version:
|
||||
description: 'The installed Node.js version'
|
||||
value: ${{ steps.versions.outputs.node }}
|
||||
bun-version:
|
||||
description: 'The installed Bun version'
|
||||
value: ${{ steps.versions.outputs.bun }}
|
||||
pnpm-version:
|
||||
description: 'The installed pnpm version'
|
||||
value: ${{ steps.versions.outputs.pnpm }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup mise
|
||||
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
|
||||
with:
|
||||
version: 2026.5.18
|
||||
install: false
|
||||
cache: ${{ inputs.enable-caching }}
|
||||
|
||||
- name: Install mise-managed Node.js and Bun
|
||||
shell: bash
|
||||
env:
|
||||
NODE_VERSION: ${{ inputs.node-version }}
|
||||
BUN_VERSION: ${{ inputs.bun-version }}
|
||||
run: |
|
||||
tools=()
|
||||
if [ -z "$NODE_VERSION" ]; then
|
||||
tools+=(node)
|
||||
fi
|
||||
if [ -z "$BUN_VERSION" ]; then
|
||||
tools+=(bun)
|
||||
fi
|
||||
if [ "${#tools[@]}" -gt 0 ]; then
|
||||
mise install "${tools[@]}"
|
||||
fi
|
||||
|
||||
- name: Setup Node.js override
|
||||
if: inputs.node-version != ''
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
|
||||
- name: Setup Bun override
|
||||
if: inputs.bun-version != ''
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version: ${{ inputs.bun-version }}
|
||||
|
||||
- name: Install mise-managed pnpm
|
||||
shell: bash
|
||||
run: mise install npm:pnpm
|
||||
|
||||
- name: Prefer mise-managed pnpm
|
||||
shell: bash
|
||||
run: |
|
||||
# Node.js distributions can expose their own pnpm. Keep mise.toml's
|
||||
# npm:pnpm pin ahead of the Node install, especially for Node overrides.
|
||||
echo "$(mise where npm:pnpm)/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Resolve pnpm store path and Node version
|
||||
if: inputs.enable-caching == 'true'
|
||||
id: pnpm-store
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
||||
echo "NODE_VERSION=$(node --version | sed 's/^v//')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache pnpm store
|
||||
if: inputs.enable-caching == 'true'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-node${{ steps.pnpm-store.outputs.NODE_VERSION }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node${{ steps.pnpm-store.outputs.NODE_VERSION }}-pnpm-store-
|
||||
|
||||
- name: Cache turbo build outputs
|
||||
if: inputs.enable-caching == 'true' && inputs.enable-turbo-cache == 'true'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: .turbo/cache
|
||||
# Keyed per job: concurrent jobs save different task graphs under the
|
||||
# same commit, and actions/cache is first-writer-wins on exact keys.
|
||||
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-turbo-${{ github.job }}-
|
||||
|
||||
- name: Report versions
|
||||
id: versions
|
||||
shell: bash
|
||||
run: |
|
||||
echo "node=$(node --version | sed 's/^v//')" >> "$GITHUB_OUTPUT"
|
||||
echo "bun=$(bun --version)" >> "$GITHUB_OUTPUT"
|
||||
echo "pnpm=$(pnpm --version)" >> "$GITHUB_OUTPUT"
|
||||
@@ -0,0 +1,66 @@
|
||||
name: 'Setup Python with uv via mise'
|
||||
description: 'Sets up Python and uv from mise.toml unless explicit matrix overrides are provided'
|
||||
|
||||
inputs:
|
||||
python-version:
|
||||
description: 'Python version to use. Defaults to mise.toml'
|
||||
required: false
|
||||
default: ''
|
||||
uv-version:
|
||||
description: 'uv version to install. Defaults to mise.toml'
|
||||
required: false
|
||||
default: ''
|
||||
enable-caching:
|
||||
description: 'Enable uv/tool cache'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
outputs:
|
||||
python-version:
|
||||
description: 'The installed Python version (e.g., 3.12.0)'
|
||||
value: ${{ steps.versions.outputs.python }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup mise
|
||||
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
|
||||
with:
|
||||
version: 2026.5.18
|
||||
install: false
|
||||
cache: ${{ inputs.enable-caching }}
|
||||
|
||||
- name: Install mise-managed Python and uv
|
||||
shell: bash
|
||||
env:
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
UV_VERSION: ${{ inputs.uv-version }}
|
||||
run: |
|
||||
tools=()
|
||||
if [ -z "$PYTHON_VERSION" ]; then
|
||||
tools+=(python)
|
||||
fi
|
||||
if [ -z "$UV_VERSION" ]; then
|
||||
tools+=(uv)
|
||||
fi
|
||||
if [ "${#tools[@]}" -gt 0 ]; then
|
||||
mise install "${tools[@]}"
|
||||
fi
|
||||
|
||||
- name: Setup Python override
|
||||
if: inputs.python-version != ''
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- name: Setup uv override
|
||||
if: inputs.uv-version != ''
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||
with:
|
||||
version: ${{ inputs.uv-version }}
|
||||
enable-cache: ${{ inputs.enable-caching }}
|
||||
|
||||
- name: Report versions
|
||||
id: versions
|
||||
shell: bash
|
||||
run: echo "python=$(python --version | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
@@ -0,0 +1,62 @@
|
||||
# Dependabot configuration.
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
#
|
||||
# Grouping keeps the update/security-alert volume manageable (one PR per group
|
||||
# instead of one per advisory). Routine minor/patch bumps are batched; major
|
||||
# bumps still arrive individually so they get review.
|
||||
# Schedule: every Friday at 06:30 UTC / 12:00 IST.
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# TypeScript / npm workspace
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "friday"
|
||||
time: "06:30"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
npm-production:
|
||||
applies-to: version-updates
|
||||
dependency-type: "production"
|
||||
patterns: ["*"]
|
||||
update-types: ["minor", "patch"]
|
||||
npm-development:
|
||||
applies-to: version-updates
|
||||
dependency-type: "development"
|
||||
patterns: ["*"]
|
||||
update-types: ["minor", "patch"]
|
||||
npm-security:
|
||||
applies-to: security-updates
|
||||
patterns: ["*"]
|
||||
|
||||
# Python SDK and provider packages
|
||||
- package-ecosystem: "pip"
|
||||
directories:
|
||||
- "/python"
|
||||
- "/python/providers/*"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "friday"
|
||||
time: "06:30"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
pip-version:
|
||||
applies-to: version-updates
|
||||
patterns: ["*"]
|
||||
update-types: ["minor", "patch"]
|
||||
pip-security:
|
||||
applies-to: security-updates
|
||||
patterns: ["*"]
|
||||
|
||||
# GitHub Actions workflow pins
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "friday"
|
||||
time: "06:30"
|
||||
groups:
|
||||
github-actions:
|
||||
patterns: ["*"]
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Bump version, release-tag URLs, and per-platform sha256 in a Composio Homebrew formula.
|
||||
|
||||
Used by .github/workflows/cli.bump-homebrew-tap.yml. Reads inputs from env vars
|
||||
so the bash side has a clean interface; can be run locally for testing:
|
||||
|
||||
TAG=@composio/cli@0.2.29 VERSION=0.2.29 \\
|
||||
DARWIN_ARM=<sha> DARWIN_X86=<sha> LINUX_ARM=<sha> LINUX_X86=<sha> \\
|
||||
.github/scripts/bump-homebrew-formula.py path/to/Formula/composio.rb
|
||||
|
||||
Exits non-zero if any required env var is missing/invalid or if no edits land.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
REQUIRED_ENV = ("TAG", "VERSION", "DARWIN_ARM", "DARWIN_X86", "LINUX_ARM", "LINUX_X86")
|
||||
SHA256_RE = re.compile(r"[0-9a-f]{64}")
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
if len(argv) != 2:
|
||||
print(f"usage: {argv[0]} <formula-path>", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
path = argv[1]
|
||||
missing = [k for k in REQUIRED_ENV if not os.environ.get(k)]
|
||||
if missing:
|
||||
print(f"missing env vars: {', '.join(missing)}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
tag = os.environ["TAG"]
|
||||
version = os.environ["VERSION"]
|
||||
shas = {
|
||||
"darwin-aarch64": os.environ["DARWIN_ARM"],
|
||||
"darwin-x64": os.environ["DARWIN_X86"],
|
||||
"linux-aarch64": os.environ["LINUX_ARM"],
|
||||
"linux-x64": os.environ["LINUX_X86"],
|
||||
}
|
||||
for plat, sha in shas.items():
|
||||
if not SHA256_RE.fullmatch(sha):
|
||||
print(f"invalid sha for {plat}: {sha!r}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
with open(path) as f:
|
||||
src = f.read()
|
||||
|
||||
# Bump version line. Lambda avoids replacement-string backreference parsing
|
||||
# (e.g. a hostile tag containing \g<1>); same defense on tag/sha below.
|
||||
src, n_ver = re.subn(
|
||||
r'^(\s*version\s+)"[^"]+"',
|
||||
lambda m: f'{m.group(1)}"{version}"',
|
||||
src,
|
||||
count=1,
|
||||
flags=re.M,
|
||||
)
|
||||
|
||||
# Bump the release-tag segment of each download URL.
|
||||
src, n_url = re.subn(
|
||||
r"(releases/download/)@composio/cli@[^/]+(/composio-)",
|
||||
lambda m: f"{m.group(1)}{tag}{m.group(2)}",
|
||||
src,
|
||||
)
|
||||
|
||||
# Bump each sha256 by pairing it with the URL line directly above.
|
||||
updated_platforms: set[str] = set()
|
||||
|
||||
def repl_sha(match: re.Match[str]) -> str:
|
||||
url_line, sha_line = match.group(1), match.group(2)
|
||||
for plat, sha in shas.items():
|
||||
if f"composio-{plat}.zip" in url_line:
|
||||
updated_platforms.add(plat)
|
||||
return url_line + re.sub(
|
||||
r'"[0-9a-f]{64}"',
|
||||
lambda _m: f'"{sha}"',
|
||||
sha_line,
|
||||
)
|
||||
return match.group(0)
|
||||
|
||||
src, n_sha = re.subn(
|
||||
r'(url\s+"[^"]+"\s*\n)(\s*sha256\s+"[0-9a-f]{64}")',
|
||||
repl_sha,
|
||||
src,
|
||||
)
|
||||
|
||||
expected_platforms = set(shas)
|
||||
if (
|
||||
n_ver != 1
|
||||
or n_url != len(shas)
|
||||
or n_sha != len(shas)
|
||||
or updated_platforms != expected_platforms
|
||||
):
|
||||
print(
|
||||
"bump produced unexpected edits "
|
||||
f"(version={n_ver}, url={n_url}, sha={n_sha}, "
|
||||
f"updated_platforms={sorted(updated_platforms)})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
with open(path, "w") as f:
|
||||
f.write(src)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Check the validity of all actions used in the workflows.
|
||||
#
|
||||
# Example output:
|
||||
#
|
||||
# ❯ .github/scripts/check-action-repos.sh
|
||||
# Found 18 unique uses: entries
|
||||
# WARN: unrecognized uses format: ---
|
||||
# ERROR: ref not found (tag/branch/commit): peter-evans/create-pull-request@271a8d0340265f705b14b601f8cf3c8c27d2d6cf
|
||||
# ERROR: ref not found (tag/branch/commit): slackapi/slack-github-action@b4590ed38561d3cb7512cbee19cef0e0a6064ff1
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
fail=0
|
||||
|
||||
# Collect all `uses:` entries
|
||||
mapfile -t USES < <(
|
||||
yq -r '
|
||||
.jobs[]?.steps[]? |
|
||||
select(has("uses")) |
|
||||
.uses
|
||||
' .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null \
|
||||
| sed '/^null$/d' \
|
||||
| sort -u
|
||||
)
|
||||
|
||||
if [[ ${#USES[@]} -eq 0 ]]; then
|
||||
echo "No uses: entries found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found ${#USES[@]} unique uses: entries"
|
||||
|
||||
check_local() {
|
||||
local p="$1"
|
||||
if [[ ! -e "$p" ]]; then
|
||||
echo "ERROR: local action path does not exist: $p"
|
||||
return 1
|
||||
fi
|
||||
if [[ -d "$p" ]]; then
|
||||
if [[ -f "$p/action.yml" || -f "$p/action.yaml" ]]; then
|
||||
return 0
|
||||
fi
|
||||
echo "ERROR: local action dir missing action.yml/action.yaml: $p"
|
||||
return 1
|
||||
fi
|
||||
# If a file is used directly (rare), just ensure it exists
|
||||
return 0
|
||||
}
|
||||
|
||||
# For remote actions, verify:
|
||||
# - repo exists
|
||||
# - ref exists (tag/branch/sha)
|
||||
# - action.yml or action.yaml exists at path (or root) at that ref
|
||||
check_remote() {
|
||||
local spec="$1" # owner/repo/path@ref OR owner/repo@ref
|
||||
local before_at="${spec%@*}"
|
||||
local ref="${spec#*@}"
|
||||
|
||||
local owner_repo="${before_at%%/*}/${before_at#*/}"
|
||||
owner_repo="${owner_repo%%/*}/${owner_repo#*/}" # keep first two segments
|
||||
|
||||
local owner="${before_at%%/*}"
|
||||
local rest="${before_at#*/}"
|
||||
local repo="${rest%%/*}"
|
||||
|
||||
local path=""
|
||||
if [[ "$before_at" == */*/* ]]; then
|
||||
path="${before_at#"$owner/$repo/"}"
|
||||
fi
|
||||
|
||||
# 1) repo exists
|
||||
if ! gh api -H "Accept: application/vnd.github+json" "/repos/$owner/$repo" >/dev/null 2>&1; then
|
||||
echo "ERROR: repo not found or inaccessible: $owner/$repo (from $spec)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 2) ref exists (try tag, branch, then commit)
|
||||
if ! gh api -H "Accept: application/vnd.github+json" "/repos/$owner/$repo/git/ref/tags/$ref" >/dev/null 2>&1 \
|
||||
&& ! gh api -H "Accept: application/vnd.github+json" "/repos/$owner/$repo/git/ref/heads/$ref" >/dev/null 2>&1 \
|
||||
&& ! gh api -H "Accept: application/vnd.github+json" "/repos/$owner/$repo/commits/$ref" >/dev/null 2>&1
|
||||
then
|
||||
echo "ERROR: ref not found (tag/branch/commit): $spec"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 3) action metadata exists at path (or root)
|
||||
local base_path="${path}"
|
||||
if [[ -z "$base_path" ]]; then
|
||||
base_path=""
|
||||
fi
|
||||
|
||||
local try_paths=()
|
||||
if [[ -z "$base_path" ]]; then
|
||||
try_paths=("action.yml" "action.yaml")
|
||||
else
|
||||
try_paths=("$base_path/action.yml" "$base_path/action.yaml")
|
||||
fi
|
||||
|
||||
for p in "${try_paths[@]}"; do
|
||||
if gh api -H "Accept: application/vnd.github+json" \
|
||||
"/repos/$owner/$repo/contents/$p?ref=$ref" >/dev/null 2>&1
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "ERROR: action metadata not found at ref: $spec (looked for action.yml/action.yaml under '${base_path:-repo root}')"
|
||||
return 1
|
||||
}
|
||||
|
||||
for u in "${USES[@]}"; do
|
||||
# Ignore reusable workflows here: they also use `uses:` but are `./.github/workflows/x.yml` or `owner/repo/.github/workflows/x.yml@ref`
|
||||
# If you want to validate those too, add logic similar to check_local/check_remote for workflow files.
|
||||
if [[ "$u" == docker://* ]]; then
|
||||
echo "SKIP (docker image): $u"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$u" == ./* ]]; then
|
||||
if ! check_local "$u"; then fail=1; fi
|
||||
continue
|
||||
fi
|
||||
|
||||
# Remote action or reusable workflow reference (both look like owner/repo/...@ref)
|
||||
if [[ "$u" == *@* && "$u" == */* ]]; then
|
||||
if ! check_remote "$u"; then fail=1; fi
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "WARN: unrecognized uses format: $u"
|
||||
done
|
||||
|
||||
exit $fail
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Report orphaned CI plumbing — the GitHub Actions analogue of the "dead file"
|
||||
# problem knip/vulture catch for TS/Python:
|
||||
#
|
||||
# * composite actions under .github/actions/* that no workflow `uses:`
|
||||
# * reusable workflows (on: workflow_call) that nothing calls AND that have
|
||||
# no self-trigger (push/pull_request/schedule/...), i.e. truly unreachable
|
||||
#
|
||||
# Report-only: prints findings and always exits 0 so it never blocks CI on a
|
||||
# heuristic. Vet anything it prints by hand before deleting.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
workflows_dir="$repo_root/.github/workflows"
|
||||
actions_dir="$repo_root/.github/actions"
|
||||
|
||||
findings=0
|
||||
|
||||
note() {
|
||||
findings=$((findings + 1))
|
||||
echo " - $1"
|
||||
}
|
||||
|
||||
echo "### Orphaned CI check"
|
||||
echo ""
|
||||
echo "Composite actions with no callers:"
|
||||
|
||||
if [[ -d "$actions_dir" ]]; then
|
||||
for action_yml in "$actions_dir"/*/action.yml "$actions_dir"/*/action.yaml; do
|
||||
[[ -f "$action_yml" ]] || continue
|
||||
name="$(basename "$(dirname "$action_yml")")"
|
||||
# A caller references it as `uses: ./.github/actions/<name>`.
|
||||
if ! grep -rqs "uses:.*\.github/actions/$name\b" "$workflows_dir" "$actions_dir"; then
|
||||
note "action \`$name\` (.github/actions/$name) is referenced by no workflow"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
[[ $findings -eq 0 ]] && echo " _(none)_"
|
||||
|
||||
before_wf=$findings
|
||||
echo ""
|
||||
echo "Reusable workflows that are unreachable:"
|
||||
|
||||
for wf in "$workflows_dir"/*.yml "$workflows_dir"/*.yaml; do
|
||||
[[ -f "$wf" ]] || continue
|
||||
# Only reusable workflows are candidates.
|
||||
grep -qs "workflow_call:" "$wf" || continue
|
||||
base="$(basename "$wf")"
|
||||
# Called by another workflow?
|
||||
if grep -rqs "uses:.*\.github/workflows/$base\b" "$workflows_dir"; then
|
||||
continue
|
||||
fi
|
||||
# Self-triggering (has a real event trigger besides workflow_call)?
|
||||
if grep -Eqs "^[[:space:]]+(push|pull_request|schedule|workflow_dispatch|release|issues|issue_comment|repository_dispatch|merge_group):" "$wf"; then
|
||||
continue
|
||||
fi
|
||||
note "workflow \`$base\` exposes workflow_call but has no caller and no self-trigger"
|
||||
done
|
||||
[[ $findings -eq $before_wf ]] && echo " _(none)_"
|
||||
|
||||
echo ""
|
||||
if [[ $findings -eq 0 ]]; then
|
||||
echo "No orphaned CI plumbing found."
|
||||
else
|
||||
echo "Found $findings orphaned CI item(s) above — review before removing."
|
||||
fi
|
||||
|
||||
# Report-only.
|
||||
exit 0
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Create the GitHub Release as a DRAFT with every asset attached, or resume an existing
|
||||
# draft by re-uploading (--clobber). Refuse to mutate a tag that is already published.
|
||||
#
|
||||
# Drafts fire no `release: published` event and are excluded from `/releases/latest`, so no
|
||||
# anonymous consumer (install.sh, Homebrew, the redirect) can observe a release before its
|
||||
# assets are attached and verified. The release is only flipped to published by a later step.
|
||||
#
|
||||
# Inputs (env): RELEASE_TAG, RELEASE_NAME, CHECKOUT_REF, PRERELEASE, GH_TOKEN
|
||||
# BINARIES_DIR (optional, defaults to the CLI dist dir)
|
||||
set -euo pipefail
|
||||
|
||||
binaries_dir="${BINARIES_DIR:-ts/packages/cli/dist/binaries}"
|
||||
|
||||
flags=(
|
||||
--draft
|
||||
--title "$RELEASE_NAME"
|
||||
--target "$CHECKOUT_REF"
|
||||
--generate-notes
|
||||
)
|
||||
# Preserve prerelease status on the draft so betas stay prereleases once published.
|
||||
if [[ "$PRERELEASE" == "true" ]]; then
|
||||
flags+=(--prerelease)
|
||||
fi
|
||||
|
||||
# Idempotent for re-runs of a not-yet-published release: reuse an existing draft and clobber
|
||||
# its assets. Refuse to mutate a tag that is already published.
|
||||
if gh release view "$RELEASE_TAG" --json isDraft --jq '.isDraft' 2>/dev/null | grep -qx true; then
|
||||
echo "Draft $RELEASE_TAG already exists — re-uploading assets with --clobber"
|
||||
gh release upload "$RELEASE_TAG" \
|
||||
"$binaries_dir"/*.zip \
|
||||
"$binaries_dir/checksums.txt" --clobber
|
||||
elif gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
|
||||
# A red ❌ here is EXPECTED, not a bug, when two runs target the same tag (e.g. two quick CLI
|
||||
# version bumps): per-tag `concurrency` serializes them, the first publishes, and the second —
|
||||
# finding the tag already published — fails loudly here rather than silently clobbering a live
|
||||
# release. If you hit this, confirm the tag is genuinely published before re-running.
|
||||
echo "::error::Release $RELEASE_TAG is already published — refusing to mutate it. Investigate the publish path."
|
||||
exit 1
|
||||
else
|
||||
gh release create "$RELEASE_TAG" "${flags[@]}" \
|
||||
"$binaries_dir"/*.zip \
|
||||
"$binaries_dir/checksums.txt"
|
||||
fi
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Resolve the CLI release target and write its metadata to $GITHUB_OUTPUT for the
|
||||
# build/release jobs of build-cli-binaries.yml. Three modes:
|
||||
#
|
||||
# - push to `next` with a ts/packages/cli/package.json version bump → stable release
|
||||
# - push to `next` without a bump, or workflow_dispatch build-beta → rolling beta
|
||||
# - workflow_dispatch promote-stable <beta tag> → stable promotion
|
||||
#
|
||||
# Inputs (env): EVENT_NAME, ACTION_INPUT, BETA_TAG_INPUT, GITHUB_TOKEN,
|
||||
# REPOSITORY, RUN_NUMBER, COMMIT_SHA
|
||||
# Output: key=value lines appended to $GITHUB_OUTPUT
|
||||
set -euo pipefail
|
||||
|
||||
# Latest STABLE @composio/cli release tag, by true semver order (empty if none).
|
||||
#
|
||||
# A lexical sort is wrong here: "@composio/cli@0.2.9" sorts AFTER "0.2.10", so once a
|
||||
# patch reaches double digits `last` would pick the older release and beta versions
|
||||
# would regress. Parse the version triplet to numbers and sort numerically instead.
|
||||
latest_stable_tag() {
|
||||
gh release list \
|
||||
--repo "$REPOSITORY" \
|
||||
--exclude-drafts \
|
||||
--limit 1000 \
|
||||
--json tagName,isPrerelease \
|
||||
--jq '[.[]
|
||||
| select(.tagName | startswith("@composio/cli@"))
|
||||
| select(.isPrerelease == false)]
|
||||
| sort_by(.tagName | ltrimstr("@composio/cli@") | split(".") | map(tonumber))
|
||||
| last | .tagName // empty'
|
||||
}
|
||||
|
||||
# Echo the next "<major>.<minor>.<patch+1>" off the latest stable release, falling
|
||||
# back to the working-tree package.json when no stable release exists yet.
|
||||
next_beta_base_version() {
|
||||
local latest current
|
||||
latest=$(latest_stable_tag)
|
||||
if [[ -z "$latest" ]]; then
|
||||
echo "No stable @composio/cli release found, falling back to package.json" >&2
|
||||
current=$(node -p "require('./ts/packages/cli/package.json').version")
|
||||
else
|
||||
current=${latest#@composio/cli@}
|
||||
fi
|
||||
|
||||
local major minor patch
|
||||
IFS='.' read -r major minor patch <<<"$current"
|
||||
echo "${major}.${minor}.$((patch + 1))"
|
||||
}
|
||||
|
||||
emit_beta_target() {
|
||||
local next_version release_tag
|
||||
next_version=$(next_beta_base_version)
|
||||
release_tag="@composio/cli@${next_version}-beta.${RUN_NUMBER}"
|
||||
{
|
||||
echo "checkout_ref=${COMMIT_SHA}"
|
||||
echo "release_name=CLI Beta ${release_tag}"
|
||||
echo "release_tag=${release_tag}"
|
||||
echo "release_version=${next_version}"
|
||||
echo "prerelease=true"
|
||||
echo "make_latest=false"
|
||||
} >>"$GITHUB_OUTPUT"
|
||||
}
|
||||
|
||||
emit_stable_target() {
|
||||
local release_tag=$1 release_version=$2 checkout_ref=$3
|
||||
{
|
||||
echo "checkout_ref=${checkout_ref}"
|
||||
echo "release_name=CLI ${release_tag}"
|
||||
echo "release_tag=${release_tag}"
|
||||
echo "release_version=${release_version}"
|
||||
echo "prerelease=false"
|
||||
echo "make_latest=true"
|
||||
} >>"$GITHUB_OUTPUT"
|
||||
}
|
||||
|
||||
# ── Push to next ──
|
||||
if [[ "$EVENT_NAME" == "push" ]]; then
|
||||
current_version=$(node -p "require('./ts/packages/cli/package.json').version")
|
||||
previous_version=$(git show HEAD^:ts/packages/cli/package.json 2>/dev/null \
|
||||
| node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).version" 2>/dev/null || echo "")
|
||||
|
||||
# A version bump on push is a stable release; anything else is a rolling beta.
|
||||
if [[ -n "$previous_version" && "$current_version" != "$previous_version" ]]; then
|
||||
emit_stable_target "@composio/cli@${current_version}" "$current_version" "$COMMIT_SHA"
|
||||
else
|
||||
emit_beta_target
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── workflow_dispatch: build-beta ──
|
||||
if [[ "$EVENT_NAME" == "workflow_dispatch" && "$ACTION_INPUT" == "build-beta" ]]; then
|
||||
emit_beta_target
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── workflow_dispatch: promote-stable ──
|
||||
if [[ "$ACTION_INPUT" != "promote-stable" ]]; then
|
||||
echo "Unknown action: $ACTION_INPUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$BETA_TAG_INPUT" ]]; then
|
||||
echo "beta_tag input is required for promote-stable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
encoded_beta_tag=$(python3 -c 'import os, urllib.parse; print(urllib.parse.quote(os.environ["BETA_TAG_INPUT"], safe=""))')
|
||||
|
||||
release_json=$(curl -fsSL \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${REPOSITORY}/releases/tags/${encoded_beta_tag}")
|
||||
|
||||
is_prerelease=$(jq -r '.prerelease' <<<"$release_json")
|
||||
if [[ "$is_prerelease" != "true" ]]; then
|
||||
echo "Release ${BETA_TAG_INPUT} is not a beta prerelease" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$BETA_TAG_INPUT" =~ ^@composio/cli@([0-9]+\.[0-9]+\.[0-9]+)-beta\.[0-9]+$ ]]; then
|
||||
echo "Beta tag must match @composio/cli@<version>-beta.<number>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stable_version="${BASH_REMATCH[1]}"
|
||||
stable_tag="@composio/cli@${stable_version}"
|
||||
|
||||
# Refuse to re-promote an already-PUBLISHED stable release, but allow resuming an
|
||||
# existing DRAFT (a prior promote run that built assets but did not publish). The
|
||||
# REST `/releases/tags/{tag}` endpoint returns 404 for drafts, so use `gh release view`
|
||||
# — it resolves drafts by name and exposes `isDraft`.
|
||||
if isdraft=$(gh release view "$stable_tag" --json isDraft --jq '.isDraft' 2>/dev/null); then
|
||||
if [[ "$isdraft" == "true" ]]; then
|
||||
echo "Stable release ${stable_tag} exists as a draft — resuming (assets will be re-uploaded)."
|
||||
else
|
||||
echo "Stable release ${stable_tag} is already published" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
target_commitish=$(jq -r '.target_commitish' <<<"$release_json")
|
||||
if [[ -z "$target_commitish" || "$target_commitish" == "null" ]]; then
|
||||
echo "Beta release ${BETA_TAG_INPUT} does not expose target_commitish" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
emit_stable_target "$stable_tag" "$stable_version" "$target_commitish"
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Loud failure gate: assert the full canonical asset set is attached to the release AND fully
|
||||
# uploaded (state == "uploaded"). An asset can appear in the listing while still processing,
|
||||
# which is exactly how a release ends up serving 404s — so require state == "uploaded", with a
|
||||
# bounded retry while uploads settle.
|
||||
#
|
||||
# Inputs (env): RELEASE_TAG, GH_TOKEN
|
||||
# VERIFY_ATTEMPTS / VERIFY_SLEEP_SECONDS (optional, for fast tests)
|
||||
set -euo pipefail
|
||||
|
||||
# Keep this list in lock-step with the build matrix in build-cli-binaries.yml (4 platforms)
|
||||
# plus the skill bundle and checksums. Adding a platform to the matrix must extend this list.
|
||||
expected=(
|
||||
composio-linux-x64.zip
|
||||
composio-linux-aarch64.zip
|
||||
composio-darwin-x64.zip
|
||||
composio-darwin-aarch64.zip
|
||||
composio-skill.zip
|
||||
checksums.txt
|
||||
)
|
||||
|
||||
attempts="${VERIFY_ATTEMPTS:-10}"
|
||||
sleep_seconds="${VERIFY_SLEEP_SECONDS:-15}"
|
||||
|
||||
for attempt in $(seq 1 "$attempts"); do
|
||||
# Single snapshot per attempt — querying names and state separately would open a
|
||||
# time-of-check/time-of-use gap in the very gate meant to close one.
|
||||
payload=$(gh release view "$RELEASE_TAG" --json assets)
|
||||
mapfile -t uploaded < <(jq -r '.assets[] | select(.state == "uploaded") | .name' <<<"$payload" | sort)
|
||||
|
||||
missing=()
|
||||
for want in "${expected[@]}"; do
|
||||
printf '%s\n' "${uploaded[@]}" | grep -qxF "$want" || missing+=("$want")
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -eq 0 ]]; then
|
||||
echo "✅ All ${#expected[@]} expected assets present and uploaded."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Attempt ${attempt}/${attempts} — missing or not-yet-uploaded: ${missing[*]}"
|
||||
if [[ "$attempt" -eq "$attempts" ]]; then
|
||||
echo "::error::Release $RELEASE_TAG is incomplete — missing or not uploaded: ${missing[*]}"
|
||||
exit 1
|
||||
fi
|
||||
sleep "$sleep_seconds"
|
||||
done
|
||||
@@ -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