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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ jobs:
- name: Install deps
run: pnpm install --frozen-lockfile

# Build first so downstream packages can resolve `@agentworkforce/*`
# types from the built `dist/` — the CLI's typecheck/lint depend on
# the router's emitted .d.ts files.
- name: Build
run: pnpm -r run build

- name: Lint
run: pnpm run lint

Expand Down
192 changes: 156 additions & 36 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,37 @@ name: Publish Package
on:
workflow_dispatch:
inputs:
package:
description: 'Which package(s) to publish'
required: true
default: cli
type: choice
options:
- all
- workload-router
- harness-kit
- cli
version:
description: 'Version bump type'
description: 'Version bump type (ignored if custom_version is set)'
required: true
default: patch
type: choice
options:
- patch
- minor
- major
- prepatch
- preminor
- premajor
- prerelease
- none
custom_version:
description: 'Exact version (e.g. 0.1.0). Overrides version type when set.'
required: false
prerelease_id:
description: 'Prerelease identifier for pre* bumps (e.g. "next", "beta")'
required: false
default: next
tag:
description: 'npm dist-tag'
required: true
Expand All @@ -22,8 +43,9 @@ on:
- latest
- next
- beta
- alpha
dry_run:
description: 'Dry run only (no publish)'
description: 'Dry run (no actual publish, no version commit, no git tag)'
required: true
default: false
type: boolean
Expand All @@ -32,70 +54,168 @@ permissions:
contents: write
id-token: write

concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false

jobs:
publish-workload-router:
publish:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22.14.0'
registry-url: 'https://registry.npmjs.org'

- name: Setup pnpm
uses: pnpm/action-setup@v4
cache: 'pnpm'

- name: Install deps
run: pnpm install --frozen-lockfile

- name: Build package
run: pnpm --filter @agentworkforce/workload-router build
# Build + test everything regardless of which package is being released —
# the CLI depends on harness-kit which depends on workload-router via
# workspace:*. `pnpm publish` rewrites those specs to concrete versions
# at pack time, and we want every package's dist/ to be fresh when that
# happens.
- name: Build workspace
run: pnpm -r run build

- name: Test package
run: pnpm --filter @agentworkforce/workload-router test
- name: Run tests
run: pnpm -r run test

- name: Bump version
- name: Resolve target packages (dep order)
id: targets
run: |
case "${{ github.event.inputs.package }}" in
all)
# Must be in dependency order: router → harness-kit → cli.
echo "packages=workload-router harness-kit cli" >> "$GITHUB_OUTPUT"
;;
workload-router|harness-kit|cli)
echo "packages=${{ github.event.inputs.package }}" >> "$GITHUB_OUTPUT"
;;
*)
echo "Unknown package: ${{ github.event.inputs.package }}" >&2
exit 1
;;
esac

- name: Bump versions
id: bump
working-directory: packages/workload-router
run: |
npm version ${{ github.event.inputs.version }} --no-git-tag-version
NEW_VERSION=$(node -p "require('./package.json').version")
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"

- name: Commit version bump
VERSIONS=""
CUSTOM='${{ github.event.inputs.custom_version }}'
BUMP='${{ github.event.inputs.version }}'
PREID='${{ github.event.inputs.prerelease_id }}'
for pkg in ${{ steps.targets.outputs.packages }}; do
pushd "packages/$pkg" > /dev/null
if [ -n "$CUSTOM" ]; then
npm version "$CUSTOM" --no-git-tag-version --allow-same-version
elif [ "$BUMP" = "none" ]; then
: # keep existing version (useful for first publish or re-publish)
elif [[ "$BUMP" == pre* ]]; then
npm version "$BUMP" --no-git-tag-version --preid="$PREID"
else
npm version "$BUMP" --no-git-tag-version
fi
NEW=$(node -p "require('./package.json').version")
VERSIONS+=" $pkg:$NEW"
popd > /dev/null
done
echo "versions=${VERSIONS# }" >> "$GITHUB_OUTPUT"

- name: Commit version bumps
if: ${{ github.event.inputs.dry_run != 'true' && (github.event.inputs.version != 'none' || github.event.inputs.custom_version != '') }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add packages/workload-router/package.json
git commit -m "chore(release): @agentworkforce/workload-router v${{ steps.bump.outputs.version }}"

git add packages/*/package.json
if git diff --cached --quiet; then
echo "No version changes to commit."
else
MSG="chore(release):"
for entry in ${{ steps.bump.outputs.versions }}; do
pkg="${entry%%:*}"
version="${entry##*:}"
MSG+=" @agentworkforce/$pkg@$version"
done
git commit -m "$MSG"
fi

# npm >= 11.5.1 is required for the OIDC trusted-publisher flow.
- name: Install latest npm
run: npm install -g npm@latest

- name: Publish (dry run)
if: ${{ github.event.inputs.dry_run == 'true' }}
working-directory: packages/workload-router
run: npm publish --dry-run --access public --tag ${{ github.event.inputs.tag }}

- name: Publish to npm
if: ${{ github.event.inputs.dry_run != 'true' }}
working-directory: packages/workload-router
run: npm publish --provenance --access public --tag ${{ github.event.inputs.tag }}
# Authentication note: this workflow does NOT use an NPM_TOKEN. It relies
# on npm's OIDC trusted-publisher flow — the `id-token: write` permission
# above lets `npm publish --provenance` exchange the GitHub workflow's
# OIDC identity for a short-lived publish token. Each package must be
# registered as a trusted publisher on npmjs.com under this
# repo/workflow path for the first publish.
#
# Pipeline: `pnpm pack` rewrites workspace:* deps to concrete versions
# inside the tarball's package.json, then `npm publish <tarball>`
# uploads it using npm's native auth. This decouples workspace-aware
# packing from publish-time auth and matches the agent-relay pattern.
- name: Pack + publish
run: |
set -euo pipefail
PACK_DIR="$RUNNER_TEMP/packs"
mkdir -p "$PACK_DIR"

COMMON_FLAGS="--access public --tag ${{ github.event.inputs.tag }}"
if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
COMMON_FLAGS+=" --dry-run"
else
COMMON_FLAGS+=" --provenance"
fi

for pkg in ${{ steps.targets.outputs.packages }}; do
echo "==> Packing @agentworkforce/$pkg"
pnpm --filter "@agentworkforce/$pkg" pack --pack-destination "$PACK_DIR"
TARBALL=$(ls -1t "$PACK_DIR"/agentworkforce-$pkg-*.tgz | head -n1)
if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
echo "::error::could not find packed tarball for $pkg in $PACK_DIR" >&2
ls -la "$PACK_DIR" >&2 || true
exit 1
fi
echo "==> Publishing $TARBALL $COMMON_FLAGS"
npm publish "$TARBALL" $COMMON_FLAGS
done

- name: Tag + push
if: ${{ github.event.inputs.dry_run != 'true' }}
if: ${{ github.event.inputs.dry_run != 'true' && (github.event.inputs.version != 'none' || github.event.inputs.custom_version != '') }}
run: |
git tag workload-router-v${{ steps.bump.outputs.version }}
git push origin main --follow-tags
for entry in ${{ steps.bump.outputs.versions }}; do
pkg="${entry%%:*}"
version="${entry##*:}"
git tag "$pkg-v$version"
done
git push origin HEAD --follow-tags

- name: Summary
run: |
echo "Published: @agentworkforce/workload-router@${{ steps.bump.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "Tag: ${{ github.event.inputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "Dry run: ${{ github.event.inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY
{
echo "### Published"
echo ""
for entry in ${{ steps.bump.outputs.versions }}; do
pkg="${entry%%:*}"
version="${entry##*:}"
echo "- \`@agentworkforce/$pkg@$version\`"
done
echo ""
echo "- **dist-tag**: \`${{ github.event.inputs.tag }}\`"
echo "- **dry run**: \`${{ github.event.inputs.dry_run }}\`"
echo ""
if [ "${{ github.event.inputs.dry_run }}" != "true" ]; then
echo "Next step: verify the published artifact by running the \`Verify Publish\` workflow."
fi
} >> "$GITHUB_STEP_SUMMARY"
110 changes: 110 additions & 0 deletions .github/workflows/verify-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Verify Publish

# Manually triggered smoke test against the npm registry — run after a Publish
# Package workflow completes. Installs the just-published package into a clean
# environment and exercises it enough to catch "published but broken" failures
# (bin missing +x, workspace:* deps not rewritten, missing exports, etc.).

on:
workflow_dispatch:
inputs:
package:
description: 'Package to verify'
required: true
default: '@agentworkforce/cli'
type: choice
options:
- '@agentworkforce/cli'
- '@agentworkforce/harness-kit'
- '@agentworkforce/workload-router'
version:
description: 'Version to verify (defaults to the "latest" dist-tag)'
required: false
default: latest
tag:
description: 'Alternate dist-tag to pull from if `version` is left as default'
required: false
default: latest

permissions:
contents: read

jobs:
verify:
name: Install + smoke test
runs-on: ubuntu-latest
steps:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22.14.0'
registry-url: 'https://registry.npmjs.org'

- name: Resolve version
id: resolve
run: |
INPUT='${{ github.event.inputs.version }}'
TAG='${{ github.event.inputs.tag }}'
if [ -z "$INPUT" ] || [ "$INPUT" = "latest" ]; then
RESOLVED=$(npm view "${{ github.event.inputs.package }}@$TAG" version)
else
RESOLVED="$INPUT"
fi
echo "version=$RESOLVED" >> "$GITHUB_OUTPUT"
echo "Verifying ${{ github.event.inputs.package }}@$RESOLVED"

- name: CLI smoke test
if: ${{ github.event.inputs.package == '@agentworkforce/cli' }}
run: |
set -euo pipefail
npm install -g "${{ github.event.inputs.package }}@${{ steps.resolve.outputs.version }}"
# --help should exit 0 and print the usage block.
output=$(agent-workforce --help)
echo "$output"
echo "$output" | grep -q "Usage: agent-workforce agent" || {
echo "::error::--help output did not include expected usage block" >&2
exit 1
}
# Bare invocation should exit non-zero with the usage text.
if agent-workforce > /dev/null 2>&1; then
echo "::error::bare agent-workforce should exit non-zero (missing subcommand)" >&2
exit 1
fi
# Unknown persona should exit non-zero and list the catalog.
if agent-workforce agent __definitely_not_a_persona__ > /dev/null 2>&1; then
echo "::error::unknown persona should exit non-zero" >&2
exit 1
fi
echo "CLI smoke test passed."

- name: Library smoke test (ESM import + exports surface)
if: ${{ github.event.inputs.package != '@agentworkforce/cli' }}
run: |
set -euo pipefail
WORKDIR=$(mktemp -d)
cd "$WORKDIR"
npm init -y > /dev/null
node -e "require('fs').writeFileSync('package.json', JSON.stringify({...require('./package.json'), type:'module'}, null, 2))"
npm install "${{ github.event.inputs.package }}@${{ steps.resolve.outputs.version }}"
cat > check.mjs <<'EOF'
const mod = await import(process.argv[2]);
const exports = Object.keys(mod).sort();
if (exports.length === 0) {
console.error('no exports from package');
process.exit(1);
}
console.log('exports:', exports.join(', '));
EOF
node check.mjs "${{ github.event.inputs.package }}"
echo "Library smoke test passed."

- name: Summary
if: always()
run: |
{
echo "### Verify Publish"
echo ""
echo "- **package**: \`${{ github.event.inputs.package }}\`"
echo "- **version**: \`${{ steps.resolve.outputs.version }}\`"
echo "- **result**: ${{ job.status }}"
} >> "$GITHUB_STEP_SUMMARY"
Loading
Loading