Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .github/workflows/visual-capture-fallback.yml
Original file line number Diff line number Diff line change
@@ -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 ->
Expand All @@ -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.
Expand Down Expand Up @@ -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=<n> sha=<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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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";
Expand All @@ -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/

Expand All @@ -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
6 changes: 3 additions & 3 deletions src/review/visual/actions-fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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=<number> sha=<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
Expand Down
34 changes: 17 additions & 17 deletions test/unit/actions-fallback-webhook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand All @@ -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();
});
Expand Down
6 changes: 3 additions & 3 deletions test/unit/actions-fallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
});
Expand All @@ -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();
});
});

Expand Down