Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

install.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

install.tsBlame175 lines · 1 contributor
46d6165Claude1/**
2 * Block L2 — one-command install.
3 *
63fe719Claude4 * GET /install -> the bash installer (scripts/install.sh).
5 * GET /install/vscode -> a tiny HTML landing for the VS Code extension
6 * with install instructions and a .vsix link if
7 * one has been uploaded to `public/`.
46d6165Claude8 *
9 * Curl-able: `curl -sSL https://gluecron.com/install | bash`.
10 *
11 * The script is read from disk once at module load and cached in memory; we
12 * also serve it with `Cache-Control: public, max-age=300` so any CDN in front
13 * of us can absorb the load. Mirrors the static-string pattern used by
14 * `src/routes/pwa.ts`.
15 */
16
17import { Hono } from "hono";
63fe719Claude18import { readFileSync, existsSync, statSync, readdirSync } from "fs";
46d6165Claude19import { join } from "path";
20
21const install = new Hono();
22
23// Resolve at module-load time. We try the on-disk script first; if anything
24// goes sideways (missing file in a stripped container image, weird CWD, etc.)
25// we fall back to a stub that points the user at the canonical URL so the
26// endpoint always returns *something* shell-safe.
27const FALLBACK_SCRIPT = `#!/usr/bin/env bash
28echo "Gluecron install script unavailable on this host." >&2
29echo "Fetch it directly from https://gluecron.com/install" >&2
30exit 1
31`;
32
33function loadScript(): string {
34 // Walk a few candidate locations so this works in dev, tests, and the
35 // fly.io container where CWD may be /app.
36 const candidates = [
37 join(process.cwd(), "scripts", "install.sh"),
38 join(import.meta.dir, "..", "..", "scripts", "install.sh"),
39 ];
40 for (const p of candidates) {
41 try {
42 const buf = readFileSync(p, "utf8");
43 if (buf && buf.length > 0) return buf;
44 } catch {
45 // try the next candidate
46 }
47 }
48 return FALLBACK_SCRIPT;
49}
50
51export const INSTALL_SCRIPT_SRC = loadScript();
52
53install.get("/install", (c) => {
54 c.header("content-type", "text/x-shellscript; charset=utf-8");
55 c.header("cache-control", "public, max-age=300");
56 // Make `curl https://gluecron.com/install -o install.sh` give a sensible
57 // default filename without forcing it for piped installs.
58 c.header("content-disposition", 'inline; filename="install.sh"');
59 return c.body(INSTALL_SCRIPT_SRC);
60});
61
63fe719Claude62// ─── VS Code extension landing ──────────────────────────────────────────────
63//
64// We don't (yet) have a marketplace listing, so this page shows install
65// instructions and — if a .vsix has been dropped into `public/` — a
66// download link. The page is intentionally minimalist: it does NOT pull
67// in the main app layout so a misbehaving CSS change can't break the
68// install funnel.
69
70const MARKETPLACE_URL =
71 "https://marketplace.visualstudio.com/items?itemName=gluecron.gluecron-vscode";
72
73function findVsix(): { name: string; size: number } | null {
74 // Look under `public/` so the file is served by the static handler
75 // (Bun's `Bun.file` route at /:filename). Keep it deterministic —
76 // largest-version sorted last after lexicographic sort works for our
77 // semver naming.
78 const candidates = [
79 join(process.cwd(), "public"),
80 join(import.meta.dir, "..", "..", "public"),
81 ];
82 for (const dir of candidates) {
83 try {
84 if (!existsSync(dir)) continue;
85 const files = readdirSync(dir).filter((f) => f.endsWith(".vsix"));
86 if (files.length === 0) continue;
87 files.sort();
88 const pick = files[files.length - 1];
89 const st = statSync(join(dir, pick));
90 return { name: pick, size: st.size };
91 } catch {
92 // try the next candidate
93 }
94 }
95 return null;
96}
97
98function formatBytes(n: number): string {
99 if (n < 1024) return `${n} B`;
100 if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
101 return `${(n / (1024 * 1024)).toFixed(1)} MiB`;
102}
103
104function vscodeLandingHtml(vsix: { name: string; size: number } | null): string {
105 const vsixBlock = vsix
106 ? `
107 <p>
108 <a class="cta" href="/${encodeURIComponent(vsix.name)}" download>
109 Download ${vsix.name} (${formatBytes(vsix.size)})
110 </a>
111 </p>
112 <pre><code>code --install-extension ${vsix.name}</code></pre>`
113 : `
114 <p class="muted">
115 No <code>.vsix</code> uploaded yet — build one yourself:
116 </p>
117 <pre><code>git clone https://gluecron.com/ccantynz/Gluecron.com
118cd Gluecron.com/editor-extensions/vscode
119npm install
120npm run compile
121npx vsce package
122code --install-extension gluecron-vscode-0.1.0.vsix</code></pre>`;
123
124 return `<!DOCTYPE html>
125<html lang="en">
126<head>
127<meta charset="utf-8" />
128<title>Gluecron for VS Code</title>
129<meta name="viewport" content="width=device-width, initial-scale=1" />
130<style>
131 :root { color-scheme: dark light; }
132 body { font: 15px/1.55 system-ui, sans-serif; max-width: 640px; margin: 5rem auto; padding: 0 1rem; }
133 h1 { margin-top: 0; font-size: 1.6rem; }
134 .badge { display: inline-block; padding: 4px 10px; border-radius: 4px; background: #2c2c2c; color: #fff; font-size: 13px; text-decoration: none; }
135 .cta { display: inline-block; padding: 8px 14px; border-radius: 6px; background: #1f6feb; color: #fff; text-decoration: none; font-weight: 600; }
136 pre { background: #0e1116; color: #e6edf3; padding: 12px; border-radius: 6px; overflow-x: auto; }
137 code { font: 13px ui-monospace, monospace; }
138 .muted { opacity: 0.75; }
139 ul { padding-left: 1.2rem; }
140</style>
141</head>
142<body>
143 <h1>Gluecron for VS Code</h1>
144 <p>
145 Chat with this repo, ship PRs, run specs, voice-to-PR, and let Claude write
146 your commit messages — without leaving the editor.
147 </p>
148 <p>
149 <a class="badge" href="${MARKETPLACE_URL}" rel="nofollow">
150 Marketplace listing (coming soon)
151 </a>
152 </p>
153 <h2>Install</h2>
154 ${vsixBlock}
155 <h2>What you get</h2>
156 <ul>
157 <li>Sidebar <strong>repo chat</strong> grounded in the current repository</li>
158 <li>One-click <strong>AI commit messages</strong> in the SCM title bar</li>
159 <li><strong>Open in Gluecron</strong> deep-links for the active file / line</li>
160 <li>Embedded <strong>pull requests</strong>, <strong>issues</strong>, and <strong>AI standups</strong></li>
161 <li><strong>Ship spec</strong> + <strong>voice-to-PR</strong> shortcuts</li>
162 </ul>
163 <p class="muted">Source: <a href="/ccantynz/Gluecron.com/tree/main/editor-extensions/vscode">editor-extensions/vscode</a></p>
164</body>
165</html>`;
166}
167
168install.get("/install/vscode", (c) => {
169 const vsix = findVsix();
170 c.header("content-type", "text/html; charset=utf-8");
171 c.header("cache-control", "public, max-age=300");
172 return c.body(vscodeLandingHtml(vsix));
173});
174
46d6165Claude175export default install;