Skip to content

Commit 173d695

Browse files
hyperpolymathclaude
andcommitted
feat: add homoiconic run.js — platform-detect, git-cycle, play.sh delegation
Deno run script wrapping the existing homoiconic play.sh launcher with platform detection, git sync check, self-healing, and full git cycle. Preferred launch path: play.sh (existing bash launcher) → just run → binary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 505a226 commit 173d695

1 file changed

Lines changed: 281 additions & 0 deletions

File tree

run.js

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath)
3+
//
4+
// run.js — Homoiconic, fault-tolerant, platform-independent run script
5+
// for Game Server Admin (GSA)
6+
//
7+
// Usage:
8+
// deno run --allow-all run.js # auto-detect and launch
9+
// deno run --allow-all run.js --help # show usage
10+
// deno run --allow-all run.js --reflect
11+
12+
// ─────────────────────────────────────────────────────────────────────────────
13+
// REGISTRY — homoiconic data; the script reads this at runtime via reflect()
14+
// ─────────────────────────────────────────────────────────────────────────────
15+
const REGISTRY = {
16+
identity: {
17+
name: "game-server-admin",
18+
display: "Game Server Admin (GSA)",
19+
version: "0.3.0",
20+
license: "PMPL-1.0-or-later",
21+
repo: "https://github.com/hyperpolymath/game-server-admin",
22+
},
23+
launchers: {
24+
bash: "play.sh", // existing homoiconic bash launcher
25+
just: "Justfile",
26+
},
27+
binary: {
28+
zig: "src/interface/ffi/zig-out/bin/gsa",
29+
},
30+
git: {
31+
remote: "origin",
32+
branch: "main",
33+
mirrors: [],
34+
},
35+
capabilities: [
36+
"reflect", // reads own source (homoiconic)
37+
"detectPlatform", // OS, arch, display server
38+
"checkGitSync", // fetch + ahead/behind + dirty check
39+
"launchBash", // delegates to play.sh (existing homoiconic launcher)
40+
"launchJust", // delegates to just run
41+
"launchBinary", // runs zig-out/bin/gsa directly
42+
"gitCycle", // add, commit, push, mirror
43+
],
44+
};
45+
46+
// ─────────────────────────────────────────────────────────────────────────────
47+
// REFLECTION
48+
// ─────────────────────────────────────────────────────────────────────────────
49+
async function reflect() {
50+
const path = new URL(import.meta.url).pathname;
51+
const src = await Deno.readTextFile(path);
52+
return { path, lines: src.split("\n").length, capabilities: REGISTRY.capabilities };
53+
}
54+
55+
// ─────────────────────────────────────────────────────────────────────────────
56+
// PLATFORM DETECTION
57+
// ─────────────────────────────────────────────────────────────────────────────
58+
async function detectPlatform() {
59+
const os = Deno.build.os;
60+
const arch = Deno.build.arch;
61+
62+
let display = "unknown";
63+
if (os === "linux") {
64+
if (Deno.env.get("WAYLAND_DISPLAY")) display = "wayland";
65+
else if (Deno.env.get("DISPLAY")) display = "x11";
66+
else display = "headless";
67+
} else if (os === "darwin") display = "quartz";
68+
else if (os === "windows") display = "win32";
69+
70+
const has = async (cmd) => {
71+
try {
72+
const p = new Deno.Command("which", { args: [cmd], stdout: "null", stderr: "null" });
73+
return (await p.output()).success;
74+
} catch { return false; }
75+
};
76+
77+
return {
78+
os, arch, display,
79+
hasBash: os !== "windows",
80+
hasJust: await has("just"),
81+
hasDeno: await has("deno"),
82+
};
83+
}
84+
85+
// ─────────────────────────────────────────────────────────────────────────────
86+
// SHARED HELPERS
87+
// ─────────────────────────────────────────────────────────────────────────────
88+
async function run(cmd, args) {
89+
try {
90+
const p = new Deno.Command(cmd, { args, stdout: "piped", stderr: "piped" });
91+
const { code, stdout, stderr } = await p.output();
92+
return {
93+
ok: code === 0,
94+
out: new TextDecoder().decode(stdout).trim(),
95+
err: new TextDecoder().decode(stderr).trim(),
96+
};
97+
} catch (e) {
98+
return { ok: false, out: "", err: e.message };
99+
}
100+
}
101+
102+
const c = { reset:"\x1b[0m", bold:"\x1b[1m", green:"\x1b[32m", yellow:"\x1b[33m", cyan:"\x1b[36m" };
103+
const log = (m) => console.log(`${c.green}${c.reset} ${m}`);
104+
const warn = (m) => console.warn(`${c.yellow}${c.reset} ${m}`);
105+
const head = (m) => console.log(`\n${c.bold}${c.cyan}${m}${c.reset}`);
106+
107+
// ─────────────────────────────────────────────────────────────────────────────
108+
// GIT SYNC
109+
// ─────────────────────────────────────────────────────────────────────────────
110+
async function checkGitSync() {
111+
const sync = { dirty: false, ahead: 0, behind: 0, branch: "main", fetchError: false };
112+
113+
const branch = await run("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
114+
if (branch.ok) sync.branch = branch.out;
115+
116+
const fetch = await run("git", ["fetch", "--quiet", REGISTRY.git.remote]);
117+
if (!fetch.ok) { sync.fetchError = true; warn("git fetch failed — offline"); }
118+
119+
const rl = await run("git", ["rev-list", "--left-right", "--count",
120+
`${REGISTRY.git.remote}/${sync.branch}...HEAD`]);
121+
if (rl.ok) {
122+
const [b, a] = rl.out.split(/\s+/);
123+
sync.behind = parseInt(b, 10) || 0;
124+
sync.ahead = parseInt(a, 10) || 0;
125+
}
126+
127+
const diff = await run("git", ["status", "--porcelain"]);
128+
if (diff.ok && diff.out) sync.dirty = true;
129+
130+
const remotes = await run("git", ["remote", "-v"]);
131+
if (remotes.ok) {
132+
REGISTRY.git.mirrors = [...new Set(
133+
remotes.out.split("\n")
134+
.filter(l => l.includes("(push)") && !l.startsWith(REGISTRY.git.remote + "\t"))
135+
.map(l => l.split("\t")[0])
136+
)];
137+
}
138+
139+
return sync;
140+
}
141+
142+
// ─────────────────────────────────────────────────────────────────────────────
143+
// LAUNCH
144+
// ─────────────────────────────────────────────────────────────────────────────
145+
async function launchBash() {
146+
try {
147+
await Deno.stat(REGISTRY.launchers.bash);
148+
log(`Delegating to ${REGISTRY.launchers.bash} (homoiconic bash launcher)...`);
149+
const p = new Deno.Command("bash", {
150+
args: [REGISTRY.launchers.bash, "run"],
151+
stdin: "inherit", stdout: "inherit", stderr: "inherit",
152+
});
153+
const child = p.spawn();
154+
await child.status;
155+
return true;
156+
} catch { return false; }
157+
}
158+
159+
async function launchJust(platform) {
160+
if (!platform.hasJust) return false;
161+
try {
162+
await Deno.stat(REGISTRY.launchers.just);
163+
log("Launching via: just run");
164+
const p = new Deno.Command("just", {
165+
args: ["run"], stdin: "inherit", stdout: "inherit", stderr: "inherit",
166+
});
167+
const child = p.spawn();
168+
await child.status;
169+
return true;
170+
} catch { return false; }
171+
}
172+
173+
async function launchBinary() {
174+
try {
175+
await Deno.stat(REGISTRY.binary.zig);
176+
log(`Running binary: ${REGISTRY.binary.zig}`);
177+
const p = new Deno.Command(REGISTRY.binary.zig, {
178+
args: ["status"], stdin: "inherit", stdout: "inherit", stderr: "inherit",
179+
});
180+
const child = p.spawn();
181+
await child.status;
182+
return true;
183+
} catch { return false; }
184+
}
185+
186+
// ─────────────────────────────────────────────────────────────────────────────
187+
// GIT CYCLE
188+
// ─────────────────────────────────────────────────────────────────────────────
189+
async function gitCycle(sync) {
190+
head("── Git cycle ──");
191+
192+
const add = await run("git", ["add", "-A"]);
193+
if (!add.ok) { warn("git add failed: " + add.err); return; }
194+
195+
const staged = await run("git", ["diff", "--cached", "--stat"]);
196+
if (staged.out) {
197+
const commit = await run("git", ["commit", "-m",
198+
"chore: run.js launch cycle — auto-detected platform, git cycle"]);
199+
if (commit.ok) log("Committed outstanding changes");
200+
else warn("Commit failed: " + commit.err);
201+
} else {
202+
log("Nothing to commit");
203+
}
204+
205+
if (sync.ahead > 0 || staged.out) {
206+
const push = await run("git", ["push", REGISTRY.git.remote, sync.branch]);
207+
if (push.ok) log(`Pushed to ${REGISTRY.git.remote}/${sync.branch}`);
208+
else warn("Push failed: " + push.err);
209+
}
210+
211+
for (const mirror of REGISTRY.git.mirrors) {
212+
const mp = await run("git", ["push", mirror, sync.branch]);
213+
if (mp.ok) log(`Pushed to mirror: ${mirror}`);
214+
else warn(`Mirror push failed (${mirror}): ${mp.err}`);
215+
}
216+
}
217+
218+
// ─────────────────────────────────────────────────────────────────────────────
219+
// MAIN
220+
// ─────────────────────────────────────────────────────────────────────────────
221+
if (import.meta.main) {
222+
const args = Deno.args;
223+
224+
if (args.includes("--help") || args.includes("-h")) {
225+
console.log(`
226+
${c.bold}${REGISTRY.identity.display} — run.js${c.reset}
227+
${REGISTRY.identity.license} | ${REGISTRY.identity.repo}
228+
229+
Usage: deno run --allow-all run.js [OPTIONS]
230+
231+
Options:
232+
--help, -h Show this help
233+
--no-git Skip git sync check and post-launch git cycle
234+
--no-launch Git cycle only
235+
--reflect Print reflection data and exit
236+
`);
237+
Deno.exit(0);
238+
}
239+
240+
if (args.includes("--reflect")) {
241+
const r = await reflect();
242+
console.log(JSON.stringify({ registry: REGISTRY, reflection: r }, null, 2));
243+
Deno.exit(0);
244+
}
245+
246+
const skipGit = args.includes("--no-git");
247+
const doLaunch = !args.includes("--no-launch");
248+
249+
head(`${REGISTRY.identity.display} v${REGISTRY.identity.version}`);
250+
251+
const r = await reflect();
252+
log(`Reflected: ${r.lines} lines, ${r.capabilities.length} capabilities`);
253+
254+
head("Platform");
255+
const platform = await detectPlatform();
256+
log(`OS: ${platform.os}/${platform.arch} / display: ${platform.display}`);
257+
log(`just: ${platform.hasJust ? "available" : "not found"} | bash: ${platform.hasBash ? "yes" : "no"}`);
258+
259+
let sync = { dirty: false, ahead: 0, behind: 0, branch: "main", fetchError: false };
260+
if (!skipGit) {
261+
head("Git sync");
262+
sync = await checkGitSync();
263+
log(`Branch: ${sync.branch} | ahead: ${sync.ahead} | behind: ${sync.behind}`);
264+
if (sync.dirty) warn("Working tree has uncommitted changes");
265+
if (sync.behind > 0) warn(`${sync.behind} commit(s) behind remote`);
266+
}
267+
268+
if (doLaunch) {
269+
head("Launch");
270+
let launched = false;
271+
// play.sh is the preferred launcher — it's already homoiconic bash
272+
launched = launched || await launchBash();
273+
launched = launched || await launchJust(platform);
274+
launched = launched || await launchBinary();
275+
if (!launched) warn("No launch method available — check build / install tools");
276+
}
277+
278+
if (!skipGit) await gitCycle(sync);
279+
280+
head("Done");
281+
}

0 commit comments

Comments
 (0)