Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions .github/workflows/auto_generate_connector_template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
name: Auto-Generate Connector Template

on:
workflow_call:
inputs:
openapi-url:
description: "URL of the OpenAPI specification (JSON/YAML)"
required: false
type: string
ballerina-version:
description: "Ballerina version to use"
required: false
type: string
default: "nightly"
license-file:
description: "Path to license file relative to docs directory"
required: false
type: string
default: "license.txt"
quiet-mode:
description: "Enable quiet mode"
required: false
type: boolean
default: true

jobs:
auto-generate:
name: Auto-generate connector
runs-on: ubuntu-22.04
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
token: ${{ secrets.BALLERINA_BOT_TOKEN }}

- name: Setup Ballerina
uses: ballerina-platform/setup-ballerina@v1.1.3
with:
version: ${{ inputs.ballerina-version }}

- name: Verify API Key
run: |
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "Error: ANTHROPIC_API_KEY secret is not set"
echo "AI-powered features require Claude API access"
echo "Please configure the ANTHROPIC_API_KEY secret in your repository and try again"
exit 1
fi
echo "API key is configured"

- name: Clone Connector Automator
run: |
echo "Cloning connector automator tool from ballerina-library..."
git clone https://github.com/ballerina-platform/ballerina-library.git temp-library
cp -r temp-library/connector_automator ./
rm -rf temp-library
echo "Connector automator cloned successfully"

- name: Download OpenAPI Specification
if: inputs.openapi-url
working-directory: docs/spec
run: |
echo "Downloading openapi spec from provided URL..."
if [[ "${{ inputs.openapi-url }}" == *.json ]]; then
rm -f openapi.json
wget -O openapi.json "${{ inputs.openapi-url }}"
elif [[ "${{ inputs.openapi-url }}" == *.yaml || "${{ inputs.openapi-url }}" == *.yml ]]; then
rm -f openapi.yaml openapi.yml
wget -O openapi.yaml "${{ inputs.openapi-url }}"
else
echo "Unsupported file extension: ${{ inputs.openapi-url }}"
exit 1
fi

- name: Read File Extension
working-directory: docs/spec
run: |
file=$(echo openapi.*)
if [[ "$file" == "openapi.*" ]]; then
echo "Error: No OpenAPI specification found in docs/spec/"
echo "Either provide openapi-url input or commit a spec file to docs/spec/"
echo "Supported files: openapi.json, openapi.yaml, openapi.yml"
exit 1
fi
ext="${file##*.}"
echo "EXTENSION=$ext" >> "$GITHUB_ENV"
echo "SPEC_FILE=$file" >> "$GITHUB_ENV"
echo "Found OpenAPI spec: $file"

- name: Create Branch
run: |
git checkout -b auto-generate-connector
echo "Created branch: auto-generate-connector"

- name: Run Full Connector Generation Pipeline
run: |
cd connector_automator
SPEC_PATH="../docs/spec/${{ env.SPEC_FILE }}"
OUTPUT_PATH="../."

echo "Running full connector generation pipeline..."
echo "Spec path : $SPEC_PATH"
echo "Output path: $OUTPUT_PATH"

# Build pipeline arguments
PIPELINE_ARGS=("$SPEC_PATH" "$OUTPUT_PATH" "yes")

if [ "${{ inputs.quiet-mode }}" = "true" ]; then
PIPELINE_ARGS+=("quiet")
fi

# Run the complete pipeline
bal run -- pipeline "${PIPELINE_ARGS[@]}"

echo "Full connector generation pipeline completed"

- name: Cleanup and Organize Spec Files
run: |
cd docs/spec
echo "Cleaning up OpenAPI spec files..."

# List all files before cleanup
echo "Files before cleanup:"
ls -la

# Rename original spec to original_openapi.*
if [ -f "${{ env.SPEC_FILE }}" ]; then
mv "${{ env.SPEC_FILE }}" "original_openapi.${{ env.EXTENSION }}"
echo "Renamed ${{ env.SPEC_FILE }} to original_openapi.${{ env.EXTENSION }}"
fi

# Rename final aligned spec to openapi.json
if [ -f "aligned_ballerina_openapi.json" ]; then
mv "aligned_ballerina_openapi.json" "openapi.json"
echo "Renamed aligned_ballerina_openapi.json to openapi.json"
fi

# Remove intermediate files (flattened_openapi.*)
rm -f flattened_openapi.json flattened_openapi.yaml flattened_openapi.yml
echo "Removed intermediate flattened files"

# Remove any other aligned files if they exist
rm -f aligned_ballerina_openapi.yaml aligned_ballerina_openapi.yml
echo "Removed remaining aligned files"

# List files after cleanup
echo "Files after cleanup:"
ls -la

echo "OpenAPI spec file cleanup completed"

- name: Cleanup Connector Automator Tool
run: |
echo "Removing connector automator tool from repository..."
rm -rf connector_automator
echo "Connector automator tool removed"

- name: Configure Git Identity
run: |
git config user.name "ballerina-bot"
git config user.email "ballerina-bot@ballerina.org"

- name: Commit Generated Files
id: commitFiles
run: |
# Build commit message
COMMIT_MSG="[AUTOMATED] Auto-generate connector from OpenAPI spec

Complete connector generation using full pipeline with all components.
Generated using Ballerina with AI-powered automation."

# Add all changes
git add .

# Check if there are changes to commit
if git diff --cached --quiet; then
echo "hasChanged=false" >> $GITHUB_OUTPUT
echo "No changes to commit"
else
echo "$COMMIT_MSG" | git commit -F -
echo "hasChanged=true" >> $GITHUB_OUTPUT
echo "Changes committed successfully"
fi

- name: Push Results
if: ${{ steps.commitFiles.outputs.hasChanged == 'true' }}
run: git push origin auto-generate-connector
env:
GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }}

- name: Create Pull Request
id: createPR
if: ${{ steps.commitFiles.outputs.hasChanged == 'true' }}
run: |
prUrl=$(gh pr create \
--title "[Automated] Auto-generate connector from OpenAPI specification" \
--body $'## Auto-Generated Connector from OpenAPI Specification\n\n**WARNING: This PR contains AI-generated content. Please review thoroughly before merging.**\n\n### Generation Details\n- **Ballerina Version**: ${{ inputs.ballerina-version }}\n- \n### Generated Components\n- Complete connector generation using full pipeline\n- All components: sanitization, client generation, examples generation, tests generation, documentation\n\n### AI Generation Disclaimer\nThis connector was generated using AI technology. Manual validation is essential to ensure correctness, security, and compatibility with the target API.\n\nPlease do not merge this PR without thorough review and testing.' \
--base ${{ github.ref }} \
--head auto-generate-connector)
echo "prUrl=$prUrl" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }}
5 changes: 0 additions & 5 deletions connector_automator/main.bal
Original file line number Diff line number Diff line change
Expand Up @@ -493,11 +493,6 @@ function runFullPipeline(string... args) returns error? {
io:println("5. Generate tests");
io:println("6. Generate documentation");

if !getUserConfirmation("\nProceed with full pipeline?") {
io:println("Operation cancelled by user.");
return;
}

decimal sanitizationCost = 0.0d;
decimal exampleGenCost = 0.0d;
decimal testGenCost = 0.0d;
Expand Down
12 changes: 11 additions & 1 deletion connector_automator/modules/doc_generator/ai_generator.bal
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import connector_automator.utils;

import ballerina/file;
import ballerina/io;
import ballerina/lang.regexp;

const string TEMPLATES_PATH = "/home/hansika/dev/connector_automation/connector_automator/modules/doc_generator/templates";

Expand Down Expand Up @@ -117,7 +118,7 @@ public function generateIndividualExampleReadmes(string connectorPath) returns e

foreach file:MetaData example in examples {
if example.dir {
string exampleDirName = example.absPath.substring(examplesPath.length() + 1);
string exampleDirName = extractDirectoryName(example.absPath);
string exampleDirPath = examplesPath + "/" + exampleDirName;

error? result = generateSingleExampleReadme(example.absPath, exampleDirName, metadata);
Expand All @@ -137,6 +138,15 @@ public function generateIndividualExampleReadmes(string connectorPath) returns e
}
}

function extractDirectoryName(string fullPath) returns string {
// Get the last segment of the path
string[] pathParts = regexp:split(re `/`, fullPath);
if pathParts.length() > 0 {
return pathParts[pathParts.length() - 1];
}
return fullPath;
}

function generateSingleExampleReadme(string examplePath, string exampleDirName, ConnectorMetadata metadata) returns error? {
// Read all .bal files in the example directory
ExampleData exampleData = check analyzeExampleDirectory(examplePath, exampleDirName);
Expand Down