-
Notifications
You must be signed in to change notification settings - Fork 4
fix(ci): the suite was green only on the maintainer's Mac (#490) #494
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b309c80
8e83c26
4883955
17ac3a1
528a28d
9391bf3
fc2976c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| # scripts/release.sh 0.3.0 --yes # no confirmation prompt | ||
| # scripts/release.sh 0.3.0 --dry-run # print every step, change nothing | ||
| # scripts/release.sh 0.3.0 --require-contract # a skipped real-cmux gate aborts the release | ||
| # scripts/release.sh 0.3.0 --require-ci # a non-green CI on HEAD aborts the release | ||
| # | ||
| # Steps: clean-tree + green build/tests gate → bump package.json → commit + | ||
| # push main → tag vX.Y.Z + push tag → update formula url+sha256 in the | ||
|
|
@@ -30,11 +31,15 @@ VERSION="${1:-}" | |
| YES=0 | ||
| DRY=0 | ||
| REQUIRE_CONTRACT=0 | ||
| REQUIRE_CI=0 | ||
| CI_CONCLUSION="unknown" | ||
| CI_COMMIT_LABEL="HEAD" | ||
| for arg in "${@:2}"; do | ||
| case "$arg" in | ||
| --yes) YES=1 ;; | ||
| --dry-run) DRY=1 ;; | ||
| --require-contract) REQUIRE_CONTRACT=1 ;; | ||
| --require-ci) REQUIRE_CI=1 ;; | ||
| *) echo "unknown flag: $arg" >&2; exit 2 ;; | ||
| esac | ||
| done | ||
|
|
@@ -51,6 +56,19 @@ trap cleanup EXIT | |
| die() { echo "release: $*" >&2; exit 1; } | ||
| run() { if [ "$DRY" -eq 1 ]; then printf 'DRY %s\n' "$*"; else eval "$@"; fi; } | ||
|
|
||
| # In-place sed that works on BSD *and* GNU. `sed -i ''` is BSD-only: GNU sed | ||
| # reads the '' as the script and the expression as a filename, exits 2, and | ||
| # takes this script down with it — which is why every Linux CI run of the | ||
| # release-receipt tests failed while the same tests passed on a Mac. | ||
| # Writes back through the ORIGINAL file rather than mv-ing the tmpfile over it: | ||
| # mv would hand the target the tmpfile's 0600 and owner, a mode change `sed -i` | ||
| # never makes. | ||
| sed_inplace() { | ||
| local expression="$1" file="$2" tmp | ||
| tmp="$(mktemp)" | ||
| sed -E "$expression" "$file" >"$tmp" && cat "$tmp" >"$file" && rm -f "$tmp" | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Comment on lines
+67
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium A failed or interrupted - local expression="$1" file="$2" tmp
- tmp="$(mktemp)"
- sed -E "$expression" "$file" >"$tmp" && cat "$tmp" >"$file" && rm -f "$tmp"
+ local expression="$1" file="$2" tmp preserved
+ tmp="$(mktemp "${file}.XXXXXX")" || return 1
+ preserved="$(mktemp "${file}.XXXXXX")" || { rm -f "$tmp"; return 1; }
+ if ! sed -E "$expression" "$file" >"$tmp" ||
+ ! cp -p "$file" "$preserved" || ! cat "$tmp" >"$preserved"; then
+ rm -f "$tmp" "$preserved"
+ return 1
+ fi
+ rm -f "$tmp"
+ if ! mv -f "$preserved" "$file"; then
+ rm -f "$preserved"
+ return 1
+ fi🚀 Reply "fix it for me" or copy this AI Prompt for your agent: |
||
|
|
||
| # Receipt writes are never allowed to fail a release: the ledger records the | ||
| # release, it does not gate it. | ||
| receipt() { | ||
|
|
@@ -86,6 +104,37 @@ if [ "$DRY" -ne 1 ]; then | |
| receipt_record "gates.require_contract" "$([ "$REQUIRE_CONTRACT" -eq 1 ] && echo true || echo false)" | ||
| fi | ||
|
|
||
| # --- CI status of the commit being released (#490) ------------------------- | ||
| # Six tagged releases shipped while publish.yml failed on every single run and | ||
| # cmuxlayer never reached npm at all. Nothing in the release said so. The receipt | ||
| # now carries CI's verdict on the released commit, and the banner prints it, so | ||
| # "the release looked clean" can never again mean "nobody opened the log". | ||
| if [ "$DRY" -eq 1 ]; then | ||
| printf 'DRY %s\n' "read CI status for HEAD" | ||
| else | ||
| # `gh run list --commit` needs the FULL sha; an abbreviated one matches nothing | ||
| # and would read as `unknown`. Never loosen this to a short sha. | ||
| RELEASE_COMMIT="$(git rev-parse HEAD)" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High
🚀 Reply "fix it for me" or copy this AI Prompt for your agent: |
||
| CI_COMMIT_LABEL="$RELEASE_COMMIT" | ||
| # An unusable gh -- absent, unauthenticated, offline -- reads as unknown. | ||
| # Only a real `success` from a real run is allowed to look green. | ||
| CI_CONCLUSION="$(gh run list --commit "$RELEASE_COMMIT" --workflow ci.yml \ | ||
| --limit 1 --json conclusion --jq '.[0].conclusion' 2>/dev/null || true)" | ||
| [ -n "$CI_CONCLUSION" ] || CI_CONCLUSION="unknown" | ||
| receipt_record "gates.ci" "$CI_CONCLUSION" | ||
| # Name the commit the verdict is ABOUT. The read happens before the version | ||
| # bump, so this is the commit the release was cut from -- not the tag's commit. | ||
| # In the one file whose purpose is that a release cannot look cleaner than it | ||
| # is, "which commit" cannot be left to inference. | ||
| receipt_record "gates.ci_commit" "$RELEASE_COMMIT" | ||
| if [ "$CI_CONCLUSION" != "success" ]; then | ||
| if [ "$REQUIRE_CI" -eq 1 ]; then | ||
| die "--require-ci: CI for $RELEASE_COMMIT is $CI_CONCLUSION, not success" | ||
| fi | ||
| echo "release: WARNING — CI for $RELEASE_COMMIT is $CI_CONCLUSION; recorded in the receipt" | ||
| fi | ||
| fi | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| echo "release: gating on typecheck + tests…" | ||
| run "bun run typecheck" | ||
| receipt_record "gates.typecheck" "pass" | ||
|
|
@@ -159,7 +208,7 @@ if [ "$YES" -ne 1 ] && [ "$DRY" -ne 1 ]; then | |
| fi | ||
|
|
||
| # --- bump + commit + tag (cmuxlayer) -------------------------------------- | ||
| run "sed -i '' -E 's/^( \"version\": \")[^\"]+(\",)\$/\\1$VERSION\\2/' package.json" | ||
| run "sed_inplace 's/^( \"version\": \")[^\"]+(\",)\$/\\1$VERSION\\2/' package.json" | ||
| run "git commit -aqm 'chore: release $TAG'" | ||
| run "git push origin main" | ||
| run "git tag -a '$TAG' -m 'cmuxlayer $TAG'" | ||
|
|
@@ -189,8 +238,8 @@ receipt_record "artifact.url" "$URL" | |
| receipt_record "artifact.sha256" "$SHA" | ||
|
|
||
| # --- bump formula (homebrew-layers) --------------------------------------- | ||
| run "sed -i '' -E 's|archive/refs/tags/v[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz|archive/refs/tags/$TAG.tar.gz|' '$FORMULA'" | ||
| run "sed -i '' -E 's|^ sha256 \"[0-9a-f]{64}\"| sha256 \"$SHA\"|' '$FORMULA'" | ||
| run "sed_inplace 's|archive/refs/tags/v[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz|archive/refs/tags/$TAG.tar.gz|' '$FORMULA'" | ||
| run "sed_inplace 's|^ sha256 \"[0-9a-f]{64}\"| sha256 \"$SHA\"|' '$FORMULA'" | ||
| run "brew audit etanhey/layers/cmuxlayer || true" | ||
| run "git -C '$TAP_DIR' commit -aqm 'cmuxlayer $TAG'" | ||
| run "git -C '$TAP_DIR' push origin main" | ||
|
|
@@ -246,6 +295,7 @@ fi | |
| cat <<EOF | ||
|
|
||
| release: done — cmuxlayer $TAG is tagged and the formula is bumped. | ||
| CI: $CI_CONCLUSION (ci.yml on $CI_COMMIT_LABEL — the commit this release was cut from) | ||
| Receipt: $RECEIPT_LABEL | ||
| Next (on EACH Mac — each run appends its own install evidence to the receipt): | ||
| $REPO_DIR/scripts/release-verify.sh "$VERSION" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { mkdirSync, rmSync } from "node:fs"; | ||
| import { join } from "node:path"; | ||
|
|
||
| /** | ||
| * One temp root per suite RUN, removed when the run ends. | ||
| * | ||
| * See tests/vitest.setup.ts for why. Isolating per run — not per worker — is | ||
| * exactly right: within a run vitest never executes one test file twice at once, | ||
| * so the fixed fixture names only collide ACROSS runs. | ||
| */ | ||
| const root = join("/tmp", `cmuxlayer-vitest-${process.pid}`); | ||
|
|
||
| export function setup(): void { | ||
| mkdirSync(root, { recursive: true }); | ||
| process.env.CMUXLAYER_TEST_TMP_ROOT = root; | ||
| } | ||
|
|
||
| export function teardown(): void { | ||
| rmSync(root, { recursive: true, force: true }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: EtanHey/cmuxlayer
Length of output: 802
🏁 Script executed:
Repository: EtanHey/cmuxlayer
Length of output: 1781
🏁 Script executed:
Repository: EtanHey/cmuxlayer
Length of output: 4817
Pin Bun setup and disable its executable cache.
Pin
oven-sh/setup-bun@v2tooven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6. Setno-cache: true; its default isfalse.🧰 Tools
🪛 zizmor (1.29.0)
[error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 25-25: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
🤖 Prompt for AI Agents
Source: Linters/SAST tools