From 25aa850b569344e7332429ac138c6c9dc005b615 Mon Sep 17 00:00:00 2001 From: Peter Ryszkiewicz Date: Sun, 7 Jun 2026 09:23:37 -0500 Subject: [PATCH] fix(ci): expose failed upstream sync results --- package.json | 2 +- .../github-actions/upstream-sync-sh.test.ts | 104 ++++++++++++++++++ scripts/github-actions/upstream-sync.sh | 17 +++ 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 scripts/github-actions/upstream-sync-sh.test.ts diff --git a/package.json b/package.json index 3472740c..907e8f2b 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "openclaw:prompts:check": "bun scripts/sync-openclaw-prompts.ts --check", "preview": "vite preview", "typecheck": "tsc --noEmit", - "test:deploy": "bun test src/lib/deployment-config.test.ts scripts/lib/build-info.test.ts scripts/lib/build-timestamp.test.ts scripts/lib/deploy-artifact.test.ts scripts/lib/aws-deploy.test.ts scripts/lib/deploy-setup.test.ts scripts/lib/deployment-command-plan.test.ts scripts/lib/deployment-tracked-files.test.ts scripts/lib/provider-deploy.test.ts scripts/lib/readme-deploy-urls.test.ts scripts/lib/live-deploy-verify.test.ts scripts/lib/github-pages.test.ts scripts/lib/tracked-deployment-config.test.ts scripts/lib/upstream-sync.test.ts scripts/github-actions/deploy-production-workflow.test.ts scripts/github-actions/nightly-follower-history-workflow.test.ts scripts/github-actions/upstream-sync-summary.test.ts scripts/enrichment/generated-metadata.test.ts scripts/enrichment/public-augmentation.test.ts scripts/enrichment/public-browser.test.ts scripts/clean-public-build-artifacts.test.ts scripts/reset-fork-template.test.ts", + "test:deploy": "bun test src/lib/deployment-config.test.ts scripts/lib/build-info.test.ts scripts/lib/build-timestamp.test.ts scripts/lib/deploy-artifact.test.ts scripts/lib/aws-deploy.test.ts scripts/lib/deploy-setup.test.ts scripts/lib/deployment-command-plan.test.ts scripts/lib/deployment-tracked-files.test.ts scripts/lib/provider-deploy.test.ts scripts/lib/readme-deploy-urls.test.ts scripts/lib/live-deploy-verify.test.ts scripts/lib/github-pages.test.ts scripts/lib/tracked-deployment-config.test.ts scripts/lib/upstream-sync.test.ts scripts/github-actions/deploy-production-workflow.test.ts scripts/github-actions/nightly-follower-history-workflow.test.ts scripts/github-actions/upstream-sync-sh.test.ts scripts/github-actions/upstream-sync-summary.test.ts scripts/enrichment/generated-metadata.test.ts scripts/enrichment/public-augmentation.test.ts scripts/enrichment/public-browser.test.ts scripts/clean-public-build-artifacts.test.ts scripts/reset-fork-template.test.ts", "test:playwright:navigation-menu": "playwright test tests/playwright/navigation-menu.spec.ts", "test:playwright:payment-qr": "playwright test tests/playwright/payment-qr.spec.ts", "test:playwright:payment-qr:update": "playwright test tests/playwright/payment-qr.spec.ts --update-snapshots", diff --git a/scripts/github-actions/upstream-sync-sh.test.ts b/scripts/github-actions/upstream-sync-sh.test.ts new file mode 100644 index 00000000..1e8b52a2 --- /dev/null +++ b/scripts/github-actions/upstream-sync-sh.test.ts @@ -0,0 +1,104 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import process from "node:process"; +import test from "node:test"; + +const ROOT = process.cwd(); +const SCRIPT_PATH = path.join(ROOT, "scripts/github-actions/upstream-sync.sh"); + +const writeExecutable = (filePath: string, contents: string) => { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, contents, "utf8"); + fs.chmodSync(filePath, 0o755); +}; + +const createFakeGit = (binDir: string) => { + writeExecutable( + path.join(binDir, "git"), + `#!/usr/bin/env bash +set -euo pipefail + +if [[ "$1" == "rev-parse" && "\${2:-}" == "HEAD" ]]; then + printf 'fake-head-sha\\n' + exit 0 +fi + +printf 'unexpected git args: %s\\n' "$*" >&2 +exit 1 +`, + ); +}; + +const createFakeBun = (binDir: string, exitCode: 0 | 1) => { + writeExecutable( + path.join(binDir, "bun"), + `#!/usr/bin/env bash +set -euo pipefail + +if [[ "$1" == "run" && "\${2:-}" == "sync:upstream" && "\${3:-}" == "--json" ]]; then + cat <<'JSON' +{ + "status": "${exitCode === 0 ? "up_to_date" : "conflict"}", + "message": "${exitCode === 0 ? "Already up to date with upstream/main." : "Fork has shared-file conflicts with upstream; manual resolution required (README.md)."}", + "branchChanged": false, + "targetBranch": "main", + "upstreamRef": "upstream/main", + "sharedConflicts": ${exitCode === 0 ? "[]" : '["README.md"]'}, + "forkOwnedConflicts": [] +} +JSON + exit ${exitCode} +fi + +printf 'unexpected bun args: %s\\n' "$*" >&2 +exit 1 +`, + ); +}; + +const runWrapper = (t: test.TestContext, exitCode: 0 | 1) => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "open-links-upstream-sync-sh-")); + const binDir = path.join(tempDir, "bin"); + t.after(() => { + fs.rmSync(tempDir, { force: true, recursive: true }); + }); + + createFakeGit(binDir); + createFakeBun(binDir, exitCode); + + return spawnSync("bash", [SCRIPT_PATH, "run-sync"], { + cwd: ROOT, + encoding: "utf8", + env: { + ...process.env, + GITHUB_OUTPUT: "", + PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ""}`, + RUNNER_TEMP: tempDir, + }, + }); +}; + +test("upstream sync wrapper prints structured result when sync fails", (t) => { + const result = runWrapper(t, 1); + + assert.equal(result.status, 0); + assert.match(result.stdout, /before_sha=fake-head-sha/u); + assert.match(result.stdout, /after_sha=fake-head-sha/u); + assert.match(result.stdout, /command_status=1/u); + assert.match(result.stdout, /result_path=/u); + assert.match(result.stderr, /Upstream sync failed; structured result follows:/u); + assert.match(result.stderr, /"status": "conflict"/u); + assert.match(result.stderr, /"sharedConflicts": \["README\.md"\]/u); +}); + +test("upstream sync wrapper keeps successful runs quiet", (t) => { + const result = runWrapper(t, 0); + + assert.equal(result.status, 0); + assert.match(result.stdout, /command_status=0/u); + assert.doesNotMatch(result.stderr, /structured result follows/u); + assert.doesNotMatch(result.stderr, /"status": "up_to_date"/u); +}); diff --git a/scripts/github-actions/upstream-sync.sh b/scripts/github-actions/upstream-sync.sh index c8d9e35e..b1a4f239 100644 --- a/scripts/github-actions/upstream-sync.sh +++ b/scripts/github-actions/upstream-sync.sh @@ -9,6 +9,19 @@ write_output() { fi } +print_failed_sync_result() { + local result_path="$1" + + if [[ ! -s "$result_path" ]]; then + printf 'Upstream sync failed before writing a structured result: %s\n' "$result_path" >&2 + return 0 + fi + + printf 'Upstream sync failed; structured result follows:\n' >&2 + cat "$result_path" >&2 + printf '\n' >&2 +} + configure_upstream_remote() { local upstream_url="${UPSTREAM_REPOSITORY_URL:-https://github.com/pRizz/open-links.git}" @@ -57,6 +70,10 @@ run_sync() { local command_status=$? set -e + if [[ "$command_status" -ne 0 ]]; then + print_failed_sync_result "$result_path" + fi + local after_sha after_sha="$(git rev-parse HEAD)" write_output "after_sha" "$after_sha"