diff --git a/.github/workflows/avenger.lock.yml b/.github/workflows/avenger.lock.yml index 092aada6f01..3d6eb6ec67f 100644 --- a/.github/workflows/avenger.lock.yml +++ b/.github/workflows/avenger.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6807590fb97b42c0858f3b0e068f5f2b823e6d3a5a873b1a1652f5658361eb44","body_hash":"e5fd04fe008ba327d939d9aee395ffb802836e30fd1f3772ca36984bdd1478f4","strict":true,"agent_id":"claude","agent_model":"claude-haiku-4.5","engine_versions":{"claude":"2.1.210"}} -# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35","digest":"sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35","digest":"sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -44,7 +44,6 @@ # Custom actions used: # - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 # - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 -# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1146,7 +1145,7 @@ jobs: GH_HOST="${GH_HOST#http://}" echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Check last CI workflow run status on main branch diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index 7fbc3953765..4c96d7c4a51 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"8b86cc12904088cf1b7630e0faa28299f64081df184a028b6293cb4f8bfdd07d","body_hash":"8eb6f8fd1e8e3d63cfd268226d09333129d49634435ae9a3c88debb099521ed5","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.210"}} -# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35","digest":"sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35","digest":"sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -45,7 +45,6 @@ # Custom actions used: # - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 # - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 -# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1153,7 +1152,7 @@ jobs: GH_HOST="${GH_HOST#http://}" echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Check last CI workflow run status on main branch diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 5e5b9a932d6..b670dfbb32e 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -47,7 +47,7 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) -# - actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # b7ad1dad31e06c5925ef5d2fc7ad053ef454303e +# - actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 # - docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 @@ -1949,7 +1949,7 @@ jobs: env: RELEASE_TAG: ${{ needs.config.outputs.release_tag }} - name: Setup Go - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # b7ad1dad31e06c5925ef5d2fc7ad053ef454303e + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: cache: false go-version-file: go.mod diff --git a/.github/workflows/skillet.lock.yml b/.github/workflows/skillet.lock.yml index 8d7320c75be..81a6ce1825b 100644 --- a/.github/workflows/skillet.lock.yml +++ b/.github/workflows/skillet.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2b7791e73899163b45a0881db45f5747495a6a7dbd2dc4e17f3f31af4ecf6898","body_hash":"ff6f6b1fbf4788ce998b5ddca9ed8f907259189ec1db98a5cd9768e4e0ed2f50","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"3a2844b7e9c422d3c10d287c895573f7108da1b3"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35","digest":"sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"3a2844b7e9c422d3c10d287c895573f7108da1b3"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35","digest":"sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.35@sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -45,7 +45,6 @@ # Custom actions used: # - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 # - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 -# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 @@ -1728,7 +1727,7 @@ jobs: GH_AW_INFO_AWF_VERSION: "v0.27.35" GH_AW_INFO_ENGINE_ID: "copilot" - name: Checkout skills directory - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false sparse-checkout: | diff --git a/docs/adr/46332-split-large-functions-bootstrap-profile-runner.md b/docs/adr/46332-split-large-functions-bootstrap-profile-runner.md new file mode 100644 index 00000000000..59ce950d4a9 --- /dev/null +++ b/docs/adr/46332-split-large-functions-bootstrap-profile-runner.md @@ -0,0 +1,45 @@ +# ADR-46332: Split Large Functions in bootstrap_profile_runner.go to Fix largefunc Lint + +**Date**: 2026-07-18 +**Status**: Accepted +**Deciders**: copilot-swe-agent (PR author), reviewers of PR #46332 + +--- + +### Context + +`pkg/cli/bootstrap_profile_runner.go` had accumulated 14 `largefunc` lint findings (limit: 60 lines per function) concentrated in three functions: `executeBootstrapProfile` (78 lines), `runBootstrapGitHubAppAction` (86 lines), and `createBootstrapGitHubApp` (118 lines). The codebase enforces the 60-line maximum via the `largefunc` linter, and violations block CI. These functions grew large because they combine multi-branch dispatch logic, credential resolution, and HTTP handler construction in a single body. The refactoring must resolve all lint findings without changing any observable behavior. + +### Decision + +We will extract focused helper functions from each oversized function, grouping logically coherent blocks into named helpers: `applyBootstrapAction` (action-type dispatch), `handleBootstrapGitHubAppExistingFlow` and `handleBootstrapGitHubAppCreateOrExistingChoice` (two credential-resolution paths), `setupBootstrapGitHubAppDetails` (app metadata derivation), and `buildBootstrapGitHubAppMux` (HTTP handler construction). A new `bootstrapGitHubAppFlowChannels` struct groups the result/error channels to keep `buildBootstrapGitHubAppMux` under the 8-parameter linter limit. All extracted helpers carry the same behavior as the inlined code they replace. + +### Alternatives Considered + +#### Alternative 1: Raise or Disable the largefunc Limit + +Increase the `largefunc` threshold for these functions, or add per-function suppression comments. This avoids the refactoring work and keeps all logic co-located. Rejected because accepting `largefunc` exceptions for already-large functions encourages further accumulation; the lint limit exists precisely to enforce readability discipline across the codebase. + +#### Alternative 2: Restructure Using a Command/Strategy Object Pattern + +Replace the `switch action.Type` dispatch with a registry map of handler functions or a formal strategy hierarchy. This is a more architecturally significant approach that would make adding new action types easier. Rejected as disproportionate to a lint compliance fix — the helper-extraction approach resolves all 14 findings with minimal structural churn and zero behavior change, whereas a strategy pattern would require new types, a registration mechanism, and a larger diff to review. + +### Consequences + +#### Positive +- All 14 `largefunc` CI findings are resolved, unblocking future changes to this file. +- Each extracted function is individually testable with a single, named responsibility. +- The `bootstrapGitHubAppFlowChannels` struct makes channel ownership explicit, improving readability of the browser manifest flow coordinator. + +#### Negative +- Following `createBootstrapGitHubApp` end-to-end now requires reading through `setupBootstrapGitHubAppDetails` and `buildBootstrapGitHubAppMux` separately, adding indirection. + +#### Neutral +- `io.WriteString` replaces `w.Write([]byte(...))` as an incidental cleanup — functionally identical. +- The `continue` statement in the `copilot-auth` switch branch becomes `return nil` inside the extracted `applyBootstrapAction` function — semantically equivalent, required by the extraction boundary. +- `setupBootstrapGitHubAppDetails` now uses explicit return values (no named returns) to remove fragility around bare `return` statements. +- `buildBootstrapGitHubAppMux` now receives the override-resolved `appOwner`/`appOwnerType` so `exchangeBootstrapGitHubAppCode` stores the correct owner in the created app result when `overrides.Owner` is set. This fixes a pre-existing latent bug exposed during the extraction review. + +--- + +*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* diff --git a/pkg/cli/bootstrap_profile_runner.go b/pkg/cli/bootstrap_profile_runner.go index ff1118bf545..59dae492f6a 100644 --- a/pkg/cli/bootstrap_profile_runner.go +++ b/pkg/cli/bootstrap_profile_runner.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net" "net/http" "os" @@ -108,6 +109,13 @@ type bootstrapGitHubAppUserInstallation struct { RepositorySelection string } +// bootstrapGitHubAppFlowChannels holds the send-only channels used to deliver results +// from the HTTP callback handler back to the manifest-flow coordinator. +type bootstrapGitHubAppFlowChannels struct { + resultCh chan<- *bootstrapCreatedGitHubApp + errCh chan<- error +} + func executeBootstrapProfile(ctx context.Context, config bootstrapProfileRunConfig) error { if config.Profile == nil || config.Profile.Profile == nil { return nil @@ -135,60 +143,67 @@ func executeBootstrapProfile(ctx context.Context, config bootstrapProfileRunConf } bootstrapLog.Printf("Applying bootstrap action: type=%s", action.Type) - switch action.Type { - case "require-owner-type": - if err := runBootstrapRequireOwnerType(ctx, config.Repo, action); err != nil { - return err - } - case "repo-variable": - applied, err := runBootstrapRepoVariableAction(ctx, config.Repo, action, state) - if err != nil { - return err - } - if applied { - state.variables[action.Name] = struct{}{} - } - case "repo-secret": - applied, err := runBootstrapRepoSecretAction(ctx, config.Repo, action, state) - if err != nil { - return err - } - if applied { - state.secrets[action.Name] = struct{}{} - } - case "github-app": - _, err := runBootstrapGitHubAppAction(ctx, config.Repo, action, state) - if err != nil { - return err - } - state.variables[action.AppIDVariable] = struct{}{} - state.secrets[action.PrivateKeySecret] = struct{}{} - case "copilot-auth": - if config.UseCopilotRequests { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Skipping Copilot PAT setup because org Copilot billing is enabled.")) - continue - } - applied, err := runBootstrapCopilotAuthAction(ctx, config.Repo, action, state, usesActionsToken) - if err != nil { - return err - } - if applied { - state.secrets[action.Secret] = struct{}{} - } - case "commit-and-push": - if err := runBootstrapCommitAndPushAction(ctx, config.RepoDir, action); err != nil { - return err - } - case "handoff": - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(action.Message)) - default: - return fmt.Errorf("unsupported bootstrap action type %q. Example: use one of %s", action.Type, bootstrapActionTypeExample) + if err := applyBootstrapAction(ctx, config, action, state, usesActionsToken); err != nil { + return err } } return nil } +func applyBootstrapAction(ctx context.Context, config bootstrapProfileRunConfig, action repositoryPackageBootstrapAction, state *bootstrapProfileExistingState, usesActionsToken bool) error { + switch action.Type { + case "require-owner-type": + if err := runBootstrapRequireOwnerType(ctx, config.Repo, action); err != nil { + return err + } + case "repo-variable": + applied, err := runBootstrapRepoVariableAction(ctx, config.Repo, action, state) + if err != nil { + return err + } + if applied { + state.variables[action.Name] = struct{}{} + } + case "repo-secret": + applied, err := runBootstrapRepoSecretAction(ctx, config.Repo, action, state) + if err != nil { + return err + } + if applied { + state.secrets[action.Name] = struct{}{} + } + case "github-app": + _, err := runBootstrapGitHubAppAction(ctx, config.Repo, action, state) + if err != nil { + return err + } + state.variables[action.AppIDVariable] = struct{}{} + state.secrets[action.PrivateKeySecret] = struct{}{} + case "copilot-auth": + if config.UseCopilotRequests { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Skipping Copilot PAT setup because org Copilot billing is enabled.")) + return nil + } + applied, err := runBootstrapCopilotAuthAction(ctx, config.Repo, action, state, usesActionsToken) + if err != nil { + return err + } + if applied { + state.secrets[action.Secret] = struct{}{} + } + case "commit-and-push": + if err := runBootstrapCommitAndPushAction(ctx, config.RepoDir, action); err != nil { + return err + } + case "handoff": + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(action.Message)) + default: + return fmt.Errorf("unsupported bootstrap action type %q. Example: use one of %s", action.Type, bootstrapActionTypeExample) + } + return nil +} + func bootstrapProfileState(ctx context.Context, repo string) (*bootstrapProfileExistingState, error) { variableNames, err := listBootstrapRepoVariableNames(ctx, repo) if err != nil { @@ -386,49 +401,22 @@ func runBootstrapGitHubAppAction(ctx context.Context, repo string, action reposi return nil, err } - var clientID string - var privateKey string - clientID = strings.TrimSpace(os.Getenv(bootstrapGitHubAppClientIDEnv)) - privateKey = strings.TrimRight(os.Getenv(bootstrapGitHubAppPrivateKeyEnv), "\r\n") - if clientID != "" || privateKey != "" || action.Mode == "existing" || overrides.Mode == "existing" { - resolvedClientID, resolvedPrivateKey, err := completeExistingGitHubAppCredentials(clientID, privateKey, action, repo) - if err != nil { - return nil, err - } - if err := bootstrapUpsertVariable(ctx, repo, action.AppIDVariable, resolvedClientID); err != nil { - return nil, err - } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Set repository variable "+action.AppIDVariable)) - if err := bootstrapSetSecret(ctx, repo, action.PrivateKeySecret, resolvedPrivateKey); err != nil { - return nil, err - } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Set repository secret "+action.PrivateKeySecret)) + clientID := strings.TrimSpace(os.Getenv(bootstrapGitHubAppClientIDEnv)) + privateKey := strings.TrimRight(os.Getenv(bootstrapGitHubAppPrivateKeyEnv), "\r\n") + handled, err := handleBootstrapGitHubAppExistingFlow(ctx, repo, action, overrides, clientID, privateKey) + if err != nil { + return nil, err + } + if handled { return nil, nil } - createNew := action.Mode == "create-or-existing" || overrides.Mode == "create" - if createNew { - choice := overrides.Mode - if choice == "" { - choice, err = chooseBootstrapGitHubAppMode() - if err != nil { - return nil, err - } - } - if choice == "existing" { - resolvedClientID, resolvedPrivateKey, err := completeExistingGitHubAppCredentials(clientID, privateKey, action, repo) - if err != nil { - return nil, err - } - if err := bootstrapUpsertVariable(ctx, repo, action.AppIDVariable, resolvedClientID); err != nil { - return nil, err - } - if err := bootstrapSetSecret(ctx, repo, action.PrivateKeySecret, resolvedPrivateKey); err != nil { - return nil, err - } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Configured existing GitHub App credentials")) - return nil, nil - } + handled, err = handleBootstrapGitHubAppCreateOrExistingChoice(ctx, repo, action, overrides) + if err != nil { + return nil, err + } + if handled { + return nil, nil } if !bootstrapIsInteractive() && overrides.Mode != "create" { @@ -454,6 +442,61 @@ func runBootstrapGitHubAppAction(ctx context.Context, repo string, action reposi return createdApp, nil } +// handleBootstrapGitHubAppExistingFlow handles the path where existing app credentials +// are provided via environment variables or an explicit "existing" mode override. +// Returns (true, nil) when credentials were applied and the caller should return, or +// (false, nil) when no existing credentials were detected and the caller should proceed. +func handleBootstrapGitHubAppExistingFlow(ctx context.Context, repo string, action repositoryPackageBootstrapAction, overrides bootstrapGitHubAppOverrides, clientID, privateKey string) (bool, error) { + if clientID == "" && privateKey == "" && action.Mode != "existing" && overrides.Mode != "existing" { + return false, nil + } + resolvedClientID, resolvedPrivateKey, err := completeExistingGitHubAppCredentials(clientID, privateKey, action, repo) + if err != nil { + return false, err + } + if err := bootstrapUpsertVariable(ctx, repo, action.AppIDVariable, resolvedClientID); err != nil { + return false, err + } + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Set repository variable "+action.AppIDVariable)) + if err := bootstrapSetSecret(ctx, repo, action.PrivateKeySecret, resolvedPrivateKey); err != nil { + return false, err + } + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Set repository secret "+action.PrivateKeySecret)) + return true, nil +} + +// handleBootstrapGitHubAppCreateOrExistingChoice handles the "create-or-existing" action +// mode, prompting interactively when needed. Returns (true, nil) when the user chose an +// existing app and credentials were applied, or (false, nil) to proceed with creation. +func handleBootstrapGitHubAppCreateOrExistingChoice(ctx context.Context, repo string, action repositoryPackageBootstrapAction, overrides bootstrapGitHubAppOverrides) (bool, error) { + if action.Mode != "create-or-existing" && overrides.Mode != "create" { + return false, nil + } + choice := overrides.Mode + var err error + if choice == "" { + choice, err = chooseBootstrapGitHubAppMode() + if err != nil { + return false, err + } + } + if choice != "existing" { + return false, nil + } + resolvedClientID, resolvedPrivateKey, err := completeExistingGitHubAppCredentials("", "", action, repo) + if err != nil { + return false, err + } + if err := bootstrapUpsertVariable(ctx, repo, action.AppIDVariable, resolvedClientID); err != nil { + return false, err + } + if err := bootstrapSetSecret(ctx, repo, action.PrivateKeySecret, resolvedPrivateKey); err != nil { + return false, err + } + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Configured existing GitHub App credentials")) + return true, nil +} + func chooseBootstrapGitHubAppMode() (string, error) { if !bootstrapIsInteractive() { return "", fmt.Errorf("choose an existing GitHub App or set %s=create to allow browser-based creation in non-interactive environments. Example: export %s=existing", bootstrapGitHubAppModeEnv, bootstrapGitHubAppModeEnv) @@ -507,16 +550,68 @@ func createBootstrapGitHubApp(ctx context.Context, repo, owner, repoName, ownerT } defer listener.Close() + appOwner, appOwnerType, appName, homepageURL, description, err := setupBootstrapGitHubAppDetails(ctx, repo, owner, ownerType, action, overrides) + if err != nil { + return nil, err + } + + redirectURL := fmt.Sprintf("http://%s/callback", listener.Addr().String()) + manifest := buildBootstrapGitHubAppManifest(action, appName, homepageURL, redirectURL, description) + bootstrapLog.Printf("Creating GitHub App via browser manifest flow: appOwner=%s, appName=%s, redirectURL=%s", appOwner, appName, redirectURL) + registrationURL := buildBootstrapGitHubAppRegistrationURL(appOwner, appOwnerType, state) + registrationPage, err := renderBootstrapGitHubAppRegistrationPage(registrationURL, manifest) + if err != nil { + return nil, fmt.Errorf("failed to encode GitHub App registration manifest for browser handoff; report this issue if it persists: %w", err) + } + + resultCh := make(chan *bootstrapCreatedGitHubApp, 1) + errCh := make(chan error, 1) + flowCh := bootstrapGitHubAppFlowChannels{resultCh: resultCh, errCh: errCh} + server := &http.Server{ + Handler: buildBootstrapGitHubAppMux(ctx, state, appOwner, appOwnerType, appName, description, registrationPage, flowCh), + } + go func() { + _ = server.Serve(listener) + }() + defer func() { + _ = server.Shutdown(context.Background()) + }() + + printBootstrapGitHubAppManifestReview(appOwner, manifest) + openURL := fmt.Sprintf("http://%s/register", listener.Addr().String()) + opened := overrides.OpenBrowser && openBootstrapBrowser(openURL) + if !opened { + fmt.Fprintln(os.Stderr, console.FormatCommandMessage(openURL)) + } + + timeout := time.NewTimer(bootstrapProfileManifestTimeout) + defer timeout.Stop() + + select { + case createdApp := <-resultCh: + return createdApp, nil + case err := <-errCh: + return nil, err + case <-timeout.C: + return nil, errors.New("timed out waiting for GitHub App creation to complete in the browser") + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// setupBootstrapGitHubAppDetails resolves the effective app owner (applying any override) +// and derives the app name, homepage URL, and description from the action and overrides. +func setupBootstrapGitHubAppDetails(ctx context.Context, repo, owner, ownerType string, action repositoryPackageBootstrapAction, overrides bootstrapGitHubAppOverrides) (string, string, string, string, string, error) { appOwner := owner appOwnerType := ownerType if overrides.Owner != "" { appOwner = overrides.Owner + var err error appOwnerType, err = bootstrapCheckOwnerType(ctx, appOwner) if err != nil { - return nil, err + return "", "", "", "", "", err } } - appName := deriveBootstrapAppName(repo, firstNonEmpty(overrides.Name, action.AppName)) homepageURL := strings.TrimSpace(firstNonEmpty(overrides.HomepageURL, action.HomepageURL)) if homepageURL == "" { @@ -526,23 +621,16 @@ func createBootstrapGitHubApp(ctx context.Context, repo, owner, repoName, ownerT if description == "" { description = "Bootstrap app for " + repo } + return appOwner, appOwnerType, appName, homepageURL, description, nil +} - resultCh := make(chan *bootstrapCreatedGitHubApp, 1) - errCh := make(chan error, 1) - server := &http.Server{} - redirectURL := fmt.Sprintf("http://%s/callback", listener.Addr().String()) - manifest := buildBootstrapGitHubAppManifest(action, appName, homepageURL, redirectURL, description) - bootstrapLog.Printf("Creating GitHub App via browser manifest flow: appOwner=%s, appName=%s, redirectURL=%s", appOwner, appName, redirectURL) - registrationURL := buildBootstrapGitHubAppRegistrationURL(appOwner, appOwnerType, state) - registrationPage, err := renderBootstrapGitHubAppRegistrationPage(registrationURL, manifest) - if err != nil { - return nil, fmt.Errorf("failed to encode GitHub App registration manifest for browser handoff; report this issue if it persists: %w", err) - } - +// buildBootstrapGitHubAppMux constructs the HTTP mux that handles the GitHub App manifest +// registration flow, including the /register page and the /callback that exchanges the code. +func buildBootstrapGitHubAppMux(ctx context.Context, csrfState, owner, ownerType, appName, description, registrationPage string, flowCh bootstrapGitHubAppFlowChannels) *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") - _, _ = w.Write([]byte(registrationPage)) + _, _ = io.WriteString(w, registrationPage) }) mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { returnedState := r.URL.Query().Get("state") @@ -550,15 +638,15 @@ func createBootstrapGitHubApp(ctx context.Context, repo, owner, repoName, ownerT if code == "" { http.Error(w, "Missing GitHub App manifest code.", http.StatusBadRequest) select { - case errCh <- errors.New("GitHub did not return an app manifest code"): + case flowCh.errCh <- errors.New("GitHub did not return an app manifest code"): default: } return } - if returnedState != state { + if returnedState != csrfState { http.Error(w, "State mismatch while creating the GitHub App.", http.StatusBadRequest) select { - case errCh <- errors.New("state mismatch while creating the GitHub App"): + case flowCh.errCh <- errors.New("state mismatch while creating the GitHub App"): default: } return @@ -567,7 +655,7 @@ func createBootstrapGitHubApp(ctx context.Context, repo, owner, repoName, ownerT if exchangeErr != nil { http.Error(w, "GitHub App creation completed, but gh aw could not exchange the manifest code.", http.StatusInternalServerError) select { - case errCh <- exchangeErr: + case flowCh.errCh <- exchangeErr: default: } return @@ -578,42 +666,11 @@ func createBootstrapGitHubApp(ctx context.Context, repo, owner, repoName, ownerT w.WriteHeader(http.StatusNoContent) } select { - case resultCh <- createdApp: + case flowCh.resultCh <- createdApp: default: } }) - server.Handler = mux - - go func() { - _ = server.Serve(listener) - }() - defer func() { - _ = server.Shutdown(context.Background()) - }() - - printBootstrapGitHubAppManifestReview(appOwner, manifest) - openURL := fmt.Sprintf("http://%s/register", listener.Addr().String()) - opened := false - if overrides.OpenBrowser { - opened = openBootstrapBrowser(openURL) - } - if !opened { - fmt.Fprintln(os.Stderr, console.FormatCommandMessage(openURL)) - } - - timeout := time.NewTimer(bootstrapProfileManifestTimeout) - defer timeout.Stop() - - select { - case createdApp := <-resultCh: - return createdApp, nil - case err := <-errCh: - return nil, err - case <-timeout.C: - return nil, errors.New("timed out waiting for GitHub App creation to complete in the browser") - case <-ctx.Done(): - return nil, ctx.Err() - } + return mux } func loadBootstrapGitHubAppOverrides() (bootstrapGitHubAppOverrides, error) {