Public: anyone can read this; the crew can change it.

Every preview of the forge pictures the Iterations page of a seeded fork family, at desk and phone width #9186

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/9182-every-preview-of-the-forge-pictures-the → main 5 commits fork 2 threads · 2 unresolved

  • Merged. mason/9182-every-preview-of-the-forge-pictures-the was merged into main as 791da1d.

Review

Sign in to review this change.
4 files changed +115 −9
src/spawn/preview-looks.ts +21 −4
@@ -1,9 +1,10 @@
11 /**
22 * The looks every preview of the forge takes as its owner (#8974): home
3 * with its pins, /flow, the seeded repository's head and its pull's page,
4 * each at desk and phone width. The looks service adds them to whatever
5 * forge.json lists for a preview of the forge's own repository
6 * (`FORGE_REPO`), so no edit of forge.json can drop one.
3 * with its pins, /flow, the seeded repository's head, its pull's page and
outdated

This line is much longer than the rest of the comment. Rewrap it.

4 * its Iterations page (#9182), each at desk and phone width. The looks
5 * service adds them to whatever forge.json lists for a preview of the
6 * forge's own repository (`FORGE_REPO`), so no edit of forge.json can
7 * drop one.
78 *
89 * Plain data and no imports beyond a type: the seed (`preview-seed.ts`)
910 * makes what these pages show, and the looks service reads the list.
@@ -25,6 +26,21 @@ export const PREVIEW_PULL = 3;
2526 export const PREVIEW_REPO_HOME = `/${PREVIEW_ADMIN}/${PREVIEW_REPO}`;
2627 export const PREVIEW_PULL_PAGE = `${PREVIEW_REPO_HOME}/pulls/${String(PREVIEW_PULL)}`;
2728
29 /** The Iterations page of the seeded fork family (#9182). */
30 export const PREVIEW_ITERATIONS_PAGE = `${PREVIEW_REPO_HOME}/iterations`;
31 /**
32 * The seeded forks, in the order they are made: each is `<owner>/sample`, a
33 * fork of the sample or of the fork whose owner is named by `of` (one person
34 * forks a repository once). The seed refuses to finish if the page would not
35 * draw them.
36 */
37 export const PREVIEW_FORKS: readonly { readonly owner: string; readonly of?: string }[] = [
38 { owner: "preview-ada" },
39 { owner: "preview-bo" },
40 { owner: "preview-cy" },
41 { owner: "preview-dee", of: "preview-ada" },
42 ];
43
2844 export interface OwnerLook {
2945 readonly page: string;
3046 readonly kind: LookKind;
@@ -36,6 +52,7 @@ export const PREVIEW_OWNER_LOOKS: readonly OwnerLook[] = [
3652 "/flow",
3753 PREVIEW_REPO_HOME,
3854 PREVIEW_PULL_PAGE,
55 PREVIEW_ITERATIONS_PAGE,
3956 ].flatMap((page) => (["desktop", "phone"] as const).map((kind) => ({ page, kind })));
4057
4158 /**
src/spawn/preview-seed.ts +62 −2
@@ -21,12 +21,15 @@ import { pinPanel } from "../actions/board-pins.ts";
2121 import { writeBoard } from "../actions/boards.ts";
2222 import { backBet } from "../actions/bets.ts";
2323 import { openIssue, setIssueState } from "../actions/issues.ts";
24 import { pinBuild } from "../actions/builds.ts";
2425 import { openPull } from "../actions/pulls.ts";
25 import { commitToBranch, createRepo } from "../actions/repos.ts";
26 import { commitToBranch, createRepo, forkRepo } from "../actions/repos.ts";
27 import { iterationsOf } from "../core/iterations.ts";
2628 import { turnValveFor } from "../actions/valves.ts";
2729 import { insertBet } from "../core/bets.ts";
2830 import { addDependency } from "../core/dependencies.ts";
2931 import { claimRun, createRun, finishRun } from "../core/runs.ts";
32 import { setMember } from "../core/roles.ts";
3033 import { noteLimit } from "../core/standing.ts";
3134 import { addSubscription } from "../core/subscriptions.ts";
3235 import { recordUsage } from "../core/usage.ts";
@@ -34,10 +37,19 @@ import { getUserSetting, setUserSetting } from "../core/settings.ts";
3437 import { createUser, getUserByName, type User } from "../core/users.ts";
3538 import { err, ok, type Result } from "../kernel/result.ts";
3639 import { PREVIEW_CONFIG_FILES } from "./preview-config.ts";
37 import { PREVIEW_ADMIN, PREVIEW_PULL, PREVIEW_PULL_PAGE, PREVIEW_REPO } from "./preview-looks.ts";
40 import {
41 PREVIEW_ADMIN,
42 PREVIEW_FORKS,
43 PREVIEW_ITERATIONS_PAGE,
44 PREVIEW_PULL,
45 PREVIEW_PULL_PAGE,
46 PREVIEW_REPO,
47 } from "./preview-looks.ts";
3848
3949 export {
4050 PREVIEW_ADMIN,
51 PREVIEW_FORKS,
52 PREVIEW_ITERATIONS_PAGE,
4153 PREVIEW_OWNER_LOOKS,
4254 PREVIEW_PULL,
4355 PREVIEW_PULL_PAGE,
@@ -199,6 +211,8 @@ async function seedWorld(app: App, admin: User): Promise<Result<void, string>> {
199211 if (!waved.ok) return waved;
200212 const bets = seedBets(app, admin, repo.value, open);
201213 if (!bets.ok) return bets;
214 const family = await seedForkFamily(app, admin, repo.value, start.value.commitOid);
215 if (!family.ok) return family;
202216 const valve = turnValveFor(app, admin, `${PREVIEW_ADMIN}/${PREVIEW_REPO}`, 60);
203217 if (!valve.ok) return err(`the valve: ${valve.error}`);
204218
@@ -341,3 +355,49 @@ function seedBets(app: App, admin: User, repo: Repo, open: readonly Issue[]): Re
341355 const result = backBet(app, admin, repo, backed.id, 20);
342356 return result.ok ? ok() : err(`backing a bet: ${result.error.message}`);
343357 }
358
359 /**
360 * The fork family the Iterations page draws: the sample pinned at its first
361 * commit, three forks of it and a fork of the first, each with a commit of
362 * its own (so ahead/behind is not zero) and a pin on it. Refused when the
363 * page would still be a 404 or would draw fewer cards than forks.
364 */
365 async function seedForkFamily(app: App, admin: User, repo: Repo, first: string): Promise<Result<void, string>> {
366 const pinned = await pinBuild(app, admin, repo, { label: "v1", oid: first, note: "The sample, as started" });
367 if (!pinned.ok) return err(`the sample's pin: ${JSON.stringify(pinned.error)}`);
368 const made = new Map<string, Repo>();
369 for (const { owner, of } of PREVIEW_FORKS) {
370 const parent = of === undefined ? repo : made.get(of);
371 if (parent === undefined) return err(`the fork of ${owner} comes before ${String(of)}'s`);
372 const person = await createUser(app.db, {
373 origin: "seed",
374 trusted: true,
375 allowReserved: true,
376 username: owner,
377 email: `${owner}@preview.invalid`,
378 });
379 if (!person.ok) return err(`the fork's owner ${owner}: ${person.error}`);
380 setMember(app.db, person.value.id, true, admin.id, new Date());
381 const fork = await forkRepo(app, person.value, parent, PREVIEW_REPO, { visibility: "public" });
382 if (!fork.ok) return err(`the fork of ${owner}: ${String(fork.error)}`);
383 made.set(owner, fork.value);
384 const commit = await commitToBranch(app, person.value, fork.value, {
385 message: `${owner} goes its own way`,
386 files: [{ path: `${owner}.md`, content: `# ${owner}\n\nA change of its own.\n` }],
387 });
388 if (!commit.ok) return err(`the commit of ${owner}: ${JSON.stringify(commit.error)}`);
389 const pin = await pinBuild(app, person.value, fork.value, { label: "try-1", oid: commit.value.commitOid, note: `${owner}, first try` });
390 if (!pin.ok) return err(`the pin of ${owner}: ${JSON.stringify(pin.error)}`);
391 }
392 const home = getRepo(app.db, admin.username, PREVIEW_REPO);
393 const drawn = home === undefined ? undefined : await iterationsOf({ app, auth: { user: admin } }, home);
394 if (drawn === undefined) return err(`${PREVIEW_ITERATIONS_PAGE} would be a 404`);
395 if (drawn.forks.length < PREVIEW_FORKS.length + 1) return err(`${PREVIEW_ITERATIONS_PAGE} draws ${String(drawn.forks.length)} forks`);

The seed refuses to finish if the page draws fewer than the sample plus the forks. It does not check that the family contains a fork of a fork. Check that the dee entry has forkOf set to the ada fork, so the tree cannot silently flatten.

396 // Each fork hangs under the one it was made from, so the tree cannot flatten.
397 for (const { owner, of } of PREVIEW_FORKS) {
398 const card = drawn.forks.find((f) => f.repo === `${owner}/${PREVIEW_REPO}`);
399 const under = of === undefined ? `${admin.username}/${PREVIEW_REPO}` : `${of}/${PREVIEW_REPO}`;
400 if (card?.forkOf !== under) return err(`${PREVIEW_ITERATIONS_PAGE} draws ${owner}/${PREVIEW_REPO} under ${String(card?.forkOf)}, not ${under}`);
401 }
402 return ok();
403 }
test/looks-owner.test.ts +31 −2
@@ -45,7 +45,8 @@ import {
4545 seedPreview,
4646 } from "../src/spawn/preview-seed.ts";
4747 import { PREVIEW_CONFIG_FILES } from "../src/spawn/preview-config.ts";
48 import { ownerLooksFor } from "../src/spawn/preview-looks.ts";
48 import { ownerLooksFor, PREVIEW_FORKS, PREVIEW_ITERATIONS_PAGE } from "../src/spawn/preview-looks.ts";
49 import { iterationsOf } from "../src/core/iterations.ts";
4950 import { localSource } from "../src/view/local.ts";
5051 import { isFormConfig, parseConfig } from "../src/web/render/config-format.ts";
5152 import { quietLogs } from "./helpers/globals.ts";
@@ -484,7 +485,7 @@ describe("looks of a public app", () => {
484485 if (!opened.ok) throw new Error(String(opened.error));
485486 const env = previewEnv(opened.value.number);
486487 const commit = branch.value.commitOid;
487 const pages = ["/", "/flow", PREVIEW_REPO_HOME, PREVIEW_PULL_PAGE];
488 const pages = ["/", "/flow", PREVIEW_REPO_HOME, PREVIEW_PULL_PAGE, PREVIEW_ITERATIONS_PAGE];
488489 const deadline = Date.now() + 90_000;
489490 const light = () =>
490491 looksForEnv(app.db, record.id, env).filter((l) => l.commitOid === commit && l.theme === "light");
@@ -524,3 +525,31 @@ describe("looks of a public app", () => {
524525 expect(ownerLooksFor("alice/forge", forge, previewEnv(1)).length).toBeGreaterThan(0);
525526 });
526527 });
528
529 describe("the Iterations page of the seeded fork family (#9182)", () => {
530 test("the owner's looks include it at desk and phone", () => {
531 const forge = { ownerName: "alice", name: "forge" };
532 const looks = ownerLooksFor("alice/forge", forge, previewEnv(1)).filter((l) => l.page === PREVIEW_ITERATIONS_PAGE);
533 expect(PREVIEW_ITERATIONS_PAGE).toBe("/preview-admin/sample/iterations");
534 expect(looks.map((l) => l.kind).sort()).toEqual(["desktop", "phone"]);
535 });
536
537 test("the seeded repository has the forks the page draws, each with a build of its own", async () => {
538 const dir = await mkdtemp(join(tmpdir(), "forge-iterations-seed-"));
539 const app = createApp({ dataDir: dir, port: 0 });
540 try {
541 const seeded = await seedPreview(app, { FORGE_APP_ENV: "preview" });
542 if (!seeded.ok) throw new Error(seeded.error);
543 const admin = getUserByName(app.db, PREVIEW_ADMIN)!;
544 const repo = getRepo(app.db, PREVIEW_ADMIN, PREVIEW_REPO)!;
545 const drawn = await iterationsOf({ app, auth: { user: admin } }, repo);
546 expect(drawn?.forks.length).toBeGreaterThanOrEqual(4);
547 expect(drawn!.forks.filter((f) => f.forkOf !== null).length).toBe(PREVIEW_FORKS.length);
548 expect(drawn!.forks.some((f) => f.forkOf === `preview-ada/${PREVIEW_REPO}`)).toBe(true);
549 expect(drawn!.forks.filter((f) => f.forkOf !== null).every((f) => f.builds.length > 0)).toBe(true);
550 } finally {
551 app.close();
552 await rm(dir, { recursive: true, force: true });
553 }
554 }, 30_000);
555 });
test/signup-gate-parent.test.ts +1 −1
@@ -115,7 +115,7 @@ test("every caller of createUser says where its account comes from", async () =>
115115 "src/actions/spawn.ts": ["spawn", "spawn"],
116116 "src/actions/users.ts": ["signup", "passkey"],
117117 "src/chaos/world.ts": ["seed"],
118 "src/spawn/preview-seed.ts": ["seed"],
118 "src/spawn/preview-seed.ts": ["seed", "seed"],
119119 "src/web/pages/claim.ts": ["claim"],
120120 "src/web/pages/everyday.ts": ["everyday"],
121121 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs