Public: anyone can read this; the crew can change it.
The preview seed holds a recently moved repository, and its head page is a phone look showing "Moved from" #10261
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/10195-the-preview-seed-holds-a-recently-moved → main 3 commits fork 1 thread · 1 unresolved
3 files
changed +93
−1
src/spawn/preview-looks.ts +11
| @@ -38,6 +38,16 @@ export const PREVIEW_PULL_FILES = `${PREVIEW_PULL_PAGE}/files`; | ||
| 38 | 38 | export const PREVIEW_APP = "hello"; |
| 39 | 39 | export const PREVIEW_APP_PAGE = `/${PREVIEW_ADMIN}/${PREVIEW_APP}`; |
| 40 | 40 | |
| 41 | /** | |
| 42 | * The recently moved repository (#10195): made by the admin as `moved`, then | |
| 43 | * moved to the first seeded fork owner through the action a person uses, so | |
| 44 | * its head draws "Moved from preview-admin/moved on <date>". The seed refuses | |
| 45 | * to finish unless the page answers 200 and holds the line. | |
| 46 | */ | |
| 47 | export const PREVIEW_MOVED = "moved"; | |
| 48 | export const PREVIEW_MOVED_OWNER = "preview-ada"; | |
| 49 | export const PREVIEW_MOVED_PAGE = `/${PREVIEW_MOVED_OWNER}/${PREVIEW_MOVED}`; | |
| 50 | ||
| 41 | 51 | /** The Iterations page of the seeded fork family (#9182). */ |
| 42 | 52 | export const PREVIEW_ITERATIONS_PAGE = `${PREVIEW_REPO_HOME}/iterations`; |
| 43 | 53 | /** |
| @@ -131,6 +141,7 @@ export const PREVIEW_OWNER_LOOKS: readonly OwnerLook[] = [ | ||
| 131 | 141 | "/flow", |
| 132 | 142 | PREVIEW_REPO_HOME, |
| 133 | 143 | PREVIEW_APP_PAGE, |
| 144 | PREVIEW_MOVED_PAGE, | |
| 134 | 145 | PREVIEW_PULL_PAGE, |
| 135 | 146 | PREVIEW_PULL_FILES, |
| 136 | 147 | PREVIEW_ITERATIONS_PAGE, |
src/spawn/preview-seed.ts +38 −1
| @@ -26,6 +26,8 @@ import { pinBuild } from "../actions/builds.ts"; | ||
| 26 | 26 | import { addReview, mergePull, openPull } from "../actions/pulls.ts"; |
| 27 | 27 | import { commitToBranch, createRepo, forkRepo } from "../actions/repos.ts"; |
| 28 | 28 | import { reviewGate } from "../actions/gate_review.ts"; |
| 29 | import { forgeHttp } from "../http/server.ts"; | |
| 30 | import { transferRepository } from "../actions/transfer.ts"; | |
| 29 | 31 | import { linkIssue } from "../actions/dependencies.ts"; |
| 30 | 32 | import { createApp as createAppRecord } from "../core/apps.ts"; |
| 31 | 33 | import { studioApp } from "../web/studio/app.ts"; |
| @@ -57,6 +59,9 @@ import { | ||
| 57 | 59 | PREVIEW_ITERATIONS_PAGE, |
| 58 | 60 | PREVIEW_PULL, |
| 59 | 61 | PREVIEW_PULL_PAGE, |
| 62 | PREVIEW_MOVED, | |
| 63 | PREVIEW_MOVED_OWNER, | |
| 64 | PREVIEW_MOVED_PAGE, | |
| 60 | 65 | PREVIEW_REPO, |
| 61 | 66 | PREVIEW_SETTINGS_PAGES, |
| 62 | 67 | } from "./preview-looks.ts"; |
| @@ -69,6 +74,7 @@ export { | ||
| 69 | 74 | PREVIEW_HELD_PAGE, |
| 70 | 75 | PREVIEW_HELD_THEMES, |
| 71 | 76 | PREVIEW_ITERATIONS_PAGE, |
| 77 | PREVIEW_MOVED_PAGE, | |
| 72 | 78 | PREVIEW_OWNER_LOOKS, |
| 73 | 79 | PREVIEW_PULL, |
| 74 | 80 | PREVIEW_PULL_PAGE, |
| @@ -76,6 +82,36 @@ export { | ||
| 76 | 82 | PREVIEW_REPO_HOME, |
| 77 | 83 | } from "./preview-looks.ts"; |
| 78 | 84 | |
| 85 | /** | |
| 86 | * A repository moved just now, through the action a person uses (#10195): | |
| 87 | * its head draws "Moved from <old path> on <date>". Refused unless that page | |
| 88 | * answers 200 and holds the line, in the first-paint HTML. | |
| 89 | */ | |
| 90 | async function seedMoved(app: App, admin: User): Promise<Result<void, string>> { | |
| 91 | const made = await createRepo(app, admin, { name: PREVIEW_MOVED, visibility: "public", description: "A repository that moved" }); | |
| 92 | if (!made.ok) return err(`the moved repository: ${String(made.error)}`); | |
| 93 | const first = await commitToBranch(app, admin, made.value, { | |
| 94 | message: "Start", | |
| 95 | files: [{ path: "README.md", content: "# moved\n\nA repository that changed owner.\n" }], | |
| 96 | }); | |
| 97 | if (!first.ok) return err(`the moved repository's commit: ${JSON.stringify(first.error)}`); | |
| 98 | const moved = await transferRepository(app, admin, made.value, PREVIEW_MOVED_OWNER); | |
| 99 | if (!moved.ok) return err(`the move: ${String(moved.error)}`); | |
| 100 | const http = forgeHttp(app); | |
| 101 | const session = createSession(app.db, admin.id); | |
| 102 | try { | |
| 103 | const res = await http.handle( | |
| 104 | new Request(`http://localhost${PREVIEW_MOVED_PAGE}`, { headers: { cookie: `forge_session=${session.token}` } }), | |
| 105 | ); | |
| 106 | if (res.status !== 200) return err(`${PREVIEW_MOVED_PAGE} answers ${String(res.status)}, not 200`); | |
| 107 | if (!(await res.text()).includes("moved-from")) return err(`${PREVIEW_MOVED_PAGE} holds no moved-from line`); | |
| 108 | } finally { | |
| 109 | destroySession(app.db, session.token); | |
| 110 | http.close(); | |
| 111 | } | |
| 112 | return ok(); | |
| 113 | } | |
| 114 | ||
| 79 | 115 | /** The seeded wave's version and headline, which the owner's /waves look shows. */ |
| 80 | 116 | export const PREVIEW_WAVE = "0.1.0"; |
| 81 | 117 | export const PREVIEW_WAVE_HEADLINE = "The sample says hello"; |
| @@ -173,7 +209,6 @@ async function seedSettingsPages(app: App, admin: User): Promise<Result<void, st | ||
| 173 | 209 | }); |
| 174 | 210 | if (!added.ok) return err(`the sample account: ${added.error}`); |
| 175 | 211 | } |
| 176 | const { forgeHttp } = await import("../http/server.ts"); | |
| 177 | 212 | const http = forgeHttp(app); |
| 178 | 213 | const session = createSession(app.db, admin.id); |
| 179 | 214 | try { |
| @@ -319,6 +354,8 @@ async function seedWorld(app: App, admin: User): Promise<Result<void, string>> { | ||
| 319 | 354 | if (!gated.ok) return gated; |
| 320 | 355 | const hosted = await seedApp(app, admin); |
| 321 | 356 | if (!hosted.ok) return hosted; |
| 357 | const moved = await seedMoved(app, admin); | |
| 358 | if (!moved.ok) return moved; | |
| 322 | 359 | const valve = turnValveFor(app, admin, `${PREVIEW_ADMIN}/${PREVIEW_REPO}`, 60); |
| 323 | 360 | if (!valve.ok) return err(`the valve: ${valve.error}`); |
| 324 | 361 |
test/preview-moved.test.ts added +44
| @@ -0,0 +1,44 @@ | ||
| 1 | /** The preview's recently moved repository draws "Moved from" in its first-paint HTML (#10195). */ | |
| 2 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 3 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 4 | import { tmpdir } from "node:os"; | |
| 5 | import { join } from "node:path"; | |
| 6 | import { createApp, type App } from "../src/app.ts"; | |
| 7 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 8 | import { createSession } from "../src/core/sessions.ts"; | |
| 9 | import { recentMove } from "../src/core/transfer.ts"; | |
| 10 | import { getRepo } from "../src/core/repos.ts"; | |
| 11 | import { PREVIEW_ADMIN, PREVIEW_MOVED, PREVIEW_MOVED_PAGE, PREVIEW_OWNER_LOOKS } from "../src/spawn/preview-looks.ts"; | |
| 12 | import { previewAdmin, seedPreview } from "../src/spawn/preview-seed.ts"; | |
| 13 | ||
| 14 | let dir: string; | |
| 15 | let app: App; | |
| 16 | let servers: Servers; | |
| 17 | beforeAll(async () => { | |
| 18 | dir = await mkdtemp(join(tmpdir(), "forge-preview-moved-")); | |
| 19 | app = createApp({ dataDir: dir, port: 0 }); | |
| 20 | const seeded = await seedPreview(app, { FORGE_APP_ENV: "preview" }); | |
| 21 | if (!seeded.ok) throw new Error(seeded.error); | |
| 22 | servers = await serve(app); | |
| 23 | }, 30_000); | |
| 24 | afterAll(async () => { | |
| 25 | servers.stop(); | |
| 26 | app.close(); | |
| 27 | await rm(dir, { recursive: true, force: true }); | |
| 28 | }); | |
| 29 | ||
| 30 | test("the moved repository's head says where from, in the HTML the server sends", async () => { | |
| 31 | const cookie = `forge_session=${createSession(app.db, previewAdmin(app)!.id).token}`; | |
| 32 | const res = await fetch(`${servers.url}${PREVIEW_MOVED_PAGE}`, { headers: { cookie } }); | |
| 33 | expect(res.status).toBe(200); | |
| 34 | const html = await res.text(); | |
| 35 | expect(html).toContain("moved-from"); | |
| 36 | expect(html).toContain(`Moved from ${PREVIEW_ADMIN}/${PREVIEW_MOVED} on`); | |
| 37 | const repo = getRepo(app.db, "preview-ada", PREVIEW_MOVED)!; | |
| 38 | expect(recentMove(app.db, repo.id)?.from).toBe(`${PREVIEW_ADMIN}/${PREVIEW_MOVED}`); | |
| 39 | }); | |
| 40 | ||
| 41 | test("its head is looked at on desktop and phone", () => { | |
| 42 | for (const kind of ["desktop", "phone"] as const) | |
| 43 | expect(PREVIEW_OWNER_LOOKS).toContainEqual({ page: PREVIEW_MOVED_PAGE, kind }); | |
| 44 | }); |
Why is this import lazy? If there is no import cycle, make it static; otherwise say so in one line.