203 lines · 8.1 KB
import { env } from "cloudflare:workers";
import { all, batch, first, run, stmt } from "../db";
import { randomToken, sha256Hex, timingSafeEqualStrings } from "../crypto";
import { conflict } from "../errors";
import { createRepository, deleteRepository } from "../repo-ops";
import { repoMoveStatements } from "../redirects";
import { repoOwnershipChangeStatements } from "../integrations/ownership";
import { loadRepoById } from "../repos";
import { isScratchRepo, publishMeta, SCRATCH_ACCOUNT_ID, SCRATCH_LIMITS } from "./config";
import { loadSiteById, loadSiteForRepo } from "./deploy";
import { reserveScratch, scratchResult } from "./scratch-core";
import { syncSiteForRepo } from "./sync";
import type { ScratchResult } from "./scratch-core";
import type { SiteRow } from "./config";
import type { Permission, RepoRow, Viewer } from "~/lib/types";

const PUSH_PREFIX = "gitfor_scratch";
/** createScratchRepo inserts the repo before linking it to its site; don't race that window. */
const ORPHAN_GRACE_MS = 10 * 60 * 1000;
const DEFAULT_BRANCH = "main";
const MIB = 1024 * 1024;

/** The reserved org that owns unclaimed scratch repositories; it has no members and no sessions. */
export const SCRATCH_ACTOR: Viewer = {
  id: SCRATCH_ACCOUNT_ID,
  name: "scratch",
  displayName: "Scratch sites",
  email: "scratch@invalid",
  isAdmin: false,
};

export type ScratchRepoResult = ScratchResult & { remote: string; pushToken: string };

export { isScratchRepo };

/**
 * Creates an anonymous scratch repository and site. Every push to it deploys: `main` to `<id>--scratch`, other
 * branches to `<branch>--<id>--scratch`. The push token works until the site is claimed or expires.
 */
export async function createScratchRepo(ip: string): Promise<ScratchRepoResult> {