diff --git a/.github/workflows/visual-capture-fallback.yml b/.github/workflows/visual-capture-fallback.yml index 987d32b030..2ce98ba739 100644 --- a/.github/workflows/visual-capture-fallback.yml +++ b/.github/workflows/visual-capture-fallback.yml @@ -1,4 +1,4 @@ -name: Gittensory Visual Capture Fallback +name: LoopOver Visual Capture Fallback # GitHub-Actions build-and-serve FALLBACK for a repo with no CI-produced preview deploy (#4112, part of the # #3607 visual-capture convergence epic). gittensory's preview-url.ts discovery chain (Deployments API -> @@ -23,14 +23,14 @@ name: Gittensory Visual Capture Fallback # script -- standard GitHub Actions practice to keep an input's literal text out of the shell-parsed script, # regardless of how trusted its source is. # -# Handoff: this job uploads its captured PNGs as a GitHub Actions artifact (`gittensory-visual-fallback`) and +# Handoff: this job uploads its captured PNGs as a GitHub Actions artifact (`loopover-visual-fallback`) and # stops -- it never talks to gittensory directly and holds no credential to do so. On completion, GitHub # delivers a `workflow_run` webhook; gittensory's backend then lists + downloads that run's artifact using its # OWN, already-trusted GitHub App installation token -- never a token that passed through this job. # # Setup (self-hosted repos only -- NOT needed for gittensory-ui / metagraphed, which already have their own # preview-deploy pipeline): copy this file, unmodified, into the target repo's `.github/workflows/` at this -# EXACT path and name (`visual-capture-fallback.yml` / "Gittensory Visual Capture Fallback") -- gittensory's +# EXACT path and name (`visual-capture-fallback.yml` / "LoopOver Visual Capture Fallback") -- gittensory's # dispatch call and workflow_run listener both key off this fixed name. Then set `review.visual.actions_fallback: # true` in that repo's `.gittensory.yml` (see .gittensory.yml.example) to opt in; it activates ONLY when the # existing discovery chain finds no preview at all, so a repo with its own CI-produced preview is unaffected. @@ -70,7 +70,7 @@ on: # GitHub renders run-name from these inputs and surfaces the result as workflow_run.display_title in the # completion webhook -- a workflow_dispatch run carries no natural PR association otherwise. See # actions-fallback.ts's parseFallbackRunCorrelation, which reads this EXACT "pr= sha=" shape back out. -run-name: "gittensory-visual-fallback pr=${{ inputs.pr_number }} sha=${{ inputs.head_sha }}" +run-name: "loopover-visual-fallback pr=${{ inputs.pr_number }} sha=${{ inputs.head_sha }}" permissions: contents: read @@ -85,9 +85,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 env: - GITTENSORY_DIST_DIR: ${{ inputs.dist_dir }} - GITTENSORY_SERVE_PORT: ${{ inputs.serve_port }} - GITTENSORY_ROUTES_JSON: ${{ inputs.routes }} + LOOPOVER_DIST_DIR: ${{ inputs.dist_dir }} + LOOPOVER_SERVE_PORT: ${{ inputs.serve_port }} + LOOPOVER_ROUTES_JSON: ${{ inputs.routes }} steps: - name: Checkout PR head uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 @@ -102,17 +102,17 @@ jobs: - name: Build env: - GITTENSORY_BUILD_CMD: ${{ inputs.build_cmd }} - run: bash -euo pipefail -c "$GITTENSORY_BUILD_CMD" + LOOPOVER_BUILD_CMD: ${{ inputs.build_cmd }} + run: bash -euo pipefail -c "$LOOPOVER_BUILD_CMD" - name: Serve built output on localhost run: | set -euo pipefail - npx --yes serve@14 -l "tcp://127.0.0.1:${GITTENSORY_SERVE_PORT}" "$GITTENSORY_DIST_DIR" \ + npx --yes serve@14 -l "tcp://127.0.0.1:${LOOPOVER_SERVE_PORT}" "$LOOPOVER_DIST_DIR" \ > serve.log 2>&1 & echo $! > serve.pid for _ in $(seq 1 30); do - if curl --silent --fail --output /dev/null "http://127.0.0.1:${GITTENSORY_SERVE_PORT}/"; then + if curl --silent --fail --output /dev/null "http://127.0.0.1:${LOOPOVER_SERVE_PORT}/"; then echo "Local server is up." exit 0 fi @@ -128,7 +128,7 @@ jobs: # Mirrors slugifyRoutePath in src/review/visual/actions-fallback.ts EXACTLY -- both sides must # independently compute the same filename for the same route, or the download side can't find it. cat <<'JS' > "$RUNNER_TEMP/slugify-routes.mjs" - const routes = JSON.parse(process.env.GITTENSORY_ROUTES_JSON); + const routes = JSON.parse(process.env.LOOPOVER_ROUTES_JSON); const slugify = (path) => { const trimmed = path.replace(/^\/+|\/+$/g, ""); if (trimmed === "") return "root"; @@ -152,10 +152,10 @@ jobs: [ -z "$route" ] && continue google-chrome-stable --headless=new --no-sandbox --disable-gpu --hide-scrollbars \ --window-size=1440,900 --screenshot="shots/${slug}--desktop.png" \ - "http://127.0.0.1:${GITTENSORY_SERVE_PORT}${route}" + "http://127.0.0.1:${LOOPOVER_SERVE_PORT}${route}" google-chrome-stable --headless=new --no-sandbox --disable-gpu --hide-scrollbars \ --window-size=390,844 --screenshot="shots/${slug}--mobile.png" \ - "http://127.0.0.1:${GITTENSORY_SERVE_PORT}${route}" + "http://127.0.0.1:${LOOPOVER_SERVE_PORT}${route}" done < routes.tsv ls -la shots/ @@ -168,7 +168,7 @@ jobs: - name: Upload captured screenshots uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: gittensory-visual-fallback + name: loopover-visual-fallback path: shots/*.png if-no-files-found: error retention-days: 1 diff --git a/src/review/visual/actions-fallback.ts b/src/review/visual/actions-fallback.ts index bab735db83..8b2bf4ca77 100644 --- a/src/review/visual/actions-fallback.ts +++ b/src/review/visual/actions-fallback.ts @@ -43,9 +43,9 @@ const API_VERSION = "2022-11-28"; /** The workflow file this module dispatches and whose completions it listens for. */ export const FALLBACK_WORKFLOW_FILE = "visual-capture-fallback.yml"; /** The workflow's declared `name:` -- cross-checked against `workflow_run.name` before acting on a completion. */ -export const FALLBACK_WORKFLOW_NAME = "Gittensory Visual Capture Fallback"; +export const FALLBACK_WORKFLOW_NAME = "LoopOver Visual Capture Fallback"; /** The artifact name the dispatched workflow uploads its captured PNGs under. */ -export const FALLBACK_ARTIFACT_NAME = "gittensory-visual-fallback"; +export const FALLBACK_ARTIFACT_NAME = "loopover-visual-fallback"; // --------------------------------------------------------------------------------------------------------- // SSRF allowlist extension: the artifact-download redirect target. @@ -129,7 +129,7 @@ export async function dispatchVisualCaptureFallback(params: { // Correlation: recover {prNumber, headSha} from a completed workflow_run's display_title. // --------------------------------------------------------------------------------------------------------- -const RUN_NAME_PATTERN = /gittensory-visual-fallback pr=(\d+) sha=([0-9a-f]{40})/i; +const RUN_NAME_PATTERN = /loopover-visual-fallback pr=(\d+) sha=([0-9a-f]{40})/i; /** Parse the `pr= sha=` correlation this module's own `run-name:` embeds (see the workflow file) * back out of a completed run's `display_title`. Returns null (fail-safe, never guesses) for anything that diff --git a/test/unit/actions-fallback-webhook.test.ts b/test/unit/actions-fallback-webhook.test.ts index 41ce020322..e8b35321c6 100644 --- a/test/unit/actions-fallback-webhook.test.ts +++ b/test/unit/actions-fallback-webhook.test.ts @@ -183,10 +183,10 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { installation: { id: 9101 }, workflow_run: { id: 501, - name: "Gittensory Visual Capture Fallback", + name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", - display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe", + display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe", }, }, } as never); @@ -227,7 +227,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 510, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 510, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never); @@ -269,7 +269,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 520, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 520, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never); @@ -298,7 +298,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 511, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 511, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never), ).resolves.toBeUndefined(); @@ -330,7 +330,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 512, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 512, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never), ).resolves.toBeUndefined(); @@ -357,7 +357,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 513, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 513, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never), ).resolves.toBeUndefined(); @@ -397,7 +397,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 514, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 514, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never), ).resolves.toBeUndefined(); @@ -454,7 +454,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 503, name: "Gittensory Visual Capture Fallback", event: "pull_request", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 503, name: "LoopOver Visual Capture Fallback", event: "pull_request", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never); @@ -483,7 +483,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 504, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "failure", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 504, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "failure", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never); @@ -513,7 +513,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "requested", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 589, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: null, display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 589, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: null, display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never); @@ -536,7 +536,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 590, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "failure", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 590, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "failure", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never); @@ -557,7 +557,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 591, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 591, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never); @@ -578,7 +578,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 592, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "manually triggered" }, + workflow_run: { id: 592, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "manually triggered" }, }, } as never); @@ -609,7 +609,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 505, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "manually triggered" }, + workflow_run: { id: 505, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "manually triggered" }, }, } as never); @@ -638,7 +638,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { action: "completed", repository: { name: "fallback-repo", full_name: "owner/fallback-repo", owner: { login: "owner" } }, installation: { id: 9101 }, - workflow_run: { id: 506, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "gittensory-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, + workflow_run: { id: 506, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success", display_title: "loopover-visual-fallback pr=55 sha=cafebabecafebabecafebabecafebabecafebabe" }, }, } as never); @@ -654,7 +654,7 @@ describe("workflow_run webhook -> actions_fallback storage (#4112)", () => { type: "github-webhook", deliveryId: "no-repo", eventName: "workflow_run", - payload: { action: "completed", workflow_run: { id: 507, name: "Gittensory Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success" } }, + payload: { action: "completed", workflow_run: { id: 507, name: "LoopOver Visual Capture Fallback", event: "workflow_dispatch", conclusion: "success" } }, } as never), ).resolves.toBeUndefined(); }); diff --git a/test/unit/actions-fallback.test.ts b/test/unit/actions-fallback.test.ts index 73954e8332..00f90451d1 100644 --- a/test/unit/actions-fallback.test.ts +++ b/test/unit/actions-fallback.test.ts @@ -136,14 +136,14 @@ describe("isGithubArtifactStorageUrl (SSRF allowlist extension)", () => { describe("parseFallbackRunCorrelation", () => { it("parses a matching run-name display_title", () => { - expect(parseFallbackRunCorrelation("gittensory-visual-fallback pr=42 sha=0123456789abcdef0123456789abcdef01234567")).toEqual({ + expect(parseFallbackRunCorrelation("loopover-visual-fallback pr=42 sha=0123456789abcdef0123456789abcdef01234567")).toEqual({ prNumber: 42, headSha: "0123456789abcdef0123456789abcdef01234567", }); }); it("lowercases an upper-case sha", () => { - expect(parseFallbackRunCorrelation("gittensory-visual-fallback pr=1 sha=ABCDEF0123456789ABCDEF0123456789ABCDEF01")?.headSha).toBe( + expect(parseFallbackRunCorrelation("loopover-visual-fallback pr=1 sha=ABCDEF0123456789ABCDEF0123456789ABCDEF01")?.headSha).toBe( "abcdef0123456789abcdef0123456789abcdef01", ); }); @@ -162,7 +162,7 @@ describe("parseFallbackRunCorrelation", () => { }); it("returns null when the pr number is not a positive integer", () => { - expect(parseFallbackRunCorrelation("gittensory-visual-fallback pr=0 sha=0123456789abcdef0123456789abcdef01234567")).toBeNull(); + expect(parseFallbackRunCorrelation("loopover-visual-fallback pr=0 sha=0123456789abcdef0123456789abcdef01234567")).toBeNull(); }); });