diff --git a/framework/backend/entry-passthru.ts b/framework/backend/entry-passthru.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1273b673008e9e7d4fcf886035252ce5f4e2176a
--- /dev/null
+++ b/framework/backend/entry-passthru.ts
@@ -0,0 +1,4 @@
+import "@paperclover/console/inject";
+export default app;
+
+import app from "#backend";
diff --git a/framework/css.ts b/framework/css.ts
index c7063c523d659e1f4cb8ce82281ced7e35e3f7dc..bc7ba8f647c2310105c0e41b6b0b8200e7c3b7dc 100644
--- a/framework/css.ts
+++ b/framework/css.ts
@@ -64,7 +64,7 @@ export async function bundleCssFiles(
}),
);
},
- } satisfies Plugin;
+ } satisfies esbuild.Plugin;
const build = await esbuild.build({
bundle: true,
entryPoints: ["$input$"],
@@ -85,6 +85,5 @@ export async function bundleCssFiles(
return outputFiles[0].text;
}
-import type { Plugin } from "esbuild";
import * as esbuild from "esbuild";
-import * as fs from "./fs.ts";
+import * as fs from "#sitegen/fs";
diff --git a/framework/fs.ts b/framework/fs.ts
deleted file mode 100644
index 4761f37f8d31b262d723161853230dc0e29946d7..0000000000000000000000000000000000000000
--- a/framework/fs.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-// File System APIs. Some custom APIs, but mostly a re-export a mix of built-in
-// Node.js sync+promise fs methods. For convenince.
-export {
- existsSync,
- readdir,
- readdirSync,
- readFile,
- readFileSync,
- rm,
- rmSync,
- stat,
- statSync,
- writeFile,
- writeFileSync,
-};
-
-export function mkdir(dir: string) {
- return nodeMkdir(dir, { recursive: true });
-}
-
-export function mkdirSync(dir: string) {
- return nodeMkdirSync(dir, { recursive: true });
-}
-
-export async function writeMkdir(file: string, contents: Buffer | string) {
- await mkdir(path.dirname(file));
- return writeFile(file, contents);
-}
-
-export function writeMkdirSync(file: string, contents: Buffer | string) {
- mkdirSync(path.dirname(file));
- return writeFileSync(file, contents);
-}
-
-export function readDirRecOptionalSync(dir: string) {
- try {
- return readdirSync(dir, { recursive: true, withFileTypes: true });
- } catch (err: any) {
- if (err.code === "ENOENT") return [];
- throw err;
- }
-}
-
-import * as path from "node:path";
-import {
- existsSync,
- mkdirSync as nodeMkdirSync,
- readdirSync,
- readFileSync,
- rmSync,
- statSync,
- writeFileSync,
-} from "node:fs";
-import {
- mkdir as nodeMkdir,
- readdir,
- readFile,
- rm,
- stat,
- writeFile,
-} from "node:fs/promises";
diff --git a/framework/generate.tsx b/framework/generate.tsx
index cbe24f7a1c6b98288d3615b2c87acb80995ecf8f..69825f29ca3251b3f83d55178ce962df03261423 100644
--- a/framework/generate.tsx
+++ b/framework/generate.tsx
@@ -290,10 +290,10 @@ import { OnceMap, Queue } from "./queue.ts";
import { Incremental } from "./incremental.ts";
import * as bundle from "./bundle.ts";
import * as css from "./css.ts";
-import * as fs from "./fs.ts";
-import { Spinner, withSpinner } from "@paperclover/console/Spinner";
-import * as meta from "./meta.ts";
import * as ssr from "./engine/ssr.ts";
-import * as sg from "#sitegen";
import * as hot from "./hot.ts";
+import * as fs from "#sitegen/fs";
+import * as sg from "#sitegen";
import * as path from "node:path";
+import * as meta from "#sitegen/meta";
+import { Spinner, withSpinner } from "@paperclover/console/Spinner";
diff --git a/framework/hot.ts b/framework/hot.ts
index 8e60aacff161b89d9eab8f868dfa744d64d86b15..1f612c4078a626df5645b51572317a39b3d89599 100644
--- a/framework/hot.ts
+++ b/framework/hot.ts
@@ -236,7 +236,7 @@ declare module "node:module" {
): unknown;
}
-import * as fs from "./fs.ts";
+import * as fs from "./lib/fs.ts";
import * as path from "node:path";
import { pathToFileURL } from "node:url";
import * as esbuild from "esbuild";
diff --git a/framework/incremental.ts b/framework/incremental.ts
index 793cf07dd1967602128fac66faad4a44e1c41d4d..d77f6762fb812809cc4059bcd1a06fa021e8b38c 100644
--- a/framework/incremental.ts
+++ b/framework/incremental.ts
@@ -252,11 +252,10 @@ export interface SerializedMeta {
script: [key: string, value: string][];
}
-import * as path from "node:path";
-import * as fs from "./fs.ts";
+import * as fs from "#sitegen/fs";
import * as zlib from "node:zlib";
import * as util from "node:util";
import { Queue } from "./queue.ts";
import * as hot from "./hot.ts";
-import * as mime from "./mime.ts";
+import * as mime from "#sitegen/mime";
import { Buffer } from "node:buffer";
diff --git a/framework/lib/assets.ts b/framework/lib/assets.ts
index 9dc295bb040c0cd6c9bec141a22b481a23c7150a..751571f87d1db018546f4bf9b070e1d551c3304d 100644
--- a/framework/lib/assets.ts
+++ b/framework/lib/assets.ts
@@ -105,7 +105,7 @@ function assetInner(c: Context, asset: BuiltAsset, status: StatusCode) {
return c.res = new Response(body, { headers, status });
}
-import * as fs from "../fs.ts";
+import * as fs from "#sitegen/fs";
import type { Context, Next } from "hono";
import type { StatusCode } from "hono/utils/http-status";
import type { BuiltAsset, BuiltAssetMap, View } from "../incremental.ts";
diff --git a/framework/lib/fs.ts b/framework/lib/fs.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4761f37f8d31b262d723161853230dc0e29946d7
--- /dev/null
+++ b/framework/lib/fs.ts
@@ -0,0 +1,61 @@
+// File System APIs. Some custom APIs, but mostly a re-export a mix of built-in
+// Node.js sync+promise fs methods. For convenince.
+export {
+ existsSync,
+ readdir,
+ readdirSync,
+ readFile,
+ readFileSync,
+ rm,
+ rmSync,
+ stat,
+ statSync,
+ writeFile,
+ writeFileSync,
+};
+
+export function mkdir(dir: string) {
+ return nodeMkdir(dir, { recursive: true });
+}
+
+export function mkdirSync(dir: string) {
+ return nodeMkdirSync(dir, { recursive: true });
+}
+
+export async function writeMkdir(file: string, contents: Buffer | string) {
+ await mkdir(path.dirname(file));
+ return writeFile(file, contents);
+}
+
+export function writeMkdirSync(file: string, contents: Buffer | string) {
+ mkdirSync(path.dirname(file));
+ return writeFileSync(file, contents);
+}
+
+export function readDirRecOptionalSync(dir: string) {
+ try {
+ return readdirSync(dir, { recursive: true, withFileTypes: true });
+ } catch (err: any) {
+ if (err.code === "ENOENT") return [];
+ throw err;
+ }
+}
+
+import * as path from "node:path";
+import {
+ existsSync,
+ mkdirSync as nodeMkdirSync,
+ readdirSync,
+ readFileSync,
+ rmSync,
+ statSync,
+ writeFileSync,
+} from "node:fs";
+import {
+ mkdir as nodeMkdir,
+ readdir,
+ readFile,
+ rm,
+ stat,
+ writeFile,
+} from "node:fs/promises";
diff --git a/framework/lib/meta.ts b/framework/lib/meta.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c0b65a977dd5209877fbcae58195ec2be35f594c
--- /dev/null
+++ b/framework/lib/meta.ts
@@ -0,0 +1,24 @@
+export interface Meta {
+ title: string;
+ description?: string | undefined;
+ openGraph?: OpenGraph;
+ alternates?: Alternates;
+}
+export interface OpenGraph {
+ title?: string;
+ description?: string | undefined;
+ type: string;
+ url: string;
+}
+export interface Alternates {
+ canonical: string;
+ types: { [mime: string]: AlternateType };
+}
+export interface AlternateType {
+ url: string;
+ title: string;
+}
+export function renderMeta({ title }: Meta): string {
+ return `
${esc(title)}`;
+}
+import { escapeHtml as esc } from "../engine/ssr.ts";
diff --git a/framework/lib/mime.ts b/framework/lib/mime.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3560a8c29b68069fe858f21b39515473c97fe7be
--- /dev/null
+++ b/framework/lib/mime.ts
@@ -0,0 +1,31 @@
+const entries = fs.readFileSync(
+ path.join(import.meta.dirname, "mime.txt"),
+ "utf8",
+)
+ .split("\n")
+ .map((line) => line.trim())
+ .filter((line) => line && !line.startsWith("#"))
+ .map((line) => line.split(/\s+/, 2) as [string, string]);
+const extensions = new Map(entries.filter((x) => x[0].startsWith(".")));
+const fullNames = new Map(entries.filter((x) => !x[0].startsWith(".")));
+
+/**
+ * Accepts:
+ * - Full file path or basename
+ * - Extension (with or without dot)
+ */
+export function contentTypeFor(file: string) {
+ const slash = file.indexOf("/");
+ if (slash !== -1) file = file.slice(slash + 1);
+ const dot = file.indexOf(".");
+ if (dot === -1) file = "." + file;
+ else if (dot > 0) {
+ let entry = fullNames.get(file);
+ if (entry) return entry;
+ file = file.slice(dot);
+ }
+ return extensions.get(file) ?? "application/octet-stream";
+}
+
+import * as fs from "#sitegen/fs";
+import * as path from "node:path";
diff --git a/framework/lib/mime.txt b/framework/lib/mime.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7cf59936e371a7a55e468ab37eb0725fbf92e11e
--- /dev/null
+++ b/framework/lib/mime.txt
@@ -0,0 +1,99 @@
+# media types
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types
+.aac audio/x-aac
+.aif audio/x-aiff
+.aifc audio/x-aiff
+.aiff audio/x-aiff
+.asm text/x-asm
+.avi video/x-msvideo
+.bat application/x-msdownload
+.c text/x-c
+.chat text/x-clover-chatlog
+.class application/java-vm
+.cmd application/x-msdownload
+.com application/x-msdownload
+.conf text/plain
+.cpp text/x-c
+.css text/css
+.csv text/csv
+.cxx text/x-c
+.def text/plain
+.diff text/plain
+.dll application/x-msdownload
+.dmg application/octet-stream
+.doc application/msword
+.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
+.epub application/epub+zip
+.exe application/x-msdownload
+.flv video/x-flv
+.fbx application/fbx
+.gz application/x-gzip
+.h text/x-c
+.h264 video/h264
+.hh text/x-c
+.htm text/html;charset=utf-8
+.html text/html;charset=utf-8
+.ico image/x-icon
+.ics text/calendar
+.in text/plain
+.jar application/java-archive
+.java text/x-java-source
+.jpeg image/jpeg
+.jpg image/jpeg
+.jpgv video/jpeg
+.jxl image/jxl
+.js application/javascript
+.json application/json
+.latex application/x-latex
+.list text/plain
+.log text/plain
+.m4a audio/mp4
+.man text/troff
+.mid audio/midi
+.midi audio/midi
+.mov video/quicktime
+.mp3 audio/mpeg
+.mp4 video/mp4
+.msh model/mesh
+.msi application/x-msdownload
+.obj application/octet-stream
+.ogg audio/ogg
+.otf application/x-font-otf
+.pdf application/pdf
+.png image/png
+.ppt application/vnd.ms-powerpoint
+.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
+.psd image/vnd.adobe.photoshop
+.py text/x-python
+.rar application/x-rar-compressed
+.rss application/rss+xml
+.rtf application/rtf
+.rtx text/richtext
+.s text/x-asm
+.pem application/x-pem-file"
+.ser application/java-serialized-object
+.sh application/x-sh
+.sig application/pgp-signature
+.silo model/mesh
+.svg image/svg+xml
+.t text/troff
+.tar application/x-tar
+.text text/plain
+.tgz application/x-gzip
+.tif image/tiff
+.tiff image/tiff
+.torrent application/x-bittorrent
+.ttc application/x-font-ttf
+.ttf application/x-font-ttf
+.txt text/plain
+.urls text/uri-list
+.v text/x-v
+.wav audio/x-wav
+.wmv video/x-ms-wmv
+.xls application/vnd.ms-excel
+.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+.xml application/xml
+.xps application/vnd.ms-xpsdocument
+
+# special cased based on file name
+rss.xml application/rss+xml
diff --git a/framework/lib/sqlite.ts b/framework/lib/sqlite.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4018c18741c3d76643448e32372557da502b4dc5
--- /dev/null
+++ b/framework/lib/sqlite.ts
@@ -0,0 +1,97 @@
+// Guard against reloads and bundler duplication.
+// @ts-ignore
+const map = globalThis[Symbol.for("clover.db")] ??= new Map<
+ string,
+ WrappedDatabase
+>();
+
+export function getDb(file: string) {
+ let db: WrappedDatabase | null = map.get(file);
+ if (db) return db;
+ const fileWithExt = file.includes(".") ? file : file + ".sqlite";
+ db = new WrappedDatabase(
+ new DatabaseSync(path.join(".clover/", fileWithExt)),
+ );
+ map.set(file, db);
+ return db;
+}
+
+export class WrappedDatabase {
+ node: DatabaseSync;
+ stmtTableMigrate: WeakRef | null = null;
+
+ constructor(node: DatabaseSync) {
+ this.node = node;
+ this.node.exec(`
+ create table if not exists clover_migrations (
+ key text not null primary key,
+ version integer not null
+ );
+ `);
+ }
+
+ // TODO: add migration support
+ // the idea is you keep `schema` as the new schema but can add
+ // migrations to the mix really easily.
+ table(name: string, schema: string) {
+ let s = this.stmtTableMigrate?.deref();
+ s ?? (this.stmtTableMigrate = new WeakRef(
+ s = this.node.prepare(`
+ insert or ignore into clover_migrations
+ (key, version) values (?, ?);
+ `),
+ ));
+ const { changes } = s.run(name, 1);
+ if (changes === 1) this.node.exec(schema);
+ }
+
+ prepare(
+ query: string,
+ ): Stmt {
+ return new Stmt(this.node.prepare(query));
+ }
+}
+
+export class Stmt {
+ #node: StatementSync;
+ #class: any | null = null;
+ constructor(node: StatementSync) {
+ this.#node = node;
+ }
+
+ /** Get one row */
+ get(...args: Args): Row | null {
+ const item = this.#node.get(...args as any) as Row;
+ if (!item) return null;
+ const C = this.#class;
+ if (C) Object.setPrototypeOf(item, C.prototype);
+ return item;
+ }
+ getNonNull(...args: Args) {
+ const item = this.get(...args);
+ if (!item) throw new Error("Query returned no result");
+ return item;
+ }
+ iter(...args: Args): Iterator {
+ return this.array(...args)[Symbol.iterator]();
+ }
+ /** Get all rows */
+ array(...args: Args): Row[] {
+ const array = this.#node.all(...args as any) as Row[];
+ const C = this.#class;
+ if (C) array.forEach((item) => Object.setPrototypeOf(item, C.prototype));
+ return array;
+ }
+ /** Return the number of changes / row ID */
+ run(...args: Args) {
+ return this.#node.run(...args as any);
+ }
+
+ as(Class: { new (): R }): Stmt {
+ this.#class = Class;
+ return this as any;
+ }
+}
+
+import { DatabaseSync, StatementSync } from "node:sqlite";
+import * as path from "node:path";
diff --git a/framework/meta.ts b/framework/meta.ts
deleted file mode 100644
index cfde26b31c77d52ada00b7344464aefe1192a0db..0000000000000000000000000000000000000000
--- a/framework/meta.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export interface Meta {
- title: string;
- description?: string | undefined;
- openGraph?: OpenGraph;
- alternates?: Alternates;
-}
-export interface OpenGraph {
- title?: string;
- description?: string | undefined;
- type: string;
- url: string;
-}
-export interface Alternates {
- canonical: string;
- types: { [mime: string]: AlternateType };
-}
-export interface AlternateType {
- url: string;
- title: string;
-}
-export function renderMeta({ title }: Meta): string {
- return `${esc(title)}`;
-}
-import { escapeHtml as esc } from "./engine/ssr.ts";
diff --git a/framework/mime.ts b/framework/mime.ts
deleted file mode 100644
index d71480b931e9716a12434f911b41da5a2940a4a8..0000000000000000000000000000000000000000
--- a/framework/mime.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-const entries = fs.readFileSync(
- path.join(import.meta.dirname, "mime.txt"),
- "utf8",
-)
- .split("\n")
- .map((line) => line.trim())
- .filter((line) => line && !line.startsWith("#"))
- .map((line) => line.split(/\s+/, 2) as [string, string]);
-const extensions = new Map(entries.filter((x) => x[0].startsWith(".")));
-const fullNames = new Map(entries.filter((x) => !x[0].startsWith(".")));
-
-/**
- * Accepts:
- * - Full file path or basename
- * - Extension (with or without dot)
- */
-export function contentTypeFor(file: string) {
- const slash = file.indexOf("/");
- if (slash !== -1) file = file.slice(slash + 1);
- const dot = file.indexOf(".");
- if (dot === -1) file = "." + file;
- else if (dot > 0) {
- let entry = fullNames.get(file);
- if (entry) return entry;
- file = file.slice(dot);
- }
- return extensions.get(file) ?? "application/octet-stream";
-}
-
-import * as fs from "./fs.ts";
-import * as path from "node:path";
diff --git a/framework/mime.txt b/framework/mime.txt
deleted file mode 100644
index 7cf59936e371a7a55e468ab37eb0725fbf92e11e..0000000000000000000000000000000000000000
--- a/framework/mime.txt
+++ /dev/null
@@ -1,99 +0,0 @@
-# media types
-# https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types
-.aac audio/x-aac
-.aif audio/x-aiff
-.aifc audio/x-aiff
-.aiff audio/x-aiff
-.asm text/x-asm
-.avi video/x-msvideo
-.bat application/x-msdownload
-.c text/x-c
-.chat text/x-clover-chatlog
-.class application/java-vm
-.cmd application/x-msdownload
-.com application/x-msdownload
-.conf text/plain
-.cpp text/x-c
-.css text/css
-.csv text/csv
-.cxx text/x-c
-.def text/plain
-.diff text/plain
-.dll application/x-msdownload
-.dmg application/octet-stream
-.doc application/msword
-.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
-.epub application/epub+zip
-.exe application/x-msdownload
-.flv video/x-flv
-.fbx application/fbx
-.gz application/x-gzip
-.h text/x-c
-.h264 video/h264
-.hh text/x-c
-.htm text/html;charset=utf-8
-.html text/html;charset=utf-8
-.ico image/x-icon
-.ics text/calendar
-.in text/plain
-.jar application/java-archive
-.java text/x-java-source
-.jpeg image/jpeg
-.jpg image/jpeg
-.jpgv video/jpeg
-.jxl image/jxl
-.js application/javascript
-.json application/json
-.latex application/x-latex
-.list text/plain
-.log text/plain
-.m4a audio/mp4
-.man text/troff
-.mid audio/midi
-.midi audio/midi
-.mov video/quicktime
-.mp3 audio/mpeg
-.mp4 video/mp4
-.msh model/mesh
-.msi application/x-msdownload
-.obj application/octet-stream
-.ogg audio/ogg
-.otf application/x-font-otf
-.pdf application/pdf
-.png image/png
-.ppt application/vnd.ms-powerpoint
-.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
-.psd image/vnd.adobe.photoshop
-.py text/x-python
-.rar application/x-rar-compressed
-.rss application/rss+xml
-.rtf application/rtf
-.rtx text/richtext
-.s text/x-asm
-.pem application/x-pem-file"
-.ser application/java-serialized-object
-.sh application/x-sh
-.sig application/pgp-signature
-.silo model/mesh
-.svg image/svg+xml
-.t text/troff
-.tar application/x-tar
-.text text/plain
-.tgz application/x-gzip
-.tif image/tiff
-.tiff image/tiff
-.torrent application/x-bittorrent
-.ttc application/x-font-ttf
-.ttf application/x-font-ttf
-.txt text/plain
-.urls text/uri-list
-.v text/x-v
-.wav audio/x-wav
-.wmv video/x-ms-wmv
-.xls application/vnd.ms-excel
-.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
-.xml application/xml
-.xps application/vnd.ms-xpsdocument
-
-# special cased based on file name
-rss.xml application/rss+xml
diff --git a/framework/sqlite.ts b/framework/sqlite.ts
deleted file mode 100644
index 1646932f64c99e59c25c24614ece6a81f72a194d..0000000000000000000000000000000000000000
--- a/framework/sqlite.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-// Guard against reloads and bundler duplication.
-// @ts-ignore
-const map = globalThis[Symbol.for("clover.db")] ??= new Map<
- string,
- WrappedDatabase
->();
-
-export function getDb(file: string) {
- let db: WrappedDatabase | null = map.get(file);
- if (db) return db;
- const fileWithExt = file.includes(".") ? file : file + ".sqlite";
- db = new WrappedDatabase(
- new DatabaseSync(path.join(".clover/", fileWithExt)),
- );
- map.set(file, db);
- return db;
-}
-
-export class WrappedDatabase {
- node: DatabaseSync;
- stmtTableMigrate: WeakRef | null = null;
-
- constructor(node: DatabaseSync) {
- this.node = node;
- this.node.exec(`
- create table if not exists clover_migrations (
- key text not null primary key,
- version integer not null
- );
- `);
- }
-
- // TODO: add migration support
- // the idea is you keep `schema` as the new schema but can add
- // migrations to the mix really easily.
- table(name: string, schema: string) {
- let s = this.stmtTableMigrate?.deref();
- s ?? (this.stmtTableMigrate = new WeakRef(
- s = this.node.prepare(`
- insert or ignore into clover_migrations
- (key, version) values (?, ?);
- `),
- ));
- const { changes } = s.run(name, 1);
- if (changes === 1) this.node.exec(schema);
- }
-
- prepare(
- query: string,
- ): Stmt {
- return new Stmt(this.node.prepare(query));
- }
-}
-
-export class Stmt {
- #node: StatementSync;
- #class: any | null = null;
- constructor(node: StatementSync) {
- this.#node = node;
- }
-
- /** Get one row */
- get(...args: Args): Row | null {
- const item = this.#node.get(...args as any) as Row;
- if (!item) return null;
- const C = this.#class;
- if (C) Object.setPrototypeOf(item, C.prototype);
- return item;
- }
- getNonNull(...args: Args) {
- const item = this.get(...args);
- if (!item) throw new Error("Query returned no result");
- return item;
- }
- iter(...args: Args): Iterator {
- return this.array(...args)[Symbol.iterator]();
- }
- /** Get all rows */
- array(...args: Args): Row[] {
- const array = this.#node.all(...args as any) as Row[];
- const C = this.#class;
- if (C) array.forEach((item) => Object.setPrototypeOf(item, C.prototype));
- return array;
- }
- /** Return the number of changes / row ID */
- run(...args: Args) {
- return this.#node.run(...args as any);
- }
-
- as(Class: { new (): R }): Stmt {
- this.#class = Class;
- return this as any;
- }
-}
-
-import { DatabaseSync, StatementSync } from "node:sqlite";
-import * as fs from "./fs.ts";
-import * as path from "node:path";
diff --git a/framework/typecheck.ts b/framework/typecheck.ts
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/package-lock.json b/package-lock.json
index 805fb0344b53ef18eb404e4e8f1d7e7e10e0ebe9..214569439dc8d9c4d0bc2ab65f99243f37f02986 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -943,7 +943,7 @@
}
},
"node_modules/@paperclover/console": {
- "resolved": "git+https://git.paperclover.net/clo/console.git#29ae0fe6bb948723655b2d875faef75433b78ac0",
+ "resolved": "git+https://git.paperclover.net/clo/console.git#acdad1b233b5e27dd8ff817d3b8ee7f1a88519d3",
"dependencies": {
"ansi-escapes": "^7.0.0",
"chalk": "^5.4.1",
diff --git a/package.json b/package.json
index ef8de70c34aea6e9962c5aed9dbae4b131d3c943..76abb02be19c37d5793123e1abb9b21ed4902eed 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,6 @@
"#backend": "./src/backend.ts",
"#sitegen": "./framework/lib/sitegen.ts",
"#sitegen/*": "./framework/lib/*.ts",
- "#sqlite": "./framework/sqlite.ts",
"#ssr": "./framework/engine/ssr.ts",
"#ssr/jsx-dev-runtime": "./framework/engine/jsx-runtime.ts",
"#ssr/jsx-runtime": "./framework/engine/jsx-runtime.ts",
diff --git a/readme.md b/readme.md
index 533018db6669d78935b16cdaf3a596038fe108f4..37bd508908acc582cb927aedd983e1531dabddf7 100644
--- a/readme.md
+++ b/readme.md
@@ -13,7 +13,48 @@ that assist building websites. these tools power https://paperclover.net.
- The only difference in development and production mode is hidden
source-maps and stripped assertions and `console.debug` calls. The site
you see locally is the site you see deployed.
+- Tools for building complex, content heavy web sites.
+ - Static asset serving with ETag and build-time compression.
+ - Dynamic server side rendering from within backend code.
+ - Databases with a typed SQLite wrapper.
- Built on the battle-tested Node.js runtime. Partial support for Deno and Bun.
[1]: https://next.markojs.com
+Included is `src`, which contains `paperclover.net`. Website highlights:
+
+- [Question/Answer board, custom markdown parser and components][q+a].
+- [File viewer with prefetching + client-side navigation][file].
+- [Personal, friends-only blog with password protection][friends].
+
+[q+a]: https://paperclover.net/q+a
+[file]: https://paperclover.net/file
+[friends]: https://paperclover.net/friends
+
+## Development
+
+```
+npm install
+
+# production generation
+node run generate
+
+# live development environment
+node repl
+```
+
+`repl.js` will open a read-eval-print-loop where plugin state is cached (on my
+2014 dev laptop, startup time is 600-1000ms). every file in `framework` and
+`src` besides `hot.ts` can be edited and quickly re-run. for example, to run
+`framework/generate.ts`, you can type "generate" into the shell. since
+top-level await is not supported (plugins are built on `require` as Node has
+poor module support), CLIs can include a `main` function, which is executed
+when the REPL runs it.
+
+TODO: in the future, the repl will have a "dev" command which will do the incremental
+site build + host, possibly immediatly on opening the repl.
+
+## Contributions
+
+No contributions to `src` accepted, only `framework`.
+
diff --git a/src/admin.ts b/src/admin.ts
index 96691e6a59aea94e0436bee5afeae5a156bdada3..3e9c3943a87af1db9b146b9125f6924e5519ec89 100644
--- a/src/admin.ts
+++ b/src/admin.ts
@@ -59,6 +59,6 @@ export function hasAdminToken(c: Context) {
return token && compareToken(token);
}
-import * as fs from "fs";
+import * as fs from "node:fs";
import type { Context, Next } from "hono";
import { serveAsset } from "#sitegen/assets";
diff --git a/src/backend.ts b/src/backend.ts
index 5cbda382711d713315caedd0a1ab1fec29bb68a3..3e6c20fee5870a9daa9ba18cd580222cc695ce4c 100644
--- a/src/backend.ts
+++ b/src/backend.ts
@@ -30,7 +30,7 @@ async function removeDuplicateSlashes(c: Context, next: Next) {
await next();
}
-import { Hono } from "#hono";
+import { type Context, Hono, type Next } from "#hono";
import { logger } from "hono/logger";
import { trimTrailingSlash } from "hono/trailing-slash";
import * as assets from "#sitegen/assets";
diff --git a/src/q+a/models/PendingQuestion.ts b/src/q+a/models/PendingQuestion.ts
index f1a239ac6434571fcdae581c989909060a89cebf..4dea9a7fa9942bbd7d7a4aab0210eff0bb6ffd40 100644
--- a/src/q+a/models/PendingQuestion.ts
+++ b/src/q+a/models/PendingQuestion.ts
@@ -66,7 +66,7 @@ const getByDateQuery = db.prepare<[qmid: number]>(`
SELECT * FROM questions WHERE qmid = ? AND type = ${QuestionType.pending} LIMIT 1
`).as(PendingQuestion);
-import { getDb } from "#sqlite";
+import { getDb } from "#sitegen/sqlite";
import assert from "node:assert";
import { QuestionType } from "./Question.ts";
import { formatQuestionId } from "../format.ts";
diff --git a/src/q+a/models/Question.ts b/src/q+a/models/Question.ts
index 9f4299415783c5297a3730d500dfe5b4a7e7b67b..d382955cd2b2fff787c7514cc0b305a20b10faa4 100644
--- a/src/q+a/models/Question.ts
+++ b/src/q+a/models/Question.ts
@@ -115,4 +115,4 @@ const updateByQmidQuery = db.prepare<
`);
import { formatQuestionId } from "../format.ts";
-import { getDb } from "#sqlite";
+import { getDb } from "#sitegen/sqlite";