Skip to content

Commit 3204c6c

Browse files
authored
[codex] Add GitHub release asset pipeline mode (#21)
* feat: add GitHub release asset pipeline mode * test: simplify Azure e2e workflow * fix: tighten Azure e2e pipeline behavior * test: cover tar gz release assets * docs: update agent plan follow-through [skip ci] * fix: disable Azure PR validation trigger
1 parent f48814b commit 3204c6c

12 files changed

Lines changed: 462 additions & 27 deletions

.github/workflows/ci.yml

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ jobs:
2828
${{
2929
github.event_name == 'pull_request' || startsWith(github.ref, 'refs/heads/')
3030
}}
31+
env:
32+
AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE: ${{ secrets.AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE }}
33+
SOURCE_BRANCH: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
3134
timeout-minutes: 10
3235
steps:
3336
- uses: actions/checkout@v5
@@ -38,20 +41,30 @@ jobs:
3841
- name: npm ci
3942
run: npm ci
4043
- name: Check Azure PAT
41-
env:
42-
AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE: ${{ secrets.AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE }}
4344
run: |
4445
if [ -z "${AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE:-}" ]; then
45-
echo "AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE is not configured; skipping Azure E2E."
46-
exit 0
46+
echo "AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE is required for Azure E2E."
47+
exit 1
4748
fi
48-
- name: Run Azure e2e fixture
49+
- name: Run Azure git e2e fixture
50+
run: |
51+
node scripts/runAzureFixture.js \
52+
--source-branch "$SOURCE_BRANCH" \
53+
--repo-url "https://github.com/favoyang/com.example.nuget-consumer" \
54+
--repo-branch "1.0.1" \
55+
--package-name "com.example.nuget-consumer" \
56+
--package-version "1.0.1" \
57+
--package-source "git"
58+
- name: Run Azure GitHub Release asset e2e fixture
4959
env:
50-
AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE: ${{ secrets.AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE }}
51-
SOURCE_BRANCH: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
60+
ASSET_URL: https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/test/fixtures/assets/com.example.release-asset-1.0.0.tgz
5261
run: |
53-
if [ -z "${AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE:-}" ]; then
54-
echo "AZURE_DEVOPS_TOKEN_OPENUPM_PIPELINE is not configured; skipping Azure E2E."
55-
exit 0
56-
fi
57-
node scripts/runAzureFixture.js --source-branch "$SOURCE_BRANCH"
62+
node scripts/runAzureFixture.js \
63+
--source-branch "$SOURCE_BRANCH" \
64+
--repo-url "https://github.com/${{ github.repository }}" \
65+
--repo-branch "${{ github.sha }}" \
66+
--package-name "com.example.release-asset" \
67+
--package-version "1.0.0" \
68+
--package-source "githubRelease" \
69+
--package-asset-url "$ASSET_URL" \
70+
--package-asset-name "com.example.release-asset-1.0.0.tgz"

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
- Before committing, run `npm run format:check`.
3333
- Run `npm run typecheck` when type-related changes are made (JSDoc/tsconfig).
3434
- When changing `azure-pipelines.yml`, preserve end-to-end behavior for Git submodules, Git LFS fetches, and log visibility for clone/LFS failures because OpenUPM parses those logs.
35+
- When working from a plan, after finishing any item, always state the next
36+
concrete step. Continue doing this until the plan is genuinely complete so
37+
the user does not need to ask "what's next?".
3538

3639
## Pipeline Guardrails
3740

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,37 @@ Prepare an npm service connection:
2929

3030
## Build with REST API
3131

32-
Required `parameters` payload:
32+
Required `parameters` payload for the default git source mode:
3333

3434
```json
3535
{
3636
"repoUrl": "https://...",
3737
"repoBranch": "master",
3838
"packageName": "com.yourcompany.package...",
3939
"packageVersion": "1.2.3",
40+
"packageSource": "git",
41+
"e2eTest": "false"
42+
}
43+
```
44+
45+
`packageSource` is optional and defaults to `"git"`. In git mode the pipeline
46+
clones `repoUrl` at `repoBranch`, finds `packageName`, packs it, and publishes
47+
the generated tarball.
48+
49+
For GitHub Release asset mode, OpenUPM queue resolves the release and asset
50+
before queueing Azure. The pipeline receives the exact public asset URL and
51+
filename, downloads that file without GitHub authentication, validates it, and
52+
publishes it unchanged:
53+
54+
```json
55+
{
56+
"repoUrl": "https://github.com/owner/repo",
57+
"repoBranch": "v1.2.3",
58+
"packageName": "com.yourcompany.package",
59+
"packageVersion": "1.2.3",
60+
"packageSource": "githubRelease",
61+
"packageAssetUrl": "https://github.com/owner/repo/releases/download/v1.2.3/com.yourcompany.package-1.2.3.tgz",
62+
"packageAssetName": "com.yourcompany.package-1.2.3.tgz",
4063
"e2eTest": "false"
4164
}
4265
```
@@ -65,7 +88,7 @@ curl --verbose \
6588
"https://dev.azure.com/openupm/openupm/_apis/build/builds?api-version=5.1" \
6689
--json '{
6790
"definition": { "id": 1 },
68-
"parameters": "{\"repoUrl\":\"https://...\",\"repoBranch\":\"master\",\"packageName\":\"com.yourcompany.package...\",\"packageVersion\":\"1.2.3\",\"e2eTest\":\"false\"}"
91+
"parameters": "{\"repoUrl\":\"https://...\",\"repoBranch\":\"master\",\"packageName\":\"com.yourcompany.package...\",\"packageVersion\":\"1.2.3\",\"packageSource\":\"git\",\"e2eTest\":\"false\"}"
6992
}'
7093
```
7194

@@ -79,7 +102,7 @@ testing changes from a non-default branch of `openupm-pipelines`:
79102
{
80103
"definition": { "id": 1 },
81104
"sourceBranch": "refs/heads/your-branch-name",
82-
"parameters": "{\"repoUrl\":\"https://...\",\"repoBranch\":\"master\",\"packageName\":\"com.yourcompany.package...\",\"packageVersion\":\"1.2.3\",\"e2eTest\":\"false\"}"
105+
"parameters": "{\"repoUrl\":\"https://...\",\"repoBranch\":\"master\",\"packageName\":\"com.yourcompany.package...\",\"packageVersion\":\"1.2.3\",\"packageSource\":\"git\",\"e2eTest\":\"false\"}"
83106
}
84107
```
85108

azure-pipelines.yml

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
name: $(Build.BuildId)
33

44
trigger: none
5+
pr: none
56

67
variables:
78
### context variables
89
# repoUrl: https://github.com/owner/reponame
910
# repoBranch: master
1011
# packageName: com.namespace.pkgname
1112
# packageVersion: semver
13+
# packageSource: git|githubRelease
14+
# packageAssetUrl: https://github.com/owner/repo/releases/download/tag/file.tgz
15+
# packageAssetName: file.tgz
1216
# e2eTest: true|false
1317

1418
### pre-defined variables
@@ -43,12 +47,28 @@ stages:
4347
echo repoBranch: $REPOBRANCH
4448
echo packageName: $PACKAGENAME
4549
echo packageVersion: $PACKAGEVERSION
50+
echo packageSource: ${PACKAGESOURCE:-git}
51+
echo packageAssetName: ${PACKAGEASSETNAME:-}
4652
echo e2eTest: ${E2ETEST:-false}
47-
[[ -z "$REPOURL" ]] && echo 'variable repoUrl not found' && exit 1
48-
[[ -z "$REPOBRANCH" ]] && echo 'variable repoBranch not found' && exit 1
4953
[[ -z "$PACKAGENAME" ]] && echo 'variable packageName not found' && exit 1
5054
[[ -z "$PACKAGEVERSION" ]] && echo 'variable packageVersion not found' && exit 1
51-
echo "##vso[build.updatebuildnumber]$(packageName)-$(packageVersion)-$(Build.BuildId)"
55+
if [ "${PACKAGESOURCE:-git}" = "git" ]; then
56+
[[ -z "$REPOURL" ]] && echo 'variable repoUrl not found' && exit 1
57+
[[ -z "$REPOBRANCH" ]] && echo 'variable repoBranch not found' && exit 1
58+
elif [ "${PACKAGESOURCE:-git}" = "githubRelease" ]; then
59+
[[ -z "${PACKAGEASSETURL:-}" ]] && echo 'variable packageAssetUrl not found' && exit 1
60+
[[ -z "${PACKAGEASSETNAME:-}" ]] && echo 'variable packageAssetName not found' && exit 1
61+
else
62+
echo "unsupported packageSource: ${PACKAGESOURCE:-}"
63+
exit 1
64+
fi
65+
PACKAGE_SOURCE_MODE="${PACKAGESOURCE:-git}"
66+
RUN_MODE="publish"
67+
if [ "${E2ETEST:-false}" = "true" ]; then
68+
RUN_MODE="e2e"
69+
fi
70+
echo "##vso[task.setvariable variable=packageSourceMode;]$PACKAGE_SOURCE_MODE"
71+
echo "##vso[build.updatebuildnumber]$RUN_MODE-$PACKAGE_SOURCE_MODE-$(packageName)-$(packageVersion)-$(Build.BuildId)"
5272
displayName: "Prepare context variables"
5373
5474
- script: |
@@ -83,6 +103,7 @@ stages:
83103
sudo apt-get install -y git git-lfs
84104
sudo git lfs install --system
85105
displayName: "Install Git tooling"
106+
condition: eq(variables['packageSourceMode'], 'git')
86107
87108
- script: |
88109
set -euo pipefail
@@ -103,6 +124,7 @@ stages:
103124
inputs:
104125
contents: $(repoDir)
105126
displayName: "Clean $(repoDir) folder"
127+
condition: eq(variables['packageSourceMode'], 'git')
106128

107129
- script: |
108130
set -euo pipefail
@@ -111,6 +133,7 @@ stages:
111133
git config --global url."https://github.com/".insteadOf git@github.com:
112134
git config --global url."https://".insteadOf git://
113135
displayName: "Prepare isolated Git config"
136+
condition: eq(variables['packageSourceMode'], 'git')
114137
env:
115138
HOME: $(gitHomeDir)
116139
XDG_CONFIG_HOME: $(gitConfigDir)
@@ -124,6 +147,7 @@ stages:
124147
clone --recursive --shallow-submodules --depth 1 --branch "$(repoBranch)" "$(repoUrl)" "$(repoDir)"
125148
git -C "$(repoDir)" config --local core.hooksPath /dev/null
126149
displayName: "Clone repository"
150+
condition: eq(variables['packageSourceMode'], 'git')
127151
env:
128152
HOME: $(gitHomeDir)
129153
XDG_CONFIG_HOME: $(gitConfigDir)
@@ -134,6 +158,7 @@ stages:
134158
echo "Fetching Git LFS objects for $(repoDir)"
135159
git lfs pull
136160
displayName: "Fetch Git LFS"
161+
condition: eq(variables['packageSourceMode'], 'git')
137162
env:
138163
HOME: $(gitHomeDir)
139164
XDG_CONFIG_HOME: $(gitConfigDir)
@@ -144,21 +169,33 @@ stages:
144169
echo "Fetching Git LFS objects for submodules in $(repoDir)"
145170
git submodule foreach --recursive 'git lfs pull'
146171
displayName: "Fetch submodule Git LFS"
172+
condition: eq(variables['packageSourceMode'], 'git')
147173
env:
148174
HOME: $(gitHomeDir)
149175
XDG_CONFIG_HOME: $(gitConfigDir)
150176
151177
- script: |
152178
set -euo pipefail
153179
echo "Computing dist-tag for $(packageName) against $(publishRegistryUrl)"
154-
LATEST_VERSION=$(npm --registry="$(publishRegistryUrl)" show "$(packageName)" version) || LATEST_VERSION=0.0.0
180+
NPM_SHOW_LOG="$(Agent.TempDirectory)/npm-show-version.log"
181+
if npm --registry="$(publishRegistryUrl)" show "$(packageName)" version >"$NPM_SHOW_LOG" 2>&1; then
182+
LATEST_VERSION=$(cat "$NPM_SHOW_LOG")
183+
elif grep -Eq "(E404|404 Not Found)" "$NPM_SHOW_LOG"; then
184+
cat "$NPM_SHOW_LOG"
185+
LATEST_VERSION=0.0.0
186+
else
187+
cat "$NPM_SHOW_LOG"
188+
echo "Failed to read package version from $(publishRegistryUrl)"
189+
exit 1
190+
fi
155191
[[ -z "$LATEST_VERSION" ]] && LATEST_VERSION=0.0.0
156192
echo LATEST_VERSION: $LATEST_VERSION
157193
echo packageVersion: $(packageVersion)
158194
DIST_TAG=$(node getDistTag.js "$(packageVersion)" "$LATEST_VERSION")
159195
echo DIST_TAG: $DIST_TAG
160196
echo "##vso[task.setvariable variable=distTag;]$DIST_TAG"
161197
displayName: "Set distTag variable"
198+
condition: ne(variables['e2eTest'], 'true')
162199
163200
- script: |
164201
set -euo pipefail
@@ -169,6 +206,7 @@ stages:
169206
echo "PACKAGEFOLDER: $PACKAGEFOLDER"
170207
echo "##vso[task.setvariable variable=packageFolder;]$PACKAGEFOLDER"
171208
displayName: "Detect package.json location"
209+
condition: eq(variables['packageSourceMode'], 'git')
172210
173211
- script: |
174212
set -euo pipefail
@@ -177,6 +215,7 @@ stages:
177215
npm pkg set publishConfig.registry="$(publishRegistryUrl)"
178216
cat package.json
179217
displayName: "Override publishConfig"
218+
condition: eq(variables['packageSourceMode'], 'git')
180219
181220
- script: |
182221
set -euo pipefail
@@ -199,6 +238,47 @@ stages:
199238
"$ARTIFACT_DIR/metadata.json"
200239
cat "$ARTIFACT_DIR/metadata.json"
201240
displayName: "Pack package tarball"
241+
condition: eq(variables['packageSourceMode'], 'git')
242+
243+
- script: |
244+
set -euo pipefail
245+
ARTIFACT_DIR="$(Build.ArtifactStagingDirectory)/$(artifactName)"
246+
mkdir -p "$ARTIFACT_DIR"
247+
TARBALL_FILE="$(basename "$(packageAssetName)")"
248+
if [ "$TARBALL_FILE" != "$(packageAssetName)" ]; then
249+
echo "packageAssetName must be a filename, not a path"
250+
exit 1
251+
fi
252+
TARBALL_PATH="$ARTIFACT_DIR/$TARBALL_FILE"
253+
TEMP_TARBALL_PATH="$TARBALL_PATH.download"
254+
echo "Downloading GitHub Release asset: $(packageAssetName)"
255+
HTTP_STATUS=$(curl --location --silent --show-error --write-out "%{http_code}" --output "$TEMP_TARBALL_PATH" "$(packageAssetUrl)") || {
256+
echo "GITHUB_RELEASE_ASSET_DOWNLOAD_FAILED"
257+
rm -f "$TEMP_TARBALL_PATH"
258+
exit 1
259+
}
260+
if [ "$HTTP_STATUS" = "404" ]; then
261+
echo "GITHUB_RELEASE_ASSET_DOWNLOAD_NOT_FOUND"
262+
rm -f "$TEMP_TARBALL_PATH"
263+
exit 1
264+
fi
265+
if ! [[ "$HTTP_STATUS" =~ ^2[0-9][0-9]$ ]]; then
266+
echo "GITHUB_RELEASE_ASSET_DOWNLOAD_FAILED"
267+
echo "HTTP status: $HTTP_STATUS"
268+
rm -f "$TEMP_TARBALL_PATH"
269+
exit 1
270+
fi
271+
mv "$TEMP_TARBALL_PATH" "$TARBALL_PATH"
272+
echo "Validating downloaded asset extension, archive structure, package/package.json, name, and version"
273+
node "$(Build.SourcesDirectory)/createPackageArtifactMetadataFromTarball.js" \
274+
"$TARBALL_PATH" \
275+
"$(packageName)" \
276+
"$(packageVersion)" \
277+
"$(distTag)" \
278+
"$ARTIFACT_DIR/metadata.json"
279+
cat "$ARTIFACT_DIR/metadata.json"
280+
displayName: "Download GitHub Release asset"
281+
condition: eq(variables['packageSourceMode'], 'githubRelease')
202282
203283
- publish: $(Build.ArtifactStagingDirectory)/$(artifactName)
204284
artifact: $(artifactName)

0 commit comments

Comments
 (0)