/** * The builder's CI vocabulary: preview tag conventions, the PR sticky-comment bodies, * and the generated GitHub Actions workflow. * * It lives here — not in the dashboard, not in the CLI — for the same reason the deploy * manifest does: BOTH ends must speak the same shape. Three writers exist for the same * two artifacts and any drift between them is a silent bug: * * - the **dashboard's one-click CI setup** commits the workflow to a customer repo and * (via `GithubRepoLinkDO`) posts the PR comment from the platform side; * - **`substrat init --ci github`** writes the same workflow for a builder who owns their * own CI and never connected the GitHub App; * - the **workflow itself** posts the same comment from the CI side, as the fallback for * an App installation that lacks `pull-requests: write`. * * The comment bodies are generated once here and rendered into the workflow's `printf` * format string, so the platform-written and CI-written comments cannot say different * things about the same PR. One generator, three writers, no drift. * * Pure string building — no zod, no network, no node. Safe in a worker, a CLI, and a * browser bundle alike. */ /** * The sticky preview tag for a PR — one long-lived fork, REBOUND on every push, so the * URL always serves the PR's latest code. The git analogy is a **branch ref**: a moving * pointer, bookmarked once. Successive pushes roll their migrations forward on the one * fork, which is the rehearsal that de-risks the eventual release. */ export declare const previewTag: (prNumber: number) => string; /** * The per-build preview tag — a FRESH scope per build, bound once and never rebound, so * the URL is frozen to exactly that build forever. The git analogy is a **sha**. * * A moving pointer is only safe when every build is *also* addressable immutably: "the bug * on the PR preview" must always de-reference to a fixed artifact. That is the whole reason * this tag exists alongside the sticky one. */ export declare const buildPreviewTag: (prNumber: number, runId: string | number) => string; /** * The prefix that matches every per-build tag of one PR, and NOTHING else — note that * PR 12's sticky tag (`pr-12`) does not start with PR 1's build prefix (`pr-1-`), so the * two numbering spaces never collide. */ export declare const buildPreviewTagPrefix: (prNumber: number) => string; /** * The sticky-comment marker. Every writer upserts the comment that starts with this * string, so whichever of the platform and CI posts first, the other updates in place * rather than double-posting. */ export declare const PREVIEW_COMMENT_MARKER = ""; /** * The sticky comment while the preview is live. * * `build` is the per-build immutable URL and is optional: it is present only when the repo * opted into per-build previews (`SUBSTRAT_PER_BUILD_PREVIEW`), because a frozen scope per * build is a real cost that not every project wants to pay. * * NOTE: keep the prose free of apostrophes. This same text is rendered into the workflow's * single-quoted `printf` format string, where one apostrophe closes the quote and takes the * whole preview job red on what looks like a copy-edit. */ export declare function previewCommentBody(urls: { sticky: string; build?: string | null; }): string; /** The sticky comment after the PR closed and the preview forks were deleted. */ export declare const previewReapedBody: () => string; /** * How a merge to the deploy branch turns into a prod release. * * - `trunk` — **every merge releases.** The push carries no `--version`, so the registry * patch-bumps, and the same run then promotes prod — behind the tip guard below, so a * queue that resumes out of order can never point prod at older code. The simplest thing * that works, and the default the dashboard commits. * - `changesets` — **the repo owns the version** (`package.json`), so a merge that only * lands a changeset must NOT release; only the merge that MOVES the version does. The job * compares `package.json` against the previous commit and no-ops when it did not change. * This is the workflow the release-train table in the docs describes. * * Both are legitimate; the platform enables workflows rather than encoding one (#509 §3). */ export type ReleaseMode = 'trunk' | 'changesets'; export interface DeployWorkflowOptions { /** The branch whose pushes deploy prod. */ branch: string; /** The vertical's bare slug — the control plane forms the `/` prefix. */ slug: string; /** The control-plane API base, e.g. `https://console.substrat.net/api`. */ cpUrl: string; /** Defaults to `trunk`. */ release?: ReleaseMode; /** * The vertical's directory INSIDE the repo, for a monorepo whose package is not the * repo root (e.g. `demos/auth-server`). Every push/preview runs against this directory, * the version gates read ITS package.json, and the triggers gain a `paths:` filter so a * merge that never touched the package does not release it. Install still runs at the * repo root — a workspace repo's lockfile lives there. Omitted = the repo root, exactly * the file this generator always produced. */ path?: string; } /** * Normalize a package directory to the repo-relative form the workflow embeds: no leading * `./`, no trailing `/`. Root spellings (``, `.`, `./`) collapse to undefined so callers * cannot generate a `push ./` that means the same thing as the pathless file but diffs * against it. Traversal is refused here — a generator shared by three writers must not * rely on every caller validating. */ export declare function normalizeWorkflowDir(path: string | undefined): string | undefined; /** * The workflow the one-click setup commits and `substrat init --ci github` writes. * * Self-contained on purpose: a committed file is read by humans, so there is no * reusable-workflow indirection to chase. The install step is load-bearing — `substrat * push`/`preview` runs the repo's OWN build (a wrangler custom build), which needs the * repo's devDependencies on disk; corepack picks the package manager from the lockfile. * In a monorepo (`path`) a second step builds the workspace packages the vertical imports: * install only *links* a sibling, and its `exports` point at a `dist/` a fresh checkout * does not have, so without it the very first bundle dies with `Could not resolve * "@scope/pkg"` — which is exactly how the first hosted push of an in-repo demo failed. * * Two behaviours are opt-in through **repository variables**, so one generated file serves * every project and enabling them never means regenerating it: * * - `SUBSTRAT_TEST_SCOPE_ID` — a long-lived test scope. Set it and every merge rebinds that * scope to the just-built version: the "tracks main" environment, kept a CI step rather * than a platform noun (a "this scope auto-tracks X" setting would be the retired * dev/staging channel re-buried one layer down). * - `SUBSTRAT_PER_BUILD_PREVIEW` — set it to `1` and each PR push also creates a frozen, * short-TTL clean-room preview whose URL names exactly that build. */ export declare function deployWorkflowYaml(opts: DeployWorkflowOptions): string; //# sourceMappingURL=ci.d.ts.map