Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
96 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Deploy ends with a smoke test: sign-in, sign-up and the waitlist still work on g1t.sh1#!/usr/bin/env node
2// After a deploy: the ways in for someone new still work. The pages a
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.3// visitor lands on load, and the sign-up form reaches identity and
4// answers. It writes nothing: the form is sent an address identity
5// refuses before it keeps or counts anything, so no account is made and
6// the sign-up rate limit is not used up.
Deploy ends with a smoke test: sign-in, sign-up and the waitlist still work on g1t.sh7//
8// node scripts/ops/smoke.mjs # https://g1t.sh
9// node scripts/ops/smoke.mjs --site http://localhost:5173
10//
11// Exits 1 with what failed. The Deploy workflow runs it once every stage
12// has deployed.
13
14import { pathToFileURL } from "node:url";
15
16/** The checks, in order. `body` is the page's text with tags removed. */
17export const CHECKS = [
The smoke check looks for the landing page's Get started button: the page has said Get started instead of Start for free since 81a83931, so Deploy #248 failed its smoke on a landing page that was fine.18 { name: "Landing page", path: "/", expect: ["Sign up", "Get started"] },
Deploy ends with a smoke test: sign-in, sign-up and the waitlist still work on g1t.sh19 { name: "Sign in", path: "/login", expect: ["Sign in"] },
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.20 { name: "Sign up", path: "/register", expect: ["Create your account", "Create account"] },
Deploy ends with a smoke test: sign-in, sign-up and the waitlist still work on g1t.sh21 { name: "Pricing", path: "/pricing", expect: ["Pricing"] },
22 {
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.23 name: "Sign up reaches identity",
Deploy ends with a smoke test: sign-in, sign-up and the waitlist still work on g1t.sh24 path: "/register",
25 // Not an address, so identity answers "Enter a valid email address."
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.26 // before its sign-up rate limit and before any write.
27 form: { username: "smoke-test", email: "smoke-test-not-an-address", password: "not-a-real-password" },
Deploy ends with a smoke test: sign-in, sign-up and the waitlist still work on g1t.sh28 status: 422,
29 expect: ["Enter a valid email address."],
30 },
31];
32
33/** A page's visible text: scripts and styles dropped, tags removed, spaces collapsed. */
34export function pageText(html) {
35 return html
36 .replace(/<(script|style)\b[\s\S]*?<\/\1>/gi, " ")
37 .replace(/<[^>]+>/g, " ")
38 .replace(/&nbsp;|&#160;/g, " ")
39 .replace(/&amp;/g, "&")
40 .replace(/&#x27;|&#39;/g, "'")
41 .replace(/&quot;/g, '"')
42 .replace(/\s+/g, " ")
43 .trim();
44}
45
46/** What is wrong with one answer, or null when it is as expected. */
47export function verdict(check, status, html) {
48 const want = check.status ?? 200;
49 if (status !== want) return `answered ${status}, expected ${want}`;
50 const text = pageText(html);
51 const missing = check.expect.filter((phrase) => !text.includes(phrase));
52 return missing.length ? `missing ${missing.map((m) => JSON.stringify(m)).join(", ")}` : null;
53}
54
55async function run(site) {
56 const origin = new URL(site).origin;
57 let failed = 0;
58 for (const check of CHECKS) {
59 const url = new URL(check.path, origin);
60 const started = Date.now();
61 let problem;
62 try {
63 const response = await fetch(url, {
64 method: check.form ? "POST" : "GET",
65 redirect: "manual",
66 headers: {
67 "user-agent": "g1t-smoke/1 (+https://g1t.sh)",
68 ...(check.form ? { origin, "content-type": "application/x-www-form-urlencoded" } : {}),
69 },
70 body: check.form ? new URLSearchParams(check.form).toString() : undefined,
71 signal: AbortSignal.timeout(20_000),
72 });
73 problem = verdict(check, response.status, await response.text());
74 } catch (error) {
75 problem = String(error?.message ?? error);
76 }
77 const took = `${Date.now() - started} ms`;
78 if (problem) {
79 failed += 1;
80 console.log(`FAIL ${check.name} (${check.form ? "POST" : "GET"} ${url.pathname}): ${problem} · ${took}`);
81 } else {
82 console.log(`ok ${check.name} · ${took}`);
83 }
84 }
85 return failed;
86}
87
88if (import.meta.url === `file://${process.argv[1]?.replace(/\\/g, "/").replace(/^(?=[A-Za-z]:)/, "/")}`) {
89 const at = process.argv.indexOf("--site");
90 const site = at > -1 ? process.argv[at + 1] : "https://g1t.sh";
91 const failed = await run(site);
92 if (failed) {
93 console.log(`${failed} of ${CHECKS.length} checks failed on ${site}`);
94 process.exit(1);
95 }
96}

This file's history is long; its oldest lines are credited to the oldest commit read.