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
71 changes: 71 additions & 0 deletions .github/workflows/release-agent-e2e-bundle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Release Agent E2E Bundle

# Packages the agent e2e suite (e2e/) into a version-stamped, self-contained
# tarball and attaches it to the GitHub release, so downstream repos (e.g.
# orkes-io/orkes-conductor) can pin the e2e suite to the exact javascript-sdk
# release they run against. The bundle resolves
# @io-orkes/conductor-javascript at the same version from npm.
#
# Runs on the same release events as release.yml (npm publish). Packaging is
# purely static (no install), so it does not race the npm publish — the
# bundle just references the package version.
#
# Mirrors conductor-oss/java-sdk's release-agent-e2e-bundle.yml.

on:
release:
types: [released, prereleased]
workflow_dispatch:
inputs:
version:
description: "Version (e.g. 4.0.0-rc1) — a release vX.Y.Z must already exist to attach to"
required: true
type: string

permissions:
contents: write

jobs:
package-e2e-bundle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Determine version
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
else
TAG="${{ github.event.release.tag_name }}"
VERSION="${TAG#v}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Packaging agent e2e bundle for version: ${VERSION}"

- name: Package bundle
run: |
./scripts/package-e2e-bundle.sh --version "${{ steps.version.outputs.version }}"

- name: Validate bundle
run: |
./scripts/test-package-e2e-bundle.sh

- name: Generate SHA256 checksums
working-directory: scripts/e2e-bundle-dist
run: |
for f in *.tar.gz; do
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
echo " $(cat "${f}.sha256") ${f}"
done

- name: Upload bundle to GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.version.outputs.version }}"
gh release upload "v${VERSION}" \
scripts/e2e-bundle-dist/*.tar.gz \
scripts/e2e-bundle-dist/*.sha256 \
--repo "${{ github.repository }}" \
--clobber
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,6 @@ fabric.properties
# jest-junit output (unit CI uses reports/, agent e2e uses results/)
reports/
results/

# agent e2e bundle staging output (scripts/package-e2e-bundle.sh)
scripts/e2e-bundle-dist/
185 changes: 185 additions & 0 deletions scripts/package-e2e-bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
set -euo pipefail

# ── Package the agent e2e suite as a standalone bundle ───────────────────────
# Builds conductor-ai-e2e-typescript-<version>.tar.gz: a self-contained npm
# project carrying the e2e test sources (repo-root e2e/), pinned to the
# published @io-orkes/conductor-javascript@<version> package (no SDK source
# vendored).
#
# Downstream repos (e.g. orkes-io/orkes-conductor) download the bundle from
# the javascript-sdk GitHub release and run it against their own server build.
# This replaces the agentspan-sdk-e2e-typescript-* bundles formerly cut from
# agentspan-ai/agentspan — javascript-sdk is now the canonical home of these
# suites. Mirrors conductor-oss/java-sdk's conductor-ai-e2e/release/ export.
#
# Usage:
# ./scripts/package-e2e-bundle.sh --version 4.0.0-rc1 [--out DIR]
#
# Packaging is static (no compilation, no network) — the pinned version does
# not have to be on npm yet, so this can run before the publish job finishes.

HERE="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$HERE/.." && pwd)"

VERSION=""
OUT_DIR="$HERE/e2e-bundle-dist"

while [[ $# -gt 0 ]]; do
case "$1" in
--version) VERSION="$2"; shift 2 ;;
--out) OUT_DIR="$2"; shift 2 ;;
*) echo "ERROR: unknown arg '$1' (want --version X.Y.Z [--out DIR])" >&2; exit 1 ;;
esac
done

[[ -n "$VERSION" ]] || { echo "ERROR: --version is required" >&2; exit 1; }

NAME="conductor-ai-e2e-typescript-$VERSION"
STAGE="$OUT_DIR/$NAME"

echo "Packaging agent e2e bundle ($NAME)..."
rm -rf "$STAGE"
mkdir -p "$STAGE"

# Suites import the SDK by package name (@io-orkes/conductor-javascript/agents),
# so the sources copy over verbatim — the in-repo jest moduleNameMapper aliases
# to src/ simply don't exist here and imports resolve from node_modules.
cp -R "$REPO_ROOT/e2e" "$STAGE/e2e"

cat > "$STAGE/package.json" <<'EOF'
{
"name": "conductor-ai-e2e-typescript",
"version": "@VERSION@",
"private": true,
"scripts": {
"test": "jest --config jest.config.mjs --forceExit"
},
"dependencies": {
"@io-orkes/conductor-javascript": "@VERSION@"
},
"devDependencies": {
"@jest/globals": "^30.1.3",
"@types/node": "^22.0.0",
"jest": "^30.1.3",
"jest-junit": "^16.0.0",
"ts-jest": "^29.4.2",
"tsx": "^4.21.0",
"typescript": "^5.9.2",
"zod": "^3.25.76",
"zod-to-json-schema": "^3.23.5"
}
}
EOF

# Standalone jest config: same shape as the repo's jest.e2e.config.mjs but with
# NO moduleNameMapper SDK aliases — the package import must resolve from the
# installed npm package, proving the published artifact.
cat > "$STAGE/jest.config.mjs" <<'EOF'
export default {
preset: "ts-jest",
testMatch: ["**/e2e/**/*.test.ts"],
testTimeout: 60_000,
// Credential names are unique per suite; 3 workers keeps server load
// manageable (mirrors the in-repo jest.e2e.config.mjs).
maxWorkers: 3,
reporters: [
"default",
["jest-junit", { outputDirectory: "results", outputName: "junit-e2e.xml" }],
],
transform: {
"^.+\\.tsx?$": [
"ts-jest",
// isolatedModules puts ts-jest in transpile-only mode (as in the repo's
// root tsconfig): suites dynamically import optional provider SDKs
// (@anthropic-ai/sdk, openai, @anthropic-ai/mcp) inside try/catch and
// skip when absent — full type-check would hard-fail on those
// specifiers. Package-subpath resolution (…/agents) happens at runtime
// via jest's package-exports support.
{ tsconfig: { module: "commonjs", esModuleInterop: true, isolatedModules: true } },
],
},
};
EOF

cat > "$STAGE/run.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
# Runs the agent e2e suite against a live Conductor server with the agent
# runtime enabled (conductor-oss >= 3.32.0-rc.8, or orkes-conductor with
# agentspan.embedded=true).
#
# Required services (NOT started by this script):
# - Conductor server → AGENTSPAN_SERVER_URL (default http://localhost:8080/api)
# - mcp-testkit → MCP_TESTKIT_URL (default http://localhost:3001)
# Optional:
# - AGENTSPAN_LLM_MODEL (default openai/gpt-4o-mini); the provider API key
# must be configured on the SERVER — the suites never read it.
# - AGENTSPAN_CLI_PATH (default `agentspan` on PATH) — CLI suites skip if absent.
#
# Requires node >= 20. Usage: ./run.sh [extra jest args]
HERE="$(cd "$(dirname "$0")" && pwd)"
cd "$HERE"
npm install --no-audit --no-fund --loglevel=error
npx jest --config jest.config.mjs --forceExit "$@"
npx tsx e2e/generate-report.ts results/junit-e2e.xml results/report.html || true
echo "Results: $HERE/results/junit-e2e.xml (report.html alongside)"
EOF
chmod +x "$STAGE/run.sh"

cat > "$STAGE/README.md" <<'EOF'
# Conductor Agent SDK (typescript) — E2E suite @VERSION@

Self-contained end-to-end tests for the Conductor JavaScript/TypeScript agent
SDK, pinned to release **@VERSION@**. Resolves
`@io-orkes/conductor-javascript@@VERSION@` from npm — no SDK source is
vendored. Cut from
[conductor-oss/javascript-sdk](https://github.com/conductor-oss/javascript-sdk)
(`e2e/`); supersedes the `agentspan-sdk-e2e-typescript-*` bundles formerly
released from agentspan-ai/agentspan.

## Prerequisites (you provide these)

| Requirement | Env var | Default |
|-----------------------------------|------------------------|-----------------------------|
| node >= 20 | — | — |
| Conductor server w/ agent runtime | `AGENTSPAN_SERVER_URL` | `http://localhost:8080/api` |
| LLM model | `AGENTSPAN_LLM_MODEL` | `openai/gpt-4o-mini` |
| mcp-testkit (MCP suites) | `MCP_TESTKIT_URL` | `http://localhost:3001` |
| agentspan CLI (CLI suites) | `AGENTSPAN_CLI_PATH` | `agentspan` (on `PATH`) |

The server needs the agent runtime: conductor-oss `>= 3.32.0-rc.8`, or
orkes-conductor booted with `agentspan.embedded=true`. LLM provider API keys
(e.g. `OPENAI_API_KEY`) go to the **server** process, not this suite.
Suites that need an absent optional service (CLI, LangGraph wrappers) skip
rather than fail.

## Run

```bash
./run.sh # full suite
./run.sh -t 'suite1' # filter, plus any jest args
```

JUnit XML lands in `results/junit-e2e.xml`, HTML report in
`results/report.html`.

## Testing an unreleased SDK

```bash
npm install @io-orkes/conductor-javascript@<other-version-or-tarball>
npx jest --config jest.config.mjs --forceExit
```
EOF

# Stamp the version everywhere (skip binary fixtures).
find "$STAGE" -type f ! -name '*.png' ! -name '*.jpg' ! -name '*.jpeg' \
! -name '*.gif' ! -name '*.webp' ! -name '*.pdf' -print0 \
| xargs -0 sed -i.bak "s/@VERSION@/$VERSION/g"
find "$STAGE" -name '*.bak' -delete

mkdir -p "$OUT_DIR"
tar -czf "$OUT_DIR/$NAME.tar.gz" -C "$OUT_DIR" "$NAME"
rm -rf "$STAGE"

echo "OK: $OUT_DIR/$NAME.tar.gz"
73 changes: 73 additions & 0 deletions scripts/test-package-e2e-bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail

# ── Validator for package-e2e-bundle.sh ──────────────────────────────────────
# Builds the bundle at a throwaway version and asserts:
# - tarball exists and extracts to the expected dir
# - carries an executable, syntactically-valid run.sh + README
# - every e2e source/fixture from the repo made it in (file-count parity)
# - the SDK is pinned at the version, with no @VERSION@ placeholder left
# - package.json is valid JSON and the jest config has NO src aliases
# (imports must resolve from the installed npm package)
# All checks are static + deterministic (no network, no install, no server).
# Run: ./scripts/test-package-e2e-bundle.sh

HERE="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$HERE/.." && pwd)"
VERSION="9.9.9-test"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

fail() { echo "FAIL: $*" >&2; exit 1; }
pass() { echo " ok: $*"; }

"$HERE/package-e2e-bundle.sh" --version "$VERSION" --out "$WORK/dist" >/dev/null

NAME="conductor-ai-e2e-typescript-$VERSION"
TAR="$WORK/dist/$NAME.tar.gz"

[[ -f "$TAR" ]] || fail "tarball not produced ($TAR)"
pass "tarball produced"

mkdir -p "$WORK/x"
tar -xzf "$TAR" -C "$WORK/x"
ROOT="$WORK/x/$NAME"
[[ -d "$ROOT" ]] || fail "tarball does not extract to $NAME/"
pass "extracts to $NAME/"

[[ -f "$ROOT/run.sh" ]] || fail "missing run.sh"
[[ -x "$ROOT/run.sh" ]] || fail "run.sh not executable"
bash -n "$ROOT/run.sh" || fail "run.sh has a bash syntax error"
[[ -f "$ROOT/README.md" ]] || fail "missing README.md"
pass "run.sh + README present and valid"

# Every e2e file (sources, configs, fixtures) made it into the bundle.
SRC_COUNT="$(find "$REPO_ROOT/e2e" -type f | wc -l | tr -d ' ')"
BUNDLE_COUNT="$(find "$ROOT/e2e" -type f | wc -l | tr -d ' ')"
[[ "$SRC_COUNT" == "$BUNDLE_COUNT" ]] \
|| fail "source parity: repo e2e/ has $SRC_COUNT files, bundle has $BUNDLE_COUNT"
pass "all $SRC_COUNT e2e files present"

# SDK pinned at the packaged version, no unexpanded placeholders anywhere.
python3 -c "
import json, sys
p = json.load(open(sys.argv[1]))
assert p['dependencies']['@io-orkes/conductor-javascript'] == sys.argv[2], \
f'pin mismatch: {p[\"dependencies\"]}'
" "$ROOT/package.json" "$VERSION" \
|| fail "package.json does not pin @io-orkes/conductor-javascript@$VERSION"
if grep -rn '@VERSION@' "$ROOT" >/dev/null 2>&1; then
fail "unexpanded @VERSION@ placeholder left in bundle"
fi
pass "SDK pinned at $VERSION, no placeholders"

# The standalone jest config must resolve the SDK from node_modules — no
# moduleNameMapper aliases pointing package imports back at repo sources.
[[ -f "$ROOT/jest.config.mjs" ]] || fail "missing jest.config.mjs"
! grep -q "src/agents" "$ROOT/jest.config.mjs" \
|| fail "jest.config.mjs still aliases the in-repo SDK source"
grep -q "jest-junit" "$ROOT/jest.config.mjs" \
|| fail "jest.config.mjs missing junit reporter"
pass "jest config standalone (no src aliases), junit reporter wired"

echo "ALL CHECKS PASSED"
Loading