Skip to content

Commit 61444ef

Browse files
feat(e2e): export the agent e2e suite as a release bundle for downstream repos (#134)
Package the agent e2e suite (e2e/) into a self-contained, version-stamped tarball (conductor-ai-e2e-typescript-<version>.tar.gz) attached to GitHub releases, so downstream repos (e.g. orkes-io/orkes-conductor) can pin the suite to the exact javascript-sdk release they run against. Replaces the agentspan-sdk-e2e-typescript bundles formerly cut from agentspan-ai/agentspan. Mirrors conductor-oss/java-sdk's conductor-ai-e2e/release/ export. - scripts/package-e2e-bundle.sh: stages e2e/ verbatim (suites already import by package name) + standalone package.json pinning @io-orkes/conductor-javascript@<version> from npm + jest config with NO src aliases + run.sh + README - scripts/test-package-e2e-bundle.sh: static validator (file parity, version pin, no src aliases, junit reporter; no network) - .github/workflows/release-agent-e2e-bundle.yml: package + validate + sha256 + upload on release, same triggers as release.yml Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 891bd06 commit 61444ef

4 files changed

Lines changed: 332 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Release Agent E2E Bundle
2+
3+
# Packages the agent e2e suite (e2e/) into a version-stamped, self-contained
4+
# tarball and attaches it to the GitHub release, so downstream repos (e.g.
5+
# orkes-io/orkes-conductor) can pin the e2e suite to the exact javascript-sdk
6+
# release they run against. The bundle resolves
7+
# @io-orkes/conductor-javascript at the same version from npm.
8+
#
9+
# Runs on the same release events as release.yml (npm publish). Packaging is
10+
# purely static (no install), so it does not race the npm publish — the
11+
# bundle just references the package version.
12+
#
13+
# Mirrors conductor-oss/java-sdk's release-agent-e2e-bundle.yml.
14+
15+
on:
16+
release:
17+
types: [released, prereleased]
18+
workflow_dispatch:
19+
inputs:
20+
version:
21+
description: "Version (e.g. 4.0.0-rc1) — a release vX.Y.Z must already exist to attach to"
22+
required: true
23+
type: string
24+
25+
permissions:
26+
contents: write
27+
28+
jobs:
29+
package-e2e-bundle:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- name: Determine version
35+
id: version
36+
run: |
37+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
38+
VERSION="${{ inputs.version }}"
39+
else
40+
TAG="${{ github.event.release.tag_name }}"
41+
VERSION="${TAG#v}"
42+
fi
43+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
44+
echo "Packaging agent e2e bundle for version: ${VERSION}"
45+
46+
- name: Package bundle
47+
run: |
48+
./scripts/package-e2e-bundle.sh --version "${{ steps.version.outputs.version }}"
49+
50+
- name: Validate bundle
51+
run: |
52+
./scripts/test-package-e2e-bundle.sh
53+
54+
- name: Generate SHA256 checksums
55+
working-directory: scripts/e2e-bundle-dist
56+
run: |
57+
for f in *.tar.gz; do
58+
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
59+
echo " $(cat "${f}.sha256") ${f}"
60+
done
61+
62+
- name: Upload bundle to GitHub release
63+
env:
64+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65+
run: |
66+
VERSION="${{ steps.version.outputs.version }}"
67+
gh release upload "v${VERSION}" \
68+
scripts/e2e-bundle-dist/*.tar.gz \
69+
scripts/e2e-bundle-dist/*.sha256 \
70+
--repo "${{ github.repository }}" \
71+
--clobber

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,6 @@ fabric.properties
8989
# jest-junit output (unit CI uses reports/, agent e2e uses results/)
9090
reports/
9191
results/
92+
93+
# agent e2e bundle staging output (scripts/package-e2e-bundle.sh)
94+
scripts/e2e-bundle-dist/

scripts/package-e2e-bundle.sh

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# ── Package the agent e2e suite as a standalone bundle ───────────────────────
5+
# Builds conductor-ai-e2e-typescript-<version>.tar.gz: a self-contained npm
6+
# project carrying the e2e test sources (repo-root e2e/), pinned to the
7+
# published @io-orkes/conductor-javascript@<version> package (no SDK source
8+
# vendored).
9+
#
10+
# Downstream repos (e.g. orkes-io/orkes-conductor) download the bundle from
11+
# the javascript-sdk GitHub release and run it against their own server build.
12+
# This replaces the agentspan-sdk-e2e-typescript-* bundles formerly cut from
13+
# agentspan-ai/agentspan — javascript-sdk is now the canonical home of these
14+
# suites. Mirrors conductor-oss/java-sdk's conductor-ai-e2e/release/ export.
15+
#
16+
# Usage:
17+
# ./scripts/package-e2e-bundle.sh --version 4.0.0-rc1 [--out DIR]
18+
#
19+
# Packaging is static (no compilation, no network) — the pinned version does
20+
# not have to be on npm yet, so this can run before the publish job finishes.
21+
22+
HERE="$(cd "$(dirname "$0")" && pwd)"
23+
REPO_ROOT="$(cd "$HERE/.." && pwd)"
24+
25+
VERSION=""
26+
OUT_DIR="$HERE/e2e-bundle-dist"
27+
28+
while [[ $# -gt 0 ]]; do
29+
case "$1" in
30+
--version) VERSION="$2"; shift 2 ;;
31+
--out) OUT_DIR="$2"; shift 2 ;;
32+
*) echo "ERROR: unknown arg '$1' (want --version X.Y.Z [--out DIR])" >&2; exit 1 ;;
33+
esac
34+
done
35+
36+
[[ -n "$VERSION" ]] || { echo "ERROR: --version is required" >&2; exit 1; }
37+
38+
NAME="conductor-ai-e2e-typescript-$VERSION"
39+
STAGE="$OUT_DIR/$NAME"
40+
41+
echo "Packaging agent e2e bundle ($NAME)..."
42+
rm -rf "$STAGE"
43+
mkdir -p "$STAGE"
44+
45+
# Suites import the SDK by package name (@io-orkes/conductor-javascript/agents),
46+
# so the sources copy over verbatim — the in-repo jest moduleNameMapper aliases
47+
# to src/ simply don't exist here and imports resolve from node_modules.
48+
cp -R "$REPO_ROOT/e2e" "$STAGE/e2e"
49+
50+
cat > "$STAGE/package.json" <<'EOF'
51+
{
52+
"name": "conductor-ai-e2e-typescript",
53+
"version": "@VERSION@",
54+
"private": true,
55+
"scripts": {
56+
"test": "jest --config jest.config.mjs --forceExit"
57+
},
58+
"dependencies": {
59+
"@io-orkes/conductor-javascript": "@VERSION@"
60+
},
61+
"devDependencies": {
62+
"@jest/globals": "^30.1.3",
63+
"@types/node": "^22.0.0",
64+
"jest": "^30.1.3",
65+
"jest-junit": "^16.0.0",
66+
"ts-jest": "^29.4.2",
67+
"tsx": "^4.21.0",
68+
"typescript": "^5.9.2",
69+
"zod": "^3.25.76",
70+
"zod-to-json-schema": "^3.23.5"
71+
}
72+
}
73+
EOF
74+
75+
# Standalone jest config: same shape as the repo's jest.e2e.config.mjs but with
76+
# NO moduleNameMapper SDK aliases — the package import must resolve from the
77+
# installed npm package, proving the published artifact.
78+
cat > "$STAGE/jest.config.mjs" <<'EOF'
79+
export default {
80+
preset: "ts-jest",
81+
testMatch: ["**/e2e/**/*.test.ts"],
82+
testTimeout: 60_000,
83+
// Credential names are unique per suite; 3 workers keeps server load
84+
// manageable (mirrors the in-repo jest.e2e.config.mjs).
85+
maxWorkers: 3,
86+
reporters: [
87+
"default",
88+
["jest-junit", { outputDirectory: "results", outputName: "junit-e2e.xml" }],
89+
],
90+
transform: {
91+
"^.+\\.tsx?$": [
92+
"ts-jest",
93+
// isolatedModules puts ts-jest in transpile-only mode (as in the repo's
94+
// root tsconfig): suites dynamically import optional provider SDKs
95+
// (@anthropic-ai/sdk, openai, @anthropic-ai/mcp) inside try/catch and
96+
// skip when absent — full type-check would hard-fail on those
97+
// specifiers. Package-subpath resolution (…/agents) happens at runtime
98+
// via jest's package-exports support.
99+
{ tsconfig: { module: "commonjs", esModuleInterop: true, isolatedModules: true } },
100+
],
101+
},
102+
};
103+
EOF
104+
105+
cat > "$STAGE/run.sh" <<'EOF'
106+
#!/usr/bin/env bash
107+
set -euo pipefail
108+
# Runs the agent e2e suite against a live Conductor server with the agent
109+
# runtime enabled (conductor-oss >= 3.32.0-rc.8, or orkes-conductor with
110+
# agentspan.embedded=true).
111+
#
112+
# Required services (NOT started by this script):
113+
# - Conductor server → AGENTSPAN_SERVER_URL (default http://localhost:8080/api)
114+
# - mcp-testkit → MCP_TESTKIT_URL (default http://localhost:3001)
115+
# Optional:
116+
# - AGENTSPAN_LLM_MODEL (default openai/gpt-4o-mini); the provider API key
117+
# must be configured on the SERVER — the suites never read it.
118+
# - AGENTSPAN_CLI_PATH (default `agentspan` on PATH) — CLI suites skip if absent.
119+
#
120+
# Requires node >= 20. Usage: ./run.sh [extra jest args]
121+
HERE="$(cd "$(dirname "$0")" && pwd)"
122+
cd "$HERE"
123+
npm install --no-audit --no-fund --loglevel=error
124+
npx jest --config jest.config.mjs --forceExit "$@"
125+
npx tsx e2e/generate-report.ts results/junit-e2e.xml results/report.html || true
126+
echo "Results: $HERE/results/junit-e2e.xml (report.html alongside)"
127+
EOF
128+
chmod +x "$STAGE/run.sh"
129+
130+
cat > "$STAGE/README.md" <<'EOF'
131+
# Conductor Agent SDK (typescript) — E2E suite @VERSION@
132+
133+
Self-contained end-to-end tests for the Conductor JavaScript/TypeScript agent
134+
SDK, pinned to release **@VERSION@**. Resolves
135+
`@io-orkes/conductor-javascript@@VERSION@` from npm — no SDK source is
136+
vendored. Cut from
137+
[conductor-oss/javascript-sdk](https://github.com/conductor-oss/javascript-sdk)
138+
(`e2e/`); supersedes the `agentspan-sdk-e2e-typescript-*` bundles formerly
139+
released from agentspan-ai/agentspan.
140+
141+
## Prerequisites (you provide these)
142+
143+
| Requirement | Env var | Default |
144+
|-----------------------------------|------------------------|-----------------------------|
145+
| node >= 20 | — | — |
146+
| Conductor server w/ agent runtime | `AGENTSPAN_SERVER_URL` | `http://localhost:8080/api` |
147+
| LLM model | `AGENTSPAN_LLM_MODEL` | `openai/gpt-4o-mini` |
148+
| mcp-testkit (MCP suites) | `MCP_TESTKIT_URL` | `http://localhost:3001` |
149+
| agentspan CLI (CLI suites) | `AGENTSPAN_CLI_PATH` | `agentspan` (on `PATH`) |
150+
151+
The server needs the agent runtime: conductor-oss `>= 3.32.0-rc.8`, or
152+
orkes-conductor booted with `agentspan.embedded=true`. LLM provider API keys
153+
(e.g. `OPENAI_API_KEY`) go to the **server** process, not this suite.
154+
Suites that need an absent optional service (CLI, LangGraph wrappers) skip
155+
rather than fail.
156+
157+
## Run
158+
159+
```bash
160+
./run.sh # full suite
161+
./run.sh -t 'suite1' # filter, plus any jest args
162+
```
163+
164+
JUnit XML lands in `results/junit-e2e.xml`, HTML report in
165+
`results/report.html`.
166+
167+
## Testing an unreleased SDK
168+
169+
```bash
170+
npm install @io-orkes/conductor-javascript@<other-version-or-tarball>
171+
npx jest --config jest.config.mjs --forceExit
172+
```
173+
EOF
174+
175+
# Stamp the version everywhere (skip binary fixtures).
176+
find "$STAGE" -type f ! -name '*.png' ! -name '*.jpg' ! -name '*.jpeg' \
177+
! -name '*.gif' ! -name '*.webp' ! -name '*.pdf' -print0 \
178+
| xargs -0 sed -i.bak "s/@VERSION@/$VERSION/g"
179+
find "$STAGE" -name '*.bak' -delete
180+
181+
mkdir -p "$OUT_DIR"
182+
tar -czf "$OUT_DIR/$NAME.tar.gz" -C "$OUT_DIR" "$NAME"
183+
rm -rf "$STAGE"
184+
185+
echo "OK: $OUT_DIR/$NAME.tar.gz"

scripts/test-package-e2e-bundle.sh

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# ── Validator for package-e2e-bundle.sh ──────────────────────────────────────
5+
# Builds the bundle at a throwaway version and asserts:
6+
# - tarball exists and extracts to the expected dir
7+
# - carries an executable, syntactically-valid run.sh + README
8+
# - every e2e source/fixture from the repo made it in (file-count parity)
9+
# - the SDK is pinned at the version, with no @VERSION@ placeholder left
10+
# - package.json is valid JSON and the jest config has NO src aliases
11+
# (imports must resolve from the installed npm package)
12+
# All checks are static + deterministic (no network, no install, no server).
13+
# Run: ./scripts/test-package-e2e-bundle.sh
14+
15+
HERE="$(cd "$(dirname "$0")" && pwd)"
16+
REPO_ROOT="$(cd "$HERE/.." && pwd)"
17+
VERSION="9.9.9-test"
18+
WORK="$(mktemp -d)"
19+
trap 'rm -rf "$WORK"' EXIT
20+
21+
fail() { echo "FAIL: $*" >&2; exit 1; }
22+
pass() { echo " ok: $*"; }
23+
24+
"$HERE/package-e2e-bundle.sh" --version "$VERSION" --out "$WORK/dist" >/dev/null
25+
26+
NAME="conductor-ai-e2e-typescript-$VERSION"
27+
TAR="$WORK/dist/$NAME.tar.gz"
28+
29+
[[ -f "$TAR" ]] || fail "tarball not produced ($TAR)"
30+
pass "tarball produced"
31+
32+
mkdir -p "$WORK/x"
33+
tar -xzf "$TAR" -C "$WORK/x"
34+
ROOT="$WORK/x/$NAME"
35+
[[ -d "$ROOT" ]] || fail "tarball does not extract to $NAME/"
36+
pass "extracts to $NAME/"
37+
38+
[[ -f "$ROOT/run.sh" ]] || fail "missing run.sh"
39+
[[ -x "$ROOT/run.sh" ]] || fail "run.sh not executable"
40+
bash -n "$ROOT/run.sh" || fail "run.sh has a bash syntax error"
41+
[[ -f "$ROOT/README.md" ]] || fail "missing README.md"
42+
pass "run.sh + README present and valid"
43+
44+
# Every e2e file (sources, configs, fixtures) made it into the bundle.
45+
SRC_COUNT="$(find "$REPO_ROOT/e2e" -type f | wc -l | tr -d ' ')"
46+
BUNDLE_COUNT="$(find "$ROOT/e2e" -type f | wc -l | tr -d ' ')"
47+
[[ "$SRC_COUNT" == "$BUNDLE_COUNT" ]] \
48+
|| fail "source parity: repo e2e/ has $SRC_COUNT files, bundle has $BUNDLE_COUNT"
49+
pass "all $SRC_COUNT e2e files present"
50+
51+
# SDK pinned at the packaged version, no unexpanded placeholders anywhere.
52+
python3 -c "
53+
import json, sys
54+
p = json.load(open(sys.argv[1]))
55+
assert p['dependencies']['@io-orkes/conductor-javascript'] == sys.argv[2], \
56+
f'pin mismatch: {p[\"dependencies\"]}'
57+
" "$ROOT/package.json" "$VERSION" \
58+
|| fail "package.json does not pin @io-orkes/conductor-javascript@$VERSION"
59+
if grep -rn '@VERSION@' "$ROOT" >/dev/null 2>&1; then
60+
fail "unexpanded @VERSION@ placeholder left in bundle"
61+
fi
62+
pass "SDK pinned at $VERSION, no placeholders"
63+
64+
# The standalone jest config must resolve the SDK from node_modules — no
65+
# moduleNameMapper aliases pointing package imports back at repo sources.
66+
[[ -f "$ROOT/jest.config.mjs" ]] || fail "missing jest.config.mjs"
67+
! grep -q "src/agents" "$ROOT/jest.config.mjs" \
68+
|| fail "jest.config.mjs still aliases the in-repo SDK source"
69+
grep -q "jest-junit" "$ROOT/jest.config.mjs" \
70+
|| fail "jest.config.mjs missing junit reporter"
71+
pass "jest config standalone (no src aliases), junit reporter wired"
72+
73+
echo "ALL CHECKS PASSED"

0 commit comments

Comments
 (0)