Skip to content
Open
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
14 changes: 6 additions & 8 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,20 @@ care what triggered it.

An orchestrator calls jobs via `workflow_call`.

### Current example
### Current examples

```
ci.yml
|-- check.yml (lint, format, typecheck, audit, secret scan)
|-- build.yml (bundle, package, compile, smoke test)
`-- unit-test.yml (tests on Linux, Windows, macOS)
```

### Future examples
release-prepare.yml (version bump, vended CDK pin, release PR)

```
release.yml
├── unit-test.yml
├── build.yml
└── publish-npm.yml
release-publish.yml (npm publish and GitHub release when a release PR merges)
|-- check.yml
|-- build.yml
`-- unit-test.yml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we take the opportunity to put all of these in one. That will be much more efficient and will save time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Harrison and TJ seemed to think that the release workflow
From the SDKs made a lot of sense. So I restructured it to be like those, it was originally one file.

Let's discuss

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think alex's comment is in reference to the check/build/unit-test workflows which are always run together, so I think the suggestion is to make it a single workflow containing check, build, and unit-test as jobs.

My understanding is that the runtime behavior would be the same, but we no longer need to manually wire in each job as a separate call. Instead, we call a single workflow that triggers all three, so it'd simplify some of the changes here.

```

Jobs like `unit-test.yml` and `build.yml` appear in multiple orchestrators. This
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-automation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ jobs:
id: tarball
run: |
bun pm pack --destination "$RUNNER_TEMP"
tarball="$(find "$RUNNER_TEMP" -maxdepth 1 -type f -name 'agentcore-*.tgz' -print -quit)"
tarball="$(find "$RUNNER_TEMP" -maxdepth 1 -type f -name 'aws-agentcore-*.tgz' -print -quit)"
test -f "$tarball"
echo "name=$(basename "$tarball")" >> "$GITHUB_OUTPUT"
echo "path=$tarball" >> "$GITHUB_OUTPUT"
Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/release-prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Opens the release PR: bumps package.json, refreshes the vended @aws/agentcore-cdk pin, and pushes
# release/v<version>. Merging that PR is the release approval and triggers release-publish.yml.
name: release-prepare
on:
workflow_dispatch:
inputs:
bump:
description: Semver component to release. Ignored while an rc series is open, which continues or graduates instead.
required: true
type: choice
options: [major, minor, patch]
channel:
description: rc publishes under the rc dist-tag, stable under latest
required: true
type: choice
options: [rc, stable]

jobs:
prepare:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
fetch-tags: true
persist-credentials: false
- uses: oven-sh/setup-bun@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: Bump version
id: version
env:
BUMP: ${{ inputs.bump }}
CHANNEL: ${{ inputs.channel }}
run: |
set -euo pipefail
CURRENT=$(bun pm pkg get version | tr -d '"')
# 0.28.1 -> 1.0.0-rc.0 -> 1.0.0-rc.1 -> 1.0.0. Bun's own "major" would jump an rc to 2.0.0.
if [[ "$CURRENT" == *-rc.* ]]; then
INCREMENT=$([ "$CHANNEL" = rc ] && echo prerelease || echo "${CURRENT%%-*}")
else
INCREMENT=$([ "$CHANNEL" = rc ] && echo "pre$BUMP" || echo "$BUMP")
fi
TAG=$(bun pm version "$INCREMENT" --preid rc --no-git-tag-version)
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
echo "::error::$TAG is already released"
exit 1
fi
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"

- run: bun scripts/sync-vended-cdk.ts

- name: Open release PR
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
BRANCH="release/v$VERSION"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -am "chore(release): v$VERSION"
git push --force "https://x-access-token:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git" "HEAD:refs/heads/$BRANCH"

if [ -z "$(gh pr list --base "$GITHUB_REF_NAME" --head "$BRANCH" --json number --jq '.[0].number')" ]; then
gh pr create --base "$GITHUB_REF_NAME" --head "$BRANCH" --title "chore(release): v$VERSION" \
--body "Merging publishes \`@aws/agentcore@$VERSION\` to npm and creates the \`v$VERSION\` GitHub release with generated notes."
fi
124 changes: 124 additions & 0 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Publishes @aws/agentcore to npm and creates the GitHub release when a release PR from
# release-prepare.yml merges. The version is whatever the merged package.json says.
name: release-publish
on:
pull_request:
types: [closed]
# TODO: switch to main once the refactor lands there.
branches: [refactor]
workflow_dispatch:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need workflow dispatch here?

inputs:
dry_run:
description: Build, pack and compile, but skip npm publish and the GitHub release
type: boolean
default: true

jobs:
check:
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged && startsWith(github.head_ref, 'release/v'))
uses: ./.github/workflows/check.yml
permissions:
contents: read
with:
ref: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
build:
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged && startsWith(github.head_ref, 'release/v'))
uses: ./.github/workflows/build.yml
permissions:
contents: read
with:
ref: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
unit-test:
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged && startsWith(github.head_ref, 'release/v'))
uses: ./.github/workflows/unit-test.yml
permissions:
contents: read
with:
ref: ${{ github.event.pull_request.merge_commit_sha || github.sha }}

publish:
needs: [check, build, unit-test]
# npm provenance is only issued from GitHub-hosted runners.
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
env:
REF: ${{ github.event.pull_request.merge_commit_sha || github.sha }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ env.REF }}
fetch-tags: true
persist-credentials: false
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v6
with:
node-version: 20.x
registry-url: https://registry.npmjs.org
- run: bun install --frozen-lockfile

- name: Resolve release
id: release
run: |
set -euo pipefail
VERSION=$(bun pm pkg get version | tr -d '"')
# rc notes span from the previous rc, stable notes from the previous stable. Only this
# workflow's tags count, main's v1.0.0-preview.N tags live in the same repo.
DIST_TAG=latest
SEMVER_FLAGS=()
if [[ "$VERSION" == *-rc.* ]]; then
DIST_TAG=rc
SEMVER_FLAGS=(--include-prerelease)
fi
PREVIOUS=$(git tag -l 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$' \
| xargs bunx semver "${SEMVER_FLAGS[@]}" --range "<$VERSION" | tail -1)
{
echo "version=$VERSION"
echo "dist_tag=$DIST_TAG"
echo "previous_tag=v$PREVIOUS"
} >> "$GITHUB_OUTPUT"

- run: bun run build
- id: pack
run: echo "tarball=$(bun pm pack --quiet --ignore-scripts)" >> "$GITHUB_OUTPUT"
- run: bun run compile

- if: ${{ !inputs.dry_run }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
VERSION: ${{ steps.release.outputs.version }}
DIST_TAG: ${{ steps.release.outputs.dist_tag }}
TARBALL: ${{ steps.pack.outputs.tarball }}
# A rerun after a failed release step must not republish, npm rejects an existing version.
run: |
if npm view "@aws/agentcore@$VERSION" version >/dev/null 2>&1; then
echo "@aws/agentcore@$VERSION is already on npm, skipping publish"
else
npm publish "$TARBALL" --provenance --access public --tag "$DIST_TAG"
fi

- if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.release.outputs.version }}
PREVIOUS_TAG: ${{ steps.release.outputs.previous_tag }}
TARBALL: ${{ steps.pack.outputs.tarball }}
# A rerun after a failed asset upload re-uploads into the existing release instead of failing on the tag.
run: |
if gh release view "v$VERSION" >/dev/null 2>&1; then
gh release upload "v$VERSION" --clobber "$TARBALL" dist/bin/*
else
gh release create "v$VERSION" --target "$REF" --title "v$VERSION" \
--generate-notes --notes-start-tag "$PREVIOUS_TAG" \
${{ steps.release.outputs.dist_tag == 'rc' && '--prerelease' || '--latest' }} \
"$TARBALL" dist/bin/*
fi

- if: ${{ inputs.dry_run }}
uses: actions/upload-artifact@v7
with:
name: release-${{ steps.release.outputs.version }}
path: |
${{ steps.pack.outputs.tarball }}
dist/bin/*
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -905,14 +905,14 @@ npm link
Re-run `bun run build` after changes; the linked command picks it up. Remove with:

```bash
npm unlink -g agentcore
npm unlink -g @aws/agentcore
```

To test the exact published artifact instead:

```bash
npm pack # builds via prepublishOnly, creates the .tgz
npm i -g ./agentcore-1.0.0.tgz
npm i -g ./aws-agentcore-0.28.1.tgz
```

# Build
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"name": "agentcore",
"version": "1.0.0",
"name": "@aws/agentcore",
"version": "0.28.1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we releasing under 1.0.0-rc or the previous version number?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to do 1.0.0-rc.1

"repository": {
"type": "git",
"url": "https://github.com/aws/agentcore-cli.git"
},
"module": "src/index.ts",
"type": "module",
"bin": {
Expand Down
27 changes: 27 additions & 0 deletions scripts/sync-vended-cdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bun

/**
Pins the vended CDK template to the published @aws/agentcore-cdk at the given dist-tag (default latest).
Runs before bun install in release-prepare.yml, so it must not import anything from src/.
**/

import { $ } from "bun";
import { join, resolve } from "node:path";

const CDK_PACKAGE = "@aws/agentcore-cdk";
const TEMPLATE = Bun.file(
join(resolve(import.meta.dir, ".."), "src", "assets", "cdk", "package.json"),
);

const tag = Bun.argv[2] ?? "latest";
const target = (await $`bun pm view ${CDK_PACKAGE}@${tag} version`.text()).trim();
const template = await TEMPLATE.json();
const current = template.dependencies[CDK_PACKAGE];

if (current === target) {
console.log(`${CDK_PACKAGE} already pinned to ${target}`);
} else {
template.dependencies[CDK_PACKAGE] = target;
await Bun.write(TEMPLATE, JSON.stringify(template, null, 2) + "\n");
console.log(`${CDK_PACKAGE}: ${current} → ${target}`);
}
9 changes: 5 additions & 4 deletions src/handlers/update/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test";
import { fetchLatestVersion, handleUpdate } from "./index";
import { NetworkingError } from "../../errors";
import type { ProcessRunner } from "../../io";
import { PACKAGE_VERSION } from "../../constants";

// No golden/fixture tests here: the repo's *.fixture.test.tsx harness records and
// replays AWS SDK responses through CoreClient, but `update` makes no AWS calls —
Expand Down Expand Up @@ -50,12 +51,12 @@ describe("handleUpdate", () => {
});

test("up-to-date when versions match, without invoking the runner", async () => {
mockLatest("1.0.0");
mockLatest(PACKAGE_VERSION);
const runner: ProcessRunner = mock(async () => {});
expect(await handleUpdate(false, { runner })).toEqual({
status: "up-to-date",
currentVersion: "1.0.0",
latestVersion: "1.0.0",
currentVersion: PACKAGE_VERSION,
latestVersion: PACKAGE_VERSION,
});
expect(runner).not.toHaveBeenCalled();
});
Expand All @@ -70,7 +71,7 @@ describe("handleUpdate", () => {
const runner: ProcessRunner = mock(async () => {});
expect(await handleUpdate(true, { runner })).toEqual({
status: "update-available",
currentVersion: "1.0.0",
currentVersion: PACKAGE_VERSION,
latestVersion: "2.0.0",
});
expect(runner).not.toHaveBeenCalled();
Expand Down
25 changes: 25 additions & 0 deletions src/telemetry/client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { MetricSink } from "./types";
import { FileSystemSink } from "./fileSystemSink";
import { DEFAULT_GLOBAL_CONFIG } from "../globalConfig";
import { PACKAGE_VERSION } from "../constants";
import { resourceAttributesSchema } from "./shapes";

describe("DefaultTelemetryClient", () => {
let tempDir: string;
Expand Down Expand Up @@ -433,3 +434,27 @@ describe("OtelHistogramSink", () => {
await rm(auditFilePath, { force: true });
});
});

describe("resourceAttributesSchema", () => {
const attributes = (version: string) => ({
"service.name": "agentcore-cli",
"service.version": version,
"agentcore-cli.installation_id": "00000000-0000-0000-0000-000000000000",
"agentcore-cli.session_id": "00000000-0000-0000-0000-000000000000",
"os.type": "Darwin",
"os.version": "25.6.0",
"host.arch": "arm64",
"node.version": "v22.0.0",
});

test.each(["0.28.1", "1.0.0-rc.0", "1.0.0-preview.29"])(
"accepts service.version %s",
(version) => {
expect(resourceAttributesSchema.safeParse(attributes(version)).success).toBe(true);
},
);

test.each(["v1.0.0", "1.0", "1.0.0-"])("rejects service.version %s", (version) => {
expect(resourceAttributesSchema.safeParse(attributes(version)).success).toBe(false);
});
});
4 changes: 2 additions & 2 deletions src/telemetry/shapes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import z from "zod";
import { ERROR_SOURCE } from "../errors";

const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
const NODE_VERSION_PATTERN = /^v\d+\.\d+\.\d+$/;
const MAX_ATTR_LENGTH = 64;

Expand All @@ -12,7 +12,7 @@ const MAX_ATTR_LENGTH = 64;
*/
export const resourceAttributesSchema = z.object({
"service.name": z.literal("agentcore-cli"),
"service.version": z.string().regex(SEMVER_PATTERN),
"service.version": z.string().max(MAX_ATTR_LENGTH).regex(SEMVER_PATTERN),
"agentcore-cli.installation_id": z.string().regex(UUID_PATTERN),
"agentcore-cli.session_id": z.string().regex(UUID_PATTERN),
"os.type": z.string().min(1).max(MAX_ATTR_LENGTH),
Expand Down
Loading