1#!/usr/bin/env node
2// This file allows using Node.js in combination with
3// all available plugins. Usage: "node run <script>"
4import * as child_process from "node:child_process";
5import * as crypto from "node:crypto";
6import * as fs from "node:fs";
7import * as os from "node:os";
8import * as path from "node:path";
9import process from "node:process";
10import * as url from "node:url";
11import * as zlib from "node:zlib";
12
13// Disable experimental warnings (Type Stripping, etc)
14const { emit: originalEmit } = process;
15const warnings = ["ExperimentalWarning"];
16process.emit = function(event, error) {
17 return event === "warning" && warnings.includes(error.name)
18 ? false
19 : originalEmit.apply(process, arguments);
20};
21
22// Auto-install with npm if needed.
23{
24 const rootDir = path.dirname(url.fileURLToPath(import.meta.url));
25 const cloverDir = path.join(rootDir, ".clover");
26 fs.mkdirSync(cloverDir, { recursive: true });
27 function computeHash() {
28 const hash = crypto.createHash("sha1");
29 hash.update("npm");
30 hash.update(fs.readFileSync(path.join(rootDir, "package.json")));
31 hash.update(fs.readFileSync(path.join(rootDir, "package-lock.json")));
32 hash.update(fs.readdirSync(path.join(rootDir, "node_modules")).join("\0"));
33 return hash.digest("hex");
34 }
35 try {
36 const expected = fs.readFileSync(path.join(cloverDir, "deps-hash.txt"), "utf-8");
37 const actual = computeHash();
38 if (expected !== actual) throw new Error();
39 } catch {
40 child_process.execSync("npm i --no-audit --no-fund", { stdio: "inherit" });
41 console.info();
42 fs.writeFileSync(path.join(cloverDir, "deps-hash.txt"), computeHash());
43 }
44}
45
46let hot;
47try {
48 // zstd is not supported in node 22
49 await new Promise((resolve) => zlib.zstdCompress(new Uint8Array(), resolve));
50
51 // seems to fail in bun, run it earlier to catch
52 (await import("source-map-support")).install({ hookRequire: true });
53
54 // init hooks. some runtimes have bugs here. and long term this system should be rebuilt
55 hot = await import("./framework/hot.ts");
56 hot.load("./lib/log.ts");
57} catch (e) {
58 const brand = process.versions.bun
59 ? `bun ${process.versions.bun}`
60 : process.versions.deno
61 ? `deno ${process.versions.deno}`
62 : null;
63 const brandText = brand ? ` (${brand})` : "";
64 globalThis.console.error(
65 `sitegen depends on a node.js v24. your runtime is missing features.\n`
66 + `this is node.js version ${process.version}${brandText}\n\n`
67 + `get node.js --> https://nodejs.org/en/download/current\n`,
68 );
69 globalThis.console.error(e);
70 process.exit(1);
71 await new Promise(() => {});
72}
73
74const log = hot.load("./lib/log.ts");
75console.info = log.info;
76console.warn = log.warn;
77console.error = log.error;
78console.debug = log.debug;
79console["log"] = log.log;
80
81process.on("uncaughtException", (error) => {
82 console.error("Uncaught Exception");
83 console.error(error);
84 log.getDrawLock("long");
85 process.exit(1);
86});
87
88if (fs.existsSync(".env")) {
89 const env = fs.readFileSync(".env", "utf-8")
90 .split("\n").filter(Boolean)
91 .map((x) => x.trim().split("="));
92 for (const [k, v] of env) process.env[k] = v;
93}
94
95// Load with hooks
96if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) {
97 if (process.argv.length == 2) {
98 log.error("usage: node run <script> [...args]");
99 process.exit(1);
100 }
101 let found;
102 for (const dir of fs.globSync("{lib,{src,framework},{src,lib}/**/bin}")) {
103 try {
104 found = hot.resolveFrom(
105 import.meta.filename,
106 `./${dir}/${process.argv[2]}`,
107 );
108 break;
109 } catch (e) {
110 continue;
111 }
112 }
113 if (!found) {
114 log.error("Cannot find script: " + process.argv[2]);
115 process.exit(1);
116 }
117 process.argv = [process.argv[0], ...process.argv.slice(2)];
118 try {
119 const mod = await hot.load(found);
120 if (mod.main) mod.main();
121 else if (mod.default?.fetch) {
122 const protocol = "http";
123 const { serve } = hot.load("@hono/node-server");
124 serve(
125 {
126 fetch: mod.default.fetch,
127 port: mod.port ?? 3000,
128 },
129 ({ address, port }) => {
130 if (address === "::" || address === "127.0.0.1") {
131 address = os.hostname().replace(/\.local$/, "");
132 }
133 log.info(
134 url.format({
135 protocol,
136 hostname: address,
137 port,
138 }),
139 );
140 },
141 );
142 } else if (!found.match(/\.test\.[tj]sx?$/)) {
143 log.warn(
144 hot.load("path").relative(".", found),
145 "does not export a \"main\" function",
146 );
147 }
148 } catch (e) {
149 log.error(e);
150 }
151}
152
153export { hot };