-
Notifications
You must be signed in to change notification settings - Fork 0
110 lines (110 loc) · 5.93 KB
/
Copy pathrelease.yml
File metadata and controls
110 lines (110 loc) · 5.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
name: Release
on:
workflow_dispatch:
inputs:
source_run:
description: Successful CI run ID
required: true
release_tag:
description: Existing draft release tag bound to the verified commit
required: true
permissions:
actions: read
contents: write
concurrency:
group: release-upload-${{ inputs.release_tag }}
cancel-in-progress: false
jobs:
upload:
if: github.repository == 'computer-mcp/plugin-codex' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
SOURCE_RUN: ${{ inputs.source_run }}
RELEASE_TAG: ${{ inputs.release_tag }}
steps:
- name: Verify accepted source and commit-bound draft
id: source
shell: bash
run: |
set -euo pipefail
[[ "$SOURCE_RUN" =~ ^[0-9]+$ ]]
gh api "repos/$GH_REPO/actions/runs/$SOURCE_RUN" > run.json
jq -e --arg repo "$GH_REPO" '
.status == "completed" and .conclusion == "success" and
.event == "push" and .head_branch == "master" and
.path == ".github/workflows/ci.yml" and
.head_repository.full_name == $repo' run.json > /dev/null
source_sha=$(jq -r .head_sha run.json)
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
gh api "repos/$GH_REPO/git/ref/tags/$RELEASE_TAG" > tag-ref.json
[[ $(jq -r .object.type tag-ref.json) == tag ]]
tag_object=$(jq -r .object.sha tag-ref.json)
gh api "repos/$GH_REPO/git/tags/$tag_object" > tag.json
jq -e --arg sha "$source_sha" '.object.type == "commit" and .object.sha == $sha' tag.json > /dev/null
gh api --paginate "repos/$GH_REPO/releases?per_page=100" --slurp > releases.json
jq -e --arg tag "$RELEASE_TAG" --arg sha "$source_sha" '
[ .[][] | select(.tag_name == $tag) ] |
if length == 1 and .[0].draft and .[0].target_commitish == $sha
then .[0] else error("Expected one commit-bound draft release") end
' releases.json > release.json
release_id=$(jq -r .id release.json)
echo "sha=$source_sha" >> "$GITHUB_OUTPUT"
echo "release_id=$release_id" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ steps.source.outputs.sha }}
persist-credentials: false
- name: Verify and promote every declared native archive
shell: bash
env:
SOURCE_SHA: ${{ steps.source.outputs.sha }}
RELEASE_ID: ${{ steps.source.outputs.release_id }}
run: |
set -euo pipefail
python3 - <<'PYTHON'
import json, os, re, tomllib
from pathlib import Path
manifest = tomllib.loads(Path("computer-mcp-plugin.toml").read_text())
if manifest["id"] != "codex" or "v" + manifest["version"] != os.environ["RELEASE_TAG"]:
raise SystemExit("Release tag does not match the accepted plugin manifest")
names = [item["name"] for item in manifest["compatibility"]["artifacts"]]
if not names or len(set(names)) != len(names) or any(not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]*\.zip", name) for name in names):
raise SystemExit("Expected unique declared native archives")
Path("archive-names.json").write_text(json.dumps(names))
PYTHON
while IFS= read -r archive; do
name=${archive%.zip}
gh run download "$SOURCE_RUN" --name "$name-$SOURCE_SHA" --dir "artifacts/$name"
python3 Scripts/check-package.py --archive "artifacts/$name/$archive" --receipt "artifacts/$name/receipt.json" > "artifacts/$name/verification.json"
done < <(jq -r '.[]' archive-names.json)
# Upload only after every declared archive passes its exact source-bound check.
upload_asset() {
local file=$1 name=$2 content_type=$3 expected existing
expected=$(sha256sum "$file" | cut -d' ' -f1)
gh api --paginate "repos/$GH_REPO/releases/$RELEASE_ID/assets?per_page=100" --slurp | jq 'add' > assets.json
existing=$(jq -er --arg name "$name" '[.[] | select(.name == $name)] | if length <= 1 then (.[0].id // "") else error("Duplicate release asset") end' assets.json)
if [[ -n "$existing" ]]; then
jq -e --argjson id "$existing" --arg digest "sha256:$expected" '
.[] | select(.id == $id) |
.state == "starter" or (.state == "uploaded" and .digest == $digest)
' assets.json > /dev/null
if jq -e --argjson id "$existing" '.[] | select(.id == $id) | .state == "uploaded"' assets.json > /dev/null; then
return
fi
gh api "repos/$GH_REPO/releases/$RELEASE_ID" | jq -e --arg tag "$RELEASE_TAG" --arg sha "$SOURCE_SHA" '.draft and .tag_name == $tag and .target_commitish == $sha' > /dev/null
gh api --method DELETE "repos/$GH_REPO/releases/assets/$existing"
fi
# Re-read immediately before upload; published releases are immutable.
gh api "repos/$GH_REPO/releases/$RELEASE_ID" | jq -e --arg tag "$RELEASE_TAG" --arg sha "$SOURCE_SHA" '.draft and .tag_name == $tag and .target_commitish == $sha' > /dev/null
gh api --method POST "https://uploads.github.com/repos/$GH_REPO/releases/$RELEASE_ID/assets?name=$name" \
--input "$file" -H "Content-Type: $content_type" > uploaded.json
jq -e --arg digest "sha256:$expected" '.state == "uploaded" and .digest == $digest' uploaded.json > /dev/null
}
while IFS= read -r archive; do
name=${archive%.zip}
upload_asset "artifacts/$name/$archive" "$archive" application/zip
upload_asset "artifacts/$name/receipt.json" "$name.receipt.json" application/json
done < <(jq -r '.[]' archive-names.json)