Skip to content

Commit aaf4962

Browse files
fix(release): harden toolkit/react release asset staging (#198)
* fix(release): stage assets before publishing * fix(release): harden staged asset validation
1 parent 6598885 commit aaf4962

File tree

6 files changed

+403
-86
lines changed

6 files changed

+403
-86
lines changed

.github/workflows/release-contract.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ on:
1010
- 'docs/**'
1111
- 'package.json'
1212
- 'pnpm-lock.yaml'
13+
- 'packages/toolkit/gulpfile.js'
1314
- 'packages/toolkit/package.json'
1415
- 'packages/react-components/package.json'
1516
- 'packages/react-components/README.md'
17+
- 'packages/react-components/scripts/**'
1618
- 'scripts/release/**'
1719

1820
jobs:
@@ -38,5 +40,5 @@ jobs:
3840
- name: Validate docs and release templates
3941
run: pnpm docs:release-contract
4042

41-
- name: Smoke test current release artifacts
43+
- name: Prepare and smoke test current release artifacts
4244
run: pnpm smoke:release-artifacts

.github/workflows/release.yml

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,57 +41,53 @@ jobs:
4141
- name: Validate release contract docs
4242
run: pnpm docs:release-contract
4343

44-
- name: Determine package and version
44+
- name: Determine package from tag
4545
id: package_info
4646
run: |
4747
TAG=${GITHUB_REF#refs/tags/}
4848
if [[ $TAG == react-v* ]]; then
4949
echo "PACKAGE=react-components" >> $GITHUB_OUTPUT
50-
echo "PACKAGE_SCOPE=@ourfuturehealth/react-components" >> $GITHUB_OUTPUT
5150
echo "PACKAGE_DIR=packages/react-components" >> $GITHUB_OUTPUT
52-
echo "VERSION=${TAG#react-v}" >> $GITHUB_OUTPUT
5351
elif [[ $TAG == toolkit-v* ]]; then
5452
echo "PACKAGE=toolkit" >> $GITHUB_OUTPUT
55-
echo "PACKAGE_SCOPE=@ourfuturehealth/toolkit" >> $GITHUB_OUTPUT
5653
echo "PACKAGE_DIR=packages/toolkit" >> $GITHUB_OUTPUT
57-
echo "VERSION=${TAG#toolkit-v}" >> $GITHUB_OUTPUT
5854
else
5955
# Assume v* tags are toolkit (backward compatibility)
6056
echo "PACKAGE=toolkit" >> $GITHUB_OUTPUT
61-
echo "PACKAGE_SCOPE=@ourfuturehealth/toolkit" >> $GITHUB_OUTPUT
6257
echo "PACKAGE_DIR=packages/toolkit" >> $GITHUB_OUTPUT
63-
echo "VERSION=${TAG#v}" >> $GITHUB_OUTPUT
6458
fi
6559
66-
- name: Build release artifacts
60+
- name: Prepare release assets
61+
id: release_assets
6762
run: |
68-
if [ "${{ steps.package_info.outputs.PACKAGE }}" == "toolkit" ]; then
69-
pnpm --filter=@ourfuturehealth/toolkit run zip
63+
./scripts/release/prepare-release-artifacts.sh \
64+
"${{ steps.package_info.outputs.PACKAGE }}" \
65+
--stage-dir "${RUNNER_TEMP}/release-assets-${{ github.run_id }}-${{ github.run_attempt }}-${{ steps.package_info.outputs.PACKAGE }}" \
66+
--github-output "${GITHUB_OUTPUT}"
67+
68+
- name: Validate tag version matches package version
69+
run: |
70+
TAG="${GITHUB_REF_NAME}"
71+
if [[ "${TAG}" == react-v* ]]; then
72+
EXPECTED_VERSION="${TAG#react-v}"
73+
elif [[ "${TAG}" == toolkit-v* ]]; then
74+
EXPECTED_VERSION="${TAG#toolkit-v}"
7075
else
71-
pnpm --filter=@ourfuturehealth/react-components run build
76+
EXPECTED_VERSION="${TAG#v}"
7277
fi
7378
74-
- name: Pack release tarball
75-
id: tarball
76-
run: |
77-
TARBALL_NAME=$(npm pack "./${{ steps.package_info.outputs.PACKAGE_DIR }}" --ignore-scripts | tail -n 1)
78-
echo "ASSET_NAME=${TARBALL_NAME}" >> $GITHUB_OUTPUT
79-
echo "ASSET_PATH=${GITHUB_WORKSPACE}/${TARBALL_NAME}" >> $GITHUB_OUTPUT
79+
ACTUAL_VERSION="${{ steps.release_assets.outputs.VERSION }}"
8080
81-
- name: Resolve toolkit asset details
82-
if: steps.package_info.outputs.PACKAGE == 'toolkit'
83-
id: toolkit_asset
84-
run: |
85-
ASSET_PATH=$(ls -1 ./packages/toolkit/dist/ofh-design-system-toolkit-*.zip | head -n 1)
86-
ASSET_NAME=$(basename "$ASSET_PATH")
87-
echo "ASSET_PATH=$ASSET_PATH" >> $GITHUB_OUTPUT
88-
echo "ASSET_NAME=$ASSET_NAME" >> $GITHUB_OUTPUT
81+
if [[ "${EXPECTED_VERSION}" != "${ACTUAL_VERSION}" ]]; then
82+
echo "Tag version '${EXPECTED_VERSION}' does not match package version '${ACTUAL_VERSION}' for tag '${TAG}'." >&2
83+
exit 1
84+
fi
8985
9086
- name: Smoke test release tarball
9187
run: |
9288
./scripts/release/smoke-package-tarball.sh \
9389
--package-dir "${{ github.workspace }}/${{ steps.package_info.outputs.PACKAGE_DIR }}" \
94-
--tarball "${{ steps.tarball.outputs.ASSET_PATH }}" \
90+
--tarball "${{ steps.release_assets.outputs.TARBALL_PATH }}" \
9591
--managers 'yarn,npm,pnpm'
9692
9793
- name: Render release notes
@@ -102,15 +98,15 @@ jobs:
10298
./scripts/release/render-release-notes.sh \
10399
--package "${{ steps.package_info.outputs.PACKAGE }}" \
104100
--tag "${GITHUB_REF_NAME}" \
105-
--version "${{ steps.package_info.outputs.VERSION }}" \
106-
--tarball "${{ steps.tarball.outputs.ASSET_NAME }}" \
107-
--zip "${{ steps.toolkit_asset.outputs.ASSET_NAME }}" > "${NOTES_PATH}"
101+
--version "${{ steps.release_assets.outputs.VERSION }}" \
102+
--tarball "${{ steps.release_assets.outputs.TARBALL_NAME }}" \
103+
--zip "${{ steps.release_assets.outputs.ZIP_NAME }}" > "${NOTES_PATH}"
108104
else
109105
./scripts/release/render-release-notes.sh \
110106
--package "${{ steps.package_info.outputs.PACKAGE }}" \
111107
--tag "${GITHUB_REF_NAME}" \
112-
--version "${{ steps.package_info.outputs.VERSION }}" \
113-
--tarball "${{ steps.tarball.outputs.ASSET_NAME }}" > "${NOTES_PATH}"
108+
--version "${{ steps.release_assets.outputs.VERSION }}" \
109+
--tarball "${{ steps.release_assets.outputs.TARBALL_NAME }}" > "${NOTES_PATH}"
114110
fi
115111
echo "NOTES_PATH=${NOTES_PATH}" >> $GITHUB_OUTPUT
116112
@@ -136,14 +132,14 @@ jobs:
136132
env:
137133
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
138134
run: |
139-
gh release upload "${GITHUB_REF_NAME}" "${{ steps.toolkit_asset.outputs.ASSET_PATH }}" \
135+
gh release upload "${GITHUB_REF_NAME}" "${{ steps.release_assets.outputs.ZIP_PATH }}" \
140136
--clobber \
141137
--repo "${GITHUB_REPOSITORY}"
142138
143139
- name: Upload package tarball asset
144140
env:
145141
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
146142
run: |
147-
gh release upload "${GITHUB_REF_NAME}" "${{ steps.tarball.outputs.ASSET_PATH }}" \
143+
gh release upload "${GITHUB_REF_NAME}" "${{ steps.release_assets.outputs.TARBALL_PATH }}" \
148144
--clobber \
149145
--repo "${GITHUB_REPOSITORY}"

docs/release-process.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,11 @@ When a release tag is pushed, [.github/workflows/release.yml](../.github/workflo
7474
1. installs dependencies with pnpm
7575
2. runs linting and tests
7676
3. validates the release-contract docs
77-
4. builds the package being released
78-
5. packs the package with `npm pack --ignore-scripts`
79-
6. smoke-tests the tarball with Yarn 1, npm, and pnpm
80-
7. renders release notes with the tarball install URL
81-
8. creates or updates the GitHub release
82-
9. uploads release assets
77+
4. prepares the package release assets in a dedicated staging directory outside the package tree
78+
5. smoke-tests the tarball with Yarn 1, npm, and pnpm
79+
6. renders release notes with the tarball install URL
80+
7. creates or updates the GitHub release
81+
8. uploads release assets
8382

8483
Toolkit releases upload:
8584

@@ -145,6 +144,7 @@ pnpm smoke:release-artifacts
145144
```
146145

147146
This validates the public docs and then tests the current branch tarballs with Yarn 1, npm, and pnpm.
147+
It uses the same staged-asset preparation path as the tag-driven release workflow, so PR validation exercises the same artifact handoff that production releases rely on.
148148

149149
For local iteration you can scope this wrapper to one package and one or more package managers:
150150

@@ -153,6 +153,17 @@ For local iteration you can scope this wrapper to one package and one or more pa
153153
./scripts/release/smoke-current-release-artifacts.sh react-components --managers npm,pnpm
154154
```
155155

156+
### Prepare release assets directly
157+
158+
If you need to inspect the exact staged assets before tagging:
159+
160+
```bash
161+
./scripts/release/prepare-release-artifacts.sh toolkit
162+
./scripts/release/prepare-release-artifacts.sh react-components
163+
```
164+
165+
The script prints the package, version, and staged asset paths. Toolkit releases must stage both the versioned `.zip` and the package tarball before the workflow can create or update the GitHub release.
166+
156167
### Test unreleased changes locally in another consumer
157168

158169
Toolkit:
@@ -184,6 +195,18 @@ Common causes:
184195
- smoke test failures for the tarball install contract
185196
- stale docs or release templates that still mention the old git-subdirectory syntax
186197

198+
### Why release assets are staged outside the package tree
199+
200+
The release workflow deliberately copies built assets into a dedicated staging directory before it runs `npm pack`.
201+
202+
This is intentional. CI previously showed the toolkit `createZip` step completing successfully, then failed later when the workflow tried to rediscover the versioned zip from `packages/toolkit/dist/`. Local reproduction did not show the same disappearance, and the local/CI npm versions differed, so the release flow now treats the package working tree as unstable across later packaging steps.
203+
204+
The staged asset copy is the source of truth for:
205+
206+
- tarball smoke testing
207+
- toolkit compiled-file uploads
208+
- release note asset references
209+
187210
### Consumer installation failed
188211

189212
Check that:

0 commit comments

Comments
 (0)