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
102 changes: 102 additions & 0 deletions apps/marketing/vercel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as NodeChildProcess from "node:child_process";
import * as NodeFS from "node:fs";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vite-plus/test";

import { config } from "./vercel.ts";

/**
* The ignore command decides whether Vercel spends a build. It is a shell
* string, so nothing type-checks it, and the previous version silently leaked:
* a push to a new branch has no previously deployed commit and fell back to
* comparing one commit, which built whenever that commit moved a lockfile.
* Exercise it against a real repository rather than asserting on its text.
*/
describe("marketing ignore command", () => {
let dir: string;
let siteChanged: string;

const run = (ref: string, previousSha: string): "skip" | "build" => {
try {
NodeChildProcess.execFileSync("bash", ["-c", config.ignoreCommand ?? ""], {
cwd: NodePath.join(dir, "apps/marketing"),
env: {
...process.env,
VERCEL_GIT_COMMIT_REF: ref,
...(previousSha ? { VERCEL_GIT_PREVIOUS_SHA: previousSha } : {}),
},
stdio: "ignore",
});
return "skip";
} catch {
return "build";
}
};

const commit = (message: string): string => {
NodeChildProcess.execFileSync("git", ["add", "-A"], { cwd: dir, stdio: "ignore" });
NodeChildProcess.execFileSync("git", ["commit", "-m", message], { cwd: dir, stdio: "ignore" });
return NodeChildProcess.execFileSync("git", ["rev-parse", "HEAD"], {
cwd: dir,
encoding: "utf8",
}).trim();
};

beforeAll(() => {
dir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "pylon-marketing-ignore-"));
NodeFS.mkdirSync(NodePath.join(dir, "apps/marketing/src"), { recursive: true });
NodeFS.mkdirSync(NodePath.join(dir, "packages/shared"), { recursive: true });
NodeChildProcess.execFileSync("git", ["init", "-q", "-b", "pylon"], {
cwd: dir,
stdio: "ignore",
});
NodeChildProcess.execFileSync("git", ["config", "user.email", "test@example.com"], {
cwd: dir,
stdio: "ignore",
});
NodeChildProcess.execFileSync("git", ["config", "user.name", "Test"], {
cwd: dir,
stdio: "ignore",
});
NodeFS.writeFileSync(NodePath.join(dir, "pnpm-lock.yaml"), "lockfile: 1\n");
NodeFS.writeFileSync(NodePath.join(dir, "pnpm-workspace.yaml"), "packages: []\n");
NodeFS.writeFileSync(NodePath.join(dir, "apps/marketing/src/index.astro"), "<h1>one</h1>\n");
commit("base");

NodeFS.writeFileSync(NodePath.join(dir, "apps/marketing/src/index.astro"), "<h1>two</h1>\n");
siteChanged = commit("touch the site");
});

afterAll(() => {
NodeFS.rmSync(dir, { recursive: true, force: true });
});

it("spends a build only for a product-branch push that moved the site", () => {
const base = NodeChildProcess.execFileSync("git", ["rev-parse", "HEAD^"], {
cwd: dir,
encoding: "utf8",
}).trim();
expect(run("pylon", base)).toBe("build");
});

it("skips a product-branch push that left the site alone", () => {
expect(run("pylon", siteChanged)).toBe("skip");
});

it("skips every other branch, even one that did move the site", () => {
const base = NodeChildProcess.execFileSync("git", ["rev-parse", "HEAD^"], {
cwd: dir,
encoding: "utf8",
}).trim();
expect(run("fix/unrelated", base)).toBe("skip");
});

it("skips a brand new branch, which has no previously deployed commit", () => {
// The regression: with no VERCEL_GIT_PREVIOUS_SHA the diff falls back to
// HEAD^, so any commit touching a lockfile used to buy a build.
NodeFS.writeFileSync(NodePath.join(dir, "pnpm-lock.yaml"), "lockfile: 2\n");
commit("bump the lockfile");
expect(run("feat/brand-new", "")).toBe("skip");
});
});
17 changes: 14 additions & 3 deletions apps/marketing/vercel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,21 @@ export const config: VercelConfig = {
// those pushes touch the site: on a busy day that is over a hundred builds and
// the account's build rate limit, which then fails unrelated pull requests and
// stalls the hosted app's release deploy. Exit 0 skips the build, anything
// else builds. The diff is against the branch's last deployed commit; when
// that commit is not in Vercel's shallow clone git fails, which builds.
// else builds.
//
// Only the product branch can publish this site, so nothing else is worth a
// build. Diffing alone did not hold: a push to a NEW branch has no previously
// deployed commit, so the fallback compares just the last commit, and both
// lockfiles move on nearly every pull request in this repository. Gate on the
// branch first, then still skip product-branch pushes that leave the site
// untouched. The diff is against the branch's last deployed commit; when that
// commit is not in Vercel's shallow clone git fails, which builds.
//
// Releases do not need a build at all: src/lib/releases.ts resolves the latest
// stable and nightly release in the browser from the GitHub API, so cutting a
// release never requires redeploying this site.
ignoreCommand:
'git diff --quiet "${VERCEL_GIT_PREVIOUS_SHA:-HEAD^}" HEAD -- . ../../packages/shared ../../pnpm-lock.yaml ../../pnpm-workspace.yaml',
'[ "$VERCEL_GIT_COMMIT_REF" != "pylon" ] || git diff --quiet "${VERCEL_GIT_PREVIOUS_SHA:-HEAD^}" HEAD -- . ../../packages/shared ../../pnpm-lock.yaml ../../pnpm-workspace.yaml',
redirects: [{ source: "/app", destination: "https://app.pylon-code.com", permanent: true }],
// Deployments were held off while this site still carried T3's product copy
// and legal pages. Those pages are gone — Pylon publishes no terms, privacy,
Expand Down
30 changes: 15 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading