Community Hub→ Via GitLab CI

Publishing via GitLab CI

If your team uses GitLab as the primary source of truth, you can set up a GitLab CI pipeline to automatically publish your function to the Aerostack Hub on every push to main or on a tagged release.


How It Works

Developer pushes to GitLab


GitLab CI pipeline triggers


Reads aerostack.json + source files


Calls POST /api/community/functions/ci/publish
  with your AEROSTACK_API_KEY CI variable


Function is live on hub.aerostack.dev ✅

No Aerostack-specific runner or plugin is required — the pipeline uses plain curl and jq which are available in any standard CI image.


Step-by-Step Setup

Get your Aerostack API key

Go to aerocall.ai/account/keys and create or copy an account key (prefix ac_).

Add the CI variable to GitLab

In your GitLab project:

  1. Go to SettingsCI/CDVariables
  2. Click Add variable
  3. Key: AEROSTACK_API_KEY
  4. Value: your ac_secret_xxxx key
  5. Type: Variable (not File)
  6. Enable Mask variable (so it’s hidden in logs)
  7. Optionally enable Protect variable to restrict it to protected branches only

Structure your repository

    • .gitlab-ci.yml
    • aerostack.json
    • index.ts
    • README.md

Add the pipeline file

Create .gitlab-ci.yml at the root of your function repository:

.gitlab-ci.yml
stages:
  - publish
 
publish-to-aerostack:
  stage: publish
  image: node:20-alpine
  before_script:
    - apk add --no-cache curl jq
  script:
    - |
      # Read aerostack.json
      CONFIG=$(cat aerostack.json)
      ENTRYPOINT=$(echo "$CONFIG" | jq -r '.entrypoint // "index.ts"')
 
      # Read source file and escape for JSON
      CODE=$(cat "$ENTRYPOINT" | jq -Rs .)
 
      # Build payload: merge config + code + publish flag
      PAYLOAD=$(jq -n \
        --argjson config "$CONFIG" \
        --argjson code "$CODE" \
        '$config + {code: $code, publish: true}')
 
      echo "Publishing $(echo "$CONFIG" | jq -r '.name') v$(echo "$CONFIG" | jq -r '.version')..."
 
      # Call Aerostack publish API
      RESPONSE=$(curl -s -w "\n%{http_code}" \
        -X POST "https://api.aerocall.ai/api/community/functions/ci/publish" \
        -H "X-API-Key: $AEROSTACK_API_KEY" \
        -H "Content-Type: application/json" \
        -d "$PAYLOAD")
 
      HTTP_CODE=$(echo "$RESPONSE" | tail -1)
      BODY=$(echo "$RESPONSE" | head -1)
 
      echo "Response: $BODY"
 
      if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
        echo "❌ Publish failed with HTTP $HTTP_CODE"
        exit 1
      fi
 
      echo "✅ Published successfully!"
      echo "$BODY" | jq .
  only:
    - main

Full Working Example

Complete .gitlab-ci.yml with staging validation + production publish, based on environment:

.gitlab-ci.yml
stages:
  - validate
  - publish
 
variables:
  AEROSTACK_API: "https://api.aerocall.ai/api/community/functions/ci/publish"
 
# ── Validate on all feature branches ──────────────────────────────────────────
validate-function:
  stage: validate
  image: node:20-alpine
  before_script:
    - apk add --no-cache jq
  script:
    - |
      echo "Validating aerostack.json..."
      
      # Check required fields exist
      for field in name version description category language entrypoint; do
        VALUE=$(cat aerostack.json | jq -r ".$field")
        if [ "$VALUE" = "null" ] || [ -z "$VALUE" ]; then
          echo "❌ Missing required field: $field"
          exit 1
        fi
      done
      
      ENTRYPOINT=$(cat aerostack.json | jq -r '.entrypoint')
      if [ ! -f "$ENTRYPOINT" ]; then
        echo "❌ Entrypoint file not found: $ENTRYPOINT"
        exit 1
      fi
      
      echo "✅ aerostack.json is valid"
      echo "Function: $(cat aerostack.json | jq -r '.name') v$(cat aerostack.json | jq -r '.version')"
  except:
    - main
    - tags
 
# ── Publish draft on push to main ─────────────────────────────────────────────
publish-draft:
  stage: publish
  image: node:20-alpine
  before_script:
    - apk add --no-cache curl jq
  script:
    - |
      CONFIG=$(cat aerostack.json)
      ENTRYPOINT=$(echo "$CONFIG" | jq -r '.entrypoint // "index.ts"')
      CODE=$(cat "$ENTRYPOINT" | jq -Rs .)
 
      # Publish as draft — review on Hub before making public
      PAYLOAD=$(jq -n \
        --argjson config "$CONFIG" \
        --argjson code "$CODE" \
        '$config + {code: $code, publish: false}')
 
      RESPONSE=$(curl -s -w "\n%{http_code}" \
        -X POST "$AEROSTACK_API" \
        -H "X-API-Key: $AEROSTACK_API_KEY" \
        -H "Content-Type: application/json" \
        -d "$PAYLOAD")
 
      HTTP_CODE=$(echo "$RESPONSE" | tail -1)
      echo "$(echo "$RESPONSE" | head -1)" | jq .
 
      [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || exit 1
      echo "📝 Published as draft. Go to hub.aerostack.dev/my-functions to review."
  only:
    - main
 
# ── Publish live on git tag ────────────────────────────────────────────────────
publish-release:
  stage: publish
  image: node:20-alpine
  before_script:
    - apk add --no-cache curl jq
  script:
    - |
      VERSION="${CI_COMMIT_TAG#v}"
      jq --arg v "$VERSION" '.version = $v' aerostack.json > tmp.json && mv tmp.json aerostack.json
 
      CONFIG=$(cat aerostack.json)
      ENTRYPOINT=$(echo "$CONFIG" | jq -r '.entrypoint // "index.ts"')
      CODE=$(cat "$ENTRYPOINT" | jq -Rs .)
 
      PAYLOAD=$(jq -n \
        --argjson config "$CONFIG" \
        --argjson code "$CODE" \
        '$config + {code: $code, publish: true}')
 
      RESPONSE=$(curl -s -w "\n%{http_code}" \
        -X POST "$AEROSTACK_API" \
        -H "X-API-Key: $AEROSTACK_API_KEY" \
        -H "Content-Type: application/json" \
        -d "$PAYLOAD")
 
      HTTP_CODE=$(echo "$RESPONSE" | tail -1)
      echo "$(echo "$RESPONSE" | head -1)" | jq .
 
      [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || exit 1
      echo "🚀 Published $VERSION to Aerostack Hub!"
  only:
    - tags

Things to Keep in Mind

Protect your CI variable

Always mark AEROSTACK_API_KEY as a masked variable in GitLab. If you also protect it, it will only be available on protected branches and tags — a good default for production publishing.

Version must always be bumped

The Aerostack platform stores a snapshot for every published version. If you try to publish the same version string twice it will still succeed but update the snapshot in place. Consumers who installed the old version will not be automatically updated — version bumping is how you signal an intentional new release.

Draft vs live on push

The publish-draft job above sends publish: false, so a push to main creates/updates the function code as a draft. Only a tagged release (via publish-release) makes it live. This separation is intentional — it lets you review on the Hub UI before going public.

If you prefer push to main = immediately live, change publish: false to publish: true in the publish-draft job.

Multi-function monorepo

If your GitLab repo contains multiple function directories, use the changes keyword to only trigger publishing for modified functions:

publish-rate-limiter:
  stage: publish
  script:
    - cd functions/rate-limiter && <curl command here>
  only:
    changes:
      - functions/rate-limiter/**
    refs:
      - main

GitLab environments — You can use GitLab’s built-in environment keyword on your publish job to get deployment tracking, rollback history, and approval gates directly in GitLab’s UI. This works independently of Aerostack’s version history.