Skip to content

Commit c3d070d

Browse files
authored
Pin microsoft/APM version to v0.8.0 and emit it in generated apm-action steps (#21297)
1 parent 810cd46 commit c3d070d

13 files changed

Lines changed: 197 additions & 10 deletions

.github/workflows/cli-version-checker.lock.yml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/cli-version-checker.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: Monitors and updates agentic CLI tools (Claude Code, GitHub Copilot CLI, OpenAI Codex, GitHub MCP Server, Playwright MCP, Playwright Browser, MCP Gateway) for new versions
2+
description: Monitors and updates agentic CLI tools (Claude Code, GitHub Copilot CLI, OpenAI Codex, GitHub MCP Server, Playwright MCP, Playwright Browser, MCP Gateway, APM) for new versions
33
on:
44
schedule: daily
55
workflow_dispatch:
@@ -31,7 +31,7 @@ timeout-minutes: 45
3131

3232
# CLI Version Checker
3333

34-
Monitor and update agentic CLI tools: Claude Code, GitHub Copilot CLI, OpenAI Codex, GitHub MCP Server, Playwright MCP, Playwright Browser, and MCP Gateway.
34+
Monitor and update agentic CLI tools: Claude Code, GitHub Copilot CLI, OpenAI Codex, GitHub MCP Server, Playwright MCP, Playwright Browser, MCP Gateway, and APM (Agent Package Manager).
3535

3636
**Repository**: ${{ github.repository }} | **Run**: ${{ github.run_id }}
3737

@@ -74,6 +74,11 @@ For each CLI/MCP server:
7474
- Release Notes: https://github.com/github/gh-aw-mcpg/releases
7575
- Docker Image: `ghcr.io/github/gh-aw-mcpg:v{VERSION}`
7676
- Used as default sandbox.agent container (see `pkg/constants/constants.go`)
77+
- **APM (Agent Package Manager)**: `https://api.github.com/repos/microsoft/APM/releases/latest`
78+
- Repository: https://github.com/microsoft/APM
79+
- Release Notes: https://github.com/microsoft/APM/releases
80+
- Pinned via `DefaultAPMVersion` constant in `pkg/constants/constants.go`
81+
- Used as the `version:` input in generated `microsoft/apm-action` steps
7782

7883
**Optimization**: Fetch all versions in parallel using multiple npm view or WebFetch calls in a single turn.
7984

@@ -119,6 +124,9 @@ For each update, analyze intermediate versions:
119124
- Parse release body for changelog entries
120125
- **CRITICAL**: Convert PR/issue references to full URLs (e.g., `https://github.com/github/gh-aw-mcpg/pull/123`)
121126
- Note: Used as default sandbox.agent container in MCP Gateway configuration
127+
- **APM**: Fetch release notes from https://github.com/microsoft/APM/releases/tag/{VERSION}
128+
- Parse release body for changelog entries
129+
- **CRITICAL**: Convert PR/issue references to full URLs (e.g., `https://github.com/microsoft/APM/pull/123`)
122130

123131
**NPM Metadata Fallback**: When GitHub release notes are unavailable, use:
124132
- `npm view <package> --json` for package metadata
@@ -267,6 +275,7 @@ Legacy template reference (adapt to use Report Structure Pattern above):
267275
- GitHub MCP Server: Always fetch from https://github.com/github/github-mcp-server/releases
268276
- Playwright Browser: Always fetch from https://github.com/microsoft/playwright/releases
269277
- MCP Gateway: Always fetch from https://github.com/github/gh-aw-mcpg/releases
278+
- APM: Always fetch from https://github.com/microsoft/APM/releases
270279
- Copilot CLI: Try to fetch, but may be inaccessible (private repo)
271280
- Playwright MCP: Check NPM metadata, uses Playwright versioning
272281
- **EXPLORE SUBCOMMANDS**: Install and test CLI tools to discover new features via `--help` and explore each subcommand

.github/workflows/smoke-claude.lock.yml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

actions/setup/js/generate_aw_info.cjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ async function main(core, ctx) {
8686
awInfo.cli_version = cliVersion;
8787
}
8888

89+
// Include apm_version only when APM dependencies are configured
90+
const apmVersion = process.env.GH_AW_INFO_APM_VERSION;
91+
if (apmVersion) {
92+
awInfo.apm_version = apmVersion;
93+
}
94+
8995
// Write to /tmp/gh-aw directory to avoid inclusion in PR
9096
fs.mkdirSync(TMP_GH_AW_PATH, { recursive: true });
9197
const tmpPath = TMP_GH_AW_PATH + "/aw_info.json";

pkg/constants/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ var SerenaLanguageSupport = map[string][]string{
406406
},
407407
}
408408

409+
// DefaultAPMVersion is the default version of the microsoft/APM (Agent Package Manager) CLI
410+
const DefaultAPMVersion Version = "v0.8.0"
411+
409412
// DefaultPlaywrightMCPVersion is the default version of the @playwright/mcp package
410413
const DefaultPlaywrightMCPVersion Version = "0.0.68"
411414

pkg/workflow/apm_dependencies.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ func GenerateAPMPackStep(apmDeps *APMDependenciesInfo, target string, data *Work
125125
" archive: 'true'",
126126
" target: "+target,
127127
" working-directory: /tmp/gh-aw/apm-workspace",
128+
" apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
128129
)
129130

130131
return GitHubActionStep(lines)
@@ -153,6 +154,7 @@ func GenerateAPMRestoreStep(apmDeps *APMDependenciesInfo, data *WorkflowData) Gi
153154
" uses: " + actionRef,
154155
" with:",
155156
" bundle: /tmp/gh-aw/apm-bundle/*.tar.gz",
157+
" apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
156158
}
157159

158160
if apmDeps.Isolated {

pkg/workflow/apm_dependencies_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,40 @@ func TestExtractAPMDependenciesGitHubApp(t *testing.T) {
198198
})
199199
}
200200

201+
func TestExtractAPMDependenciesVersion(t *testing.T) {
202+
t.Run("Object format with version field", func(t *testing.T) {
203+
frontmatter := map[string]any{
204+
"dependencies": map[string]any{
205+
"packages": []any{"microsoft/apm-sample-package"},
206+
"version": "v1.0.0",
207+
},
208+
}
209+
result := extractAPMDependenciesFromFrontmatter(frontmatter)
210+
require.NotNil(t, result, "Should return non-nil APMDependenciesInfo")
211+
assert.Equal(t, "v1.0.0", result.Version, "Version should be extracted from object format")
212+
})
213+
214+
t.Run("Array format has no version field", func(t *testing.T) {
215+
frontmatter := map[string]any{
216+
"dependencies": []any{"microsoft/apm-sample-package"},
217+
}
218+
result := extractAPMDependenciesFromFrontmatter(frontmatter)
219+
require.NotNil(t, result, "Should return non-nil APMDependenciesInfo")
220+
assert.Empty(t, result.Version, "Version should be empty for array format")
221+
})
222+
223+
t.Run("Object format without version uses empty string", func(t *testing.T) {
224+
frontmatter := map[string]any{
225+
"dependencies": map[string]any{
226+
"packages": []any{"microsoft/apm-sample-package"},
227+
},
228+
}
229+
result := extractAPMDependenciesFromFrontmatter(frontmatter)
230+
require.NotNil(t, result, "Should return non-nil APMDependenciesInfo")
231+
assert.Empty(t, result.Version, "Version should be empty when not specified")
232+
})
233+
}
234+
201235
func TestEngineGetAPMTarget(t *testing.T) {
202236
tests := []struct {
203237
name string
@@ -253,6 +287,7 @@ func TestGenerateAPMPackStep(t *testing.T) {
253287
"archive: 'true'",
254288
"target: copilot",
255289
"working-directory: /tmp/gh-aw/apm-workspace",
290+
"apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
256291
},
257292
},
258293
{
@@ -266,6 +301,7 @@ func TestGenerateAPMPackStep(t *testing.T) {
266301
"- microsoft/apm-sample-package",
267302
"- github/skills/review",
268303
"target: claude",
304+
"apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
269305
},
270306
},
271307
{
@@ -274,6 +310,15 @@ func TestGenerateAPMPackStep(t *testing.T) {
274310
target: "all",
275311
expectedContains: []string{
276312
"target: all",
313+
"apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
314+
},
315+
},
316+
{
317+
name: "Custom APM version still uses env var reference in step",
318+
apmDeps: &APMDependenciesInfo{Packages: []string{"microsoft/apm-sample-package"}, Version: "v1.0.0"},
319+
target: "copilot",
320+
expectedContains: []string{
321+
"apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
277322
},
278323
},
279324
}
@@ -323,6 +368,7 @@ func TestGenerateAPMRestoreStep(t *testing.T) {
323368
"Restore APM dependencies",
324369
"microsoft/apm-action",
325370
"bundle: /tmp/gh-aw/apm-bundle/*.tar.gz",
371+
"apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
326372
},
327373
expectedNotContains: []string{"isolated"},
328374
},
@@ -334,6 +380,14 @@ func TestGenerateAPMRestoreStep(t *testing.T) {
334380
"microsoft/apm-action",
335381
"bundle: /tmp/gh-aw/apm-bundle/*.tar.gz",
336382
"isolated: 'true'",
383+
"apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
384+
},
385+
},
386+
{
387+
name: "Custom APM version still uses env var reference in step",
388+
apmDeps: &APMDependenciesInfo{Packages: []string{"microsoft/apm-sample-package"}, Version: "v1.0.0"},
389+
expectedContains: []string{
390+
"apm-version: ${{ env.GH_AW_INFO_APM_VERSION }}",
337391
},
338392
},
339393
}

pkg/workflow/aw_info_versions_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,3 +382,64 @@ func TestAllVersionsInAwInfo(t *testing.T) {
382382
t.Errorf("Expected output to contain awmg_version '%s', got:\n%s", expectedAwmgLine, output)
383383
}
384384
}
385+
386+
func TestApmVersionInAwInfo(t *testing.T) {
387+
tests := []struct {
388+
name string
389+
apmDeps *APMDependenciesInfo
390+
expectedApmVersion string
391+
description string
392+
}{
393+
{
394+
name: "APM deps with explicit version",
395+
apmDeps: &APMDependenciesInfo{Packages: []string{"microsoft/apm-sample-package"}, Version: "v1.0.0"},
396+
expectedApmVersion: "v1.0.0",
397+
description: "Should use explicit APM version when provided",
398+
},
399+
{
400+
name: "APM deps with default version",
401+
apmDeps: &APMDependenciesInfo{Packages: []string{"microsoft/apm-sample-package"}},
402+
expectedApmVersion: string(constants.DefaultAPMVersion),
403+
description: "Should use default APM version when not specified",
404+
},
405+
{
406+
name: "No APM deps configured",
407+
apmDeps: nil,
408+
expectedApmVersion: "",
409+
description: "Should not emit GH_AW_INFO_APM_VERSION when no APM dependencies are configured",
410+
},
411+
}
412+
413+
for _, tt := range tests {
414+
t.Run(tt.name, func(t *testing.T) {
415+
compiler := NewCompilerWithVersion("1.0.0")
416+
registry := GetGlobalEngineRegistry()
417+
engine, err := registry.GetEngine("copilot")
418+
if err != nil {
419+
t.Fatalf("Failed to get copilot engine: %v", err)
420+
}
421+
422+
workflowData := &WorkflowData{
423+
Name: "Test Workflow",
424+
APMDependencies: tt.apmDeps,
425+
}
426+
427+
var yaml strings.Builder
428+
compiler.generateCreateAwInfo(&yaml, workflowData, engine)
429+
output := yaml.String()
430+
431+
if tt.expectedApmVersion == "" {
432+
if strings.Contains(output, "GH_AW_INFO_APM_VERSION") {
433+
t.Errorf("%s: Expected output to NOT contain GH_AW_INFO_APM_VERSION, got:\n%s",
434+
tt.description, output)
435+
}
436+
} else {
437+
expectedLine := `GH_AW_INFO_APM_VERSION: "` + tt.expectedApmVersion + `"`
438+
if !strings.Contains(output, expectedLine) {
439+
t.Errorf("%s: Expected output to contain '%s', got:\n%s",
440+
tt.description, expectedLine, output)
441+
}
442+
}
443+
})
444+
}
445+
}

pkg/workflow/compiler_activation_job.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,13 +511,27 @@ func (c *Compiler) buildActivationJob(data *WorkflowData, preActivationJobCreate
511511
environment = "environment: " + cleanManualApproval
512512
}
513513

514+
// Set job-level GH_AW_INFO_APM_VERSION so the apm_pack step can reference it
515+
// via ${{ env.GH_AW_INFO_APM_VERSION }} in its with: block
516+
var activationJobEnv map[string]string
517+
if data.APMDependencies != nil && len(data.APMDependencies.Packages) > 0 {
518+
apmVersion := data.APMDependencies.Version
519+
if apmVersion == "" {
520+
apmVersion = string(constants.DefaultAPMVersion)
521+
}
522+
activationJobEnv = map[string]string{
523+
"GH_AW_INFO_APM_VERSION": apmVersion,
524+
}
525+
}
526+
514527
job := &Job{
515528
Name: string(constants.ActivationJobName),
516529
If: activationCondition,
517530
HasWorkflowRunSafetyChecks: workflowRunRepoSafety != "", // Mark job as having workflow_run safety checks
518531
RunsOn: c.formatSafeOutputsRunsOn(data.SafeOutputs),
519532
Permissions: permissions,
520533
Environment: environment,
534+
Env: activationJobEnv,
521535
Steps: steps,
522536
Outputs: outputs,
523537
Needs: activationNeeds, // Depend on pre-activation job if it exists

pkg/workflow/compiler_main_job.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,19 @@ func (c *Compiler) buildMainJob(data *WorkflowData, activationJobCreated bool) (
221221
env["GH_AW_WORKFLOW_ID_SANITIZED"] = sanitizedID
222222
}
223223

224+
// Set job-level GH_AW_INFO_APM_VERSION so the apm_restore step can reference it
225+
// via ${{ env.GH_AW_INFO_APM_VERSION }} in its with: block
226+
if data.APMDependencies != nil && len(data.APMDependencies.Packages) > 0 {
227+
if env == nil {
228+
env = make(map[string]string)
229+
}
230+
apmVersion := data.APMDependencies.Version
231+
if apmVersion == "" {
232+
apmVersion = string(constants.DefaultAPMVersion)
233+
}
234+
env["GH_AW_INFO_APM_VERSION"] = apmVersion
235+
}
236+
224237
// Generate agent concurrency configuration
225238
agentConcurrency := GenerateJobConcurrencyConfig(data)
226239

0 commit comments

Comments
 (0)