From db244583d700a6c1a1367644f70d1e788a810599 Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Sun, 15 Jun 2025 11:35:28 -0700 Subject: [PATCH] work on porting paperclover.net and also some tests --- framework/backend/entry-node.ts | 1 + framework/bundle.ts | 20 +- framework/debug.safe.ts | 17 + framework/engine/ssr.test.tsx | 21 + framework/esbuild-support.ts | 6 + framework/generate.ts | 58 +- framework/incremental.ts | 4 +- framework/{queue.ts => lib/async.ts} | 489 ++++---- framework/lib/view.ts | 23 +- framework/test-fixture/Component.marko | 4 + framework/test-fixture/UseComponent.marko | 6 + framework/watch.ts | 10 +- package-lock.json | 1051 ++++++++++++++++- package.json | 2 + readme.md | 23 +- run.js | 39 +- src/admin.ts | 13 +- src/backend.ts | 13 + src/blog/pages/25/marko-intro.markodown | 283 +++++ src/pages/404.mdx | 6 - src/q+a/backend.ts | 6 +- src/q+a/image.tsx | 82 ++ src/q+a/pages/q+a.marko | 8 +- src/q+a/scripts/editor.client.tsx | 115 ++ .../clickable-links.client.ts | 0 .../question-form.marko} | 0 .../Question.marko => tags/question.marko} | 4 +- src/q+a/views/backend-inbox.client.ts | 74 ++ src/q+a/views/editor.css | 39 + src/q+a/views/editor.marko | 65 + src/q+a/views/image-embed.marko | 16 + src/q+a/views/permalink.marko | 47 + src/site.ts | 9 +- src/{components => tags}/Video.tsx | 0 src/{components => tags}/video.client.ts | 0 tsconfig.json | 3 +- 36 files changed, 2252 insertions(+), 305 deletions(-) create mode 100644 framework/debug.safe.ts rename framework/{queue.ts => lib/async.ts} (75%) create mode 100644 framework/test-fixture/Component.marko create mode 100644 framework/test-fixture/UseComponent.marko create mode 100644 src/blog/pages/25/marko-intro.markodown delete mode 100644 src/pages/404.mdx create mode 100644 src/q+a/image.tsx create mode 100644 src/q+a/scripts/editor.client.tsx rename src/q+a/{components => tags}/clickable-links.client.ts (100%) rename src/q+a/{components/QuestionForm.marko => tags/question-form.marko} (100%) rename src/q+a/{components/Question.marko => tags/question.marko} (88%) create mode 100644 src/q+a/views/editor.css create mode 100644 src/q+a/views/editor.marko create mode 100644 src/q+a/views/image-embed.marko create mode 100644 src/q+a/views/permalink.marko rename src/{components => tags}/Video.tsx (100%) rename src/{components => tags}/video.client.ts (100%) diff --git a/framework/backend/entry-node.ts b/framework/backend/entry-node.ts index 035a433..6c0a40d 100644 --- a/framework/backend/entry-node.ts +++ b/framework/backend/entry-node.ts @@ -1,4 +1,5 @@ import "@paperclover/console/inject"; +import "#debug"; const protocol = "http"; diff --git a/framework/bundle.ts b/framework/bundle.ts index 3d3daaf..762b2a4 100644 --- a/framework/bundle.ts +++ b/framework/bundle.ts @@ -39,6 +39,9 @@ export async function bundleClientJavaScript( write: false, metafile: true, external: ["node_modules/"], + jsx: "automatic", + jsxImportSource: "#ssr", + jsxDev: dev, }); if (bundle.errors.length || bundle.warnings.length) { throw new AggregateError( @@ -135,11 +138,22 @@ export async function bundleServerJavaScript( return ({ loader: "ts", contents: cacheEntry.src, + resolveDir: path.dirname(file), }); }, ); }, }, + { + name: "replace client references", + setup(b) { + b.onLoad({ filter: /\.tsx?$/ }, async ({ path: file }) => ({ + contents: + hot.resolveClientRefs(await fs.readFile(file, "utf-8"), file).code, + loader: path.extname(file).slice(1) as esbuild.Loader, + })); + }, + }, { name: "mark css external", setup(b) { @@ -154,7 +168,7 @@ export async function bundleServerJavaScript( }, }, ]; - const { metafile, outputFiles, warnings } = await esbuild.build({ + const { metafile, outputFiles } = await esbuild.build({ bundle: true, chunkNames: "c.[hash]", entryNames: "server", @@ -169,6 +183,9 @@ export async function bundleServerJavaScript( splitting: true, write: false, metafile: true, + jsx: "automatic", + jsxImportSource: "#ssr", + jsxDev: false, }); const files: Record = {}; @@ -273,3 +290,4 @@ import { } from "./esbuild-support.ts"; import { Incremental } from "./incremental.ts"; import * as css from "./css.ts"; +import * as fs from "#sitegen/fs"; diff --git a/framework/debug.safe.ts b/framework/debug.safe.ts new file mode 100644 index 0000000..009d7a8 --- /dev/null +++ b/framework/debug.safe.ts @@ -0,0 +1,17 @@ +globalThis.UNWRAP = (t, ...args) => { + if (t == null) { + throw new Error( + args.length > 0 ? util.format(...args) : "UNWRAP(" + t + ")", + ); + } + return t; +}; +globalThis.ASSERT = (t, ...args) => { + if (!t) { + throw new Error( + args.length > 0 ? util.format(...args) : "Assertion Failed", + ); + } +}; + +import * as util from "node:util"; diff --git a/framework/engine/ssr.test.tsx b/framework/engine/ssr.test.tsx index 0434258..e54dc99 100644 --- a/framework/engine/ssr.test.tsx +++ b/framework/engine/ssr.test.tsx @@ -18,3 +18,24 @@ test("simple tree", (t) => ).text, '

hello world

haha

1|0|||||
', )); +test("unescaped/escaped html", (t) => + t.assert.equal( + engine.ssrSync(
{engine.html("")}{"\"&'`<>"}
).text, + "
"&'`<>
", + )); +test("clsx built-in", (t) => + t.assert.equal( + engine.ssrSync( + <> + + + + + + + , + ).text, + '', + )); diff --git a/framework/esbuild-support.ts b/framework/esbuild-support.ts index 88faff5..eb7d5a3 100644 --- a/framework/esbuild-support.ts +++ b/framework/esbuild-support.ts @@ -64,6 +64,11 @@ export function projectRelativeResolution(root = process.cwd() + "/src") { path: path.resolve(root, id.slice(2)), }; }); + b.onResolve({ filter: /^#/ }, ({ path: id, importer }) => { + return { + path: hot.resolveFrom(importer, id), + }; + }); }, } satisfies esbuild.Plugin; } @@ -71,3 +76,4 @@ export function projectRelativeResolution(root = process.cwd() + "/src") { import * as esbuild from "esbuild"; import * as string from "#sitegen/string"; import * as path from "node:path"; +import * as hot from "./hot.ts"; diff --git a/framework/generate.ts b/framework/generate.ts index 6707fb9..f2c9812 100644 --- a/framework/generate.ts +++ b/framework/generate.ts @@ -74,26 +74,40 @@ export async function sitegen( dir: sectionPath("pages"), list: pages, prefix: "/", - exclude: [".css", ".client.ts", ".client.tsx"], + include: [".tsx", ".mdx", ".marko"], + exclude: [".client.ts", ".client.tsx"], + }, + { + dir: sectionPath("static"), + list: staticFiles, + prefix: "/", + ext: true, + }, + { + dir: sectionPath("scripts"), + list: scripts, + prefix: rootPrefix, + include: [".client.ts", ".client.tsx"], }, - { dir: sectionPath("static"), list: staticFiles, prefix: "/", ext: true }, - { dir: sectionPath("scripts"), list: scripts, prefix: rootPrefix }, { dir: sectionPath("views"), list: views, prefix: rootPrefix, - exclude: [".css", ".client.ts", ".client.tsx"], + include: [".tsx", ".mdx", ".marko"], + exclude: [".client.ts", ".client.tsx"], }, ]; - for (const { dir, list, prefix, exclude = [], ext = false } of kinds) { + for ( + const { dir, list, prefix, include = [""], exclude = [], ext = false } + of kinds + ) { const items = fs.readDirRecOptionalSync(dir); - item: for (const subPath of items) { + for (const subPath of items) { const file = path.join(dir, subPath); const stat = fs.statSync(file); if (stat.isDirectory()) continue; - for (const e of exclude) { - if (subPath.endsWith(e)) continue item; - } + if (!include.some((e) => subPath.endsWith(e))) continue; + if (exclude.some((e) => subPath.endsWith(e))) continue; const trim = ext ? subPath : subPath.slice(0, -path.extname(subPath).length).replaceAll( @@ -216,31 +230,33 @@ export async function sitegen( }, }); } - async function prepareView(view: FileItem) { - const module = require(view.file); + async function prepareView(item: FileItem) { + const module = require(item.file); if (!module.meta) { - throw new Error(`${view.file} is missing 'export const meta'`); + throw new Error(`${item.file} is missing 'export const meta'`); } if (!module.default) { - throw new Error(`${view.file} is missing a default export.`); + throw new Error(`${item.file} is missing a default export.`); } const pageTheme = module.layout?.theme ?? module.theme; const theme: css.Theme = { ...css.defaultTheme, ...pageTheme, }; - const cssImports = hot.getCssImports(view.file) - .concat("src/global.css") - .map((file) => path.relative(hot.projectSrc, path.resolve(file))); + const cssImports = Array.from( + new Set([globalCssPath, ...hot.getCssImports(item.file)]), + (file) => path.relative(hot.projectSrc, file), + ); + ensureCssGetsBuilt(cssImports, theme, item.id); incr.put({ kind: "viewMetadata", - key: view.id, - sources: [view.file], + key: item.id, + sources: [item.file], value: { - file: path.relative(hot.projectRoot, view.file), + file: path.relative(hot.projectRoot, item.file), cssImports, theme, - clientRefs: hot.getClientScriptRefs(view.file), + clientRefs: hot.getClientScriptRefs(item.file), hasLayout: !!module.layout?.default, }, }); @@ -422,7 +438,7 @@ function getItemText({ file }: FileItem) { return path.relative(hot.projectSrc, file).replaceAll("\\", "/"); } -import { OnceMap, Queue } from "./queue.ts"; +import { OnceMap, Queue } from "#sitegen/async"; import { Incremental } from "./incremental.ts"; import * as bundle from "./bundle.ts"; import * as css from "./css.ts"; diff --git a/framework/incremental.ts b/framework/incremental.ts index fd69232..1c87544 100644 --- a/framework/incremental.ts +++ b/framework/incremental.ts @@ -148,7 +148,7 @@ export class Incremental { for (const key of map.keys()) { if (!this.round.referenced.has(`${kind}\0${key}`)) { unreferenced.push({ kind: kind as ArtifactKind, key }); - console.warn("unreferened " + kind + " : " + key); + this.out[kind as ArtifactKind].delete(key); } } } @@ -652,7 +652,7 @@ const zstd = util.promisify(zlib.zstdCompress); import * as fs from "#sitegen/fs"; import * as zlib from "node:zlib"; import * as util from "node:util"; -import { Queue } from "./queue.ts"; +import { Queue } from "#sitegen/async"; import * as hot from "./hot.ts"; import * as mime from "#sitegen/mime"; import * as path from "node:path"; diff --git a/framework/queue.ts b/framework/lib/async.ts similarity index 75% rename from framework/queue.ts rename to framework/lib/async.ts index 08bfcfb..eebd3da 100644 --- a/framework/queue.ts +++ b/framework/lib/async.ts @@ -1,210 +1,279 @@ -interface QueueOptions { - name: string; - fn: (item: T, spin: Spinner) => Promise; - getItemText?: (item: T) => string; - maxJobs?: number; - passive?: boolean; -} - -// Process multiple items in parallel, queue up as many. -export class Queue { - #name: string; - #fn: (item: T, spin: Spinner) => Promise; - #maxJobs: number; - #getItemText: (item: T) => string; - #passive: boolean; - - #active: Spinner[] = []; - #queue: Array<[T] | [T, (result: R) => void, (err: unknown) => void]> = []; - - #cachedProgress: Progress<{ active: Spinner[] }> | null = null; - #done: number = 0; - #total: number = 0; - #onComplete: (() => void) | null = null; - #estimate: number | null = null; - #errors: unknown[] = []; - - constructor(options: QueueOptions) { - this.#name = options.name; - this.#fn = options.fn; - this.#maxJobs = options.maxJobs ?? 5; - this.#getItemText = options.getItemText ?? defaultGetItemText; - this.#passive = options.passive ?? false; - } - - get bar() { - const cached = this.#cachedProgress; - if (!cached) { - const bar = this.#cachedProgress = new Progress({ - spinner: null, - text: ({ active }) => { - const now = performance.now(); - let text = `[${this.#done}/${this.#total}] ${this.#name}`; - let n = 0; - for (const item of active) { - let itemText = "- " + item.format(now); - text += `\n` + - itemText.slice(0, Math.max(0, process.stdout.columns - 1)); - if (n > 10) { - text += `\n ... + ${active.length - n} more`; - break; - } - n++; - } - return text; - }, - props: { - active: [] as Spinner[], - }, - }); - bar.value = 0; - return bar; - } - return cached; - } - - add(args: T) { - this.#total += 1; - this.updateTotal(); - if (this.#active.length > this.#maxJobs) { - const { promise, resolve, reject } = Promise.withResolvers(); - this.#queue.push([args, resolve, reject]); - return promise; - } - return this.#run(args); - } - - addMany(items: T[]) { - this.#total += items.length; - this.updateTotal(); - - const runNowCount = this.#maxJobs - this.#active.length; - const runNow = items.slice(0, runNowCount); - const runLater = items.slice(runNowCount); - this.#queue.push(...runLater.reverse().map<[T]>((x) => [x])); - runNow.map((item) => this.#run(item).catch(() => {})); - } - - async #run(args: T): Promise { - const bar = this.bar; - const itemText = this.#getItemText(args); - const spinner = new Spinner(itemText); - spinner.stop(); - const active = this.#active; - try { - active.unshift(spinner); - bar.props = { active }; - const result = await this.#fn(args, spinner); - this.#done++; - return result; - } catch (err) { - if (err && typeof err === "object") { - (err as any).job = itemText; - } - this.#errors.push(err); - throw err; - } finally { - active.splice(active.indexOf(spinner), 1); - bar.props = { active }; - bar.value = this.#done; - - // Process next item - const next = this.#queue.shift(); - if (next) { - const args = next[0]; - this.#run(args) - .then((result) => next[1]?.(result)) - .catch((err) => next[2]?.(err)); - } else if (this.#active.length === 0) { - if (this.#passive) { - this.bar.stop(); - this.#cachedProgress = null; - } - this.#onComplete?.(); - } - } - } - - updateTotal() { - const bar = this.bar; - bar.total = Math.max(this.#total, this.#estimate ?? 0); - } - - set estimate(e: number) { - this.#estimate = e; - if (this.#cachedProgress) { - this.updateTotal(); - } - } - - async done(o: { method: "success" | "stop" }) { - if (this.#active.length === 0) { - this.#end(o); - return; - } - - const { promise, resolve } = Promise.withResolvers(); - this.#onComplete = resolve; - await promise; - this.#end(o); - } - - #end( - { method = this.#passive ? "stop" : "success" }: { - method: "success" | "stop"; - }, - ) { - const bar = this.#cachedProgress; - if (this.#errors.length > 0) { - if (bar) bar.stop(); - throw new AggregateError( - this.#errors, - this.#errors.length + " jobs failed in '" + this.#name + "'", - ); - } - - if (bar) bar[method](); - } - - get active(): boolean { - return this.#active.length !== 0; - } -} - -const cwd = process.cwd(); -function defaultGetItemText(item: unknown) { - let itemText = ""; - if (typeof item === "string") { - itemText = item; - } else if (typeof item === "object" && item !== null) { - const { path, label, id } = item as any; - itemText = label ?? path ?? id ?? JSON.stringify(item); - } else { - itemText = JSON.stringify(item); - } - - if (itemText.startsWith(cwd)) { - itemText = path.relative(cwd, itemText); - } - return itemText; -} - -export class OnceMap { - private ongoing = new Map>(); - - get(key: string, compute: () => Promise) { - if (this.ongoing.has(key)) { - return this.ongoing.get(key)!; - } - - const result = compute(); - this.ongoing.set(key, result); - result.finally(() => this.ongoing.delete(key)); - return result; - } -} - -import { Progress } from "@paperclover/console/Progress"; -import { Spinner } from "@paperclover/console/Spinner"; -import * as path from "node:path"; -import process from "node:process"; +const five_minutes = 5 * 60 * 1000; + +interface QueueOptions { + name: string; + fn: (item: T, spin: Spinner) => Promise; + getItemText?: (item: T) => string; + maxJobs?: number; + passive?: boolean; +} + +// Process multiple items in parallel, queue up as many. +export class Queue { + #name: string; + #fn: (item: T, spin: Spinner) => Promise; + #maxJobs: number; + #getItemText: (item: T) => string; + #passive: boolean; + + #active: Spinner[] = []; + #queue: Array<[T] | [T, (result: R) => void, (err: unknown) => void]> = []; + + #cachedProgress: Progress<{ active: Spinner[] }> | null = null; + #done: number = 0; + #total: number = 0; + #onComplete: (() => void) | null = null; + #estimate: number | null = null; + #errors: unknown[] = []; + + constructor(options: QueueOptions) { + this.#name = options.name; + this.#fn = options.fn; + this.#maxJobs = options.maxJobs ?? 5; + this.#getItemText = options.getItemText ?? defaultGetItemText; + this.#passive = options.passive ?? false; + } + + get bar() { + const cached = this.#cachedProgress; + if (!cached) { + const bar = this.#cachedProgress = new Progress({ + spinner: null, + text: ({ active }) => { + const now = performance.now(); + let text = `[${this.#done}/${this.#total}] ${this.#name}`; + let n = 0; + for (const item of active) { + let itemText = "- " + item.format(now); + text += `\n` + + itemText.slice(0, Math.max(0, process.stdout.columns - 1)); + if (n > 10) { + text += `\n ... + ${active.length - n} more`; + break; + } + n++; + } + return text; + }, + props: { + active: [] as Spinner[], + }, + }); + bar.value = 0; + return bar; + } + return cached; + } + + add(args: T) { + this.#total += 1; + this.updateTotal(); + if (this.#active.length > this.#maxJobs) { + const { promise, resolve, reject } = Promise.withResolvers(); + this.#queue.push([args, resolve, reject]); + return promise; + } + return this.#run(args); + } + + addMany(items: T[]) { + this.#total += items.length; + this.updateTotal(); + + const runNowCount = this.#maxJobs - this.#active.length; + const runNow = items.slice(0, runNowCount); + const runLater = items.slice(runNowCount); + this.#queue.push(...runLater.reverse().map<[T]>((x) => [x])); + runNow.map((item) => this.#run(item).catch(() => {})); + } + + async #run(args: T): Promise { + const bar = this.bar; + const itemText = this.#getItemText(args); + const spinner = new Spinner(itemText); + spinner.stop(); + const active = this.#active; + try { + active.unshift(spinner); + bar.props = { active }; + const result = await this.#fn(args, spinner); + this.#done++; + return result; + } catch (err) { + if (err && typeof err === "object") { + (err as any).job = itemText; + } + this.#errors.push(err); + throw err; + } finally { + active.splice(active.indexOf(spinner), 1); + bar.props = { active }; + bar.value = this.#done; + + // Process next item + const next = this.#queue.shift(); + if (next) { + const args = next[0]; + this.#run(args) + .then((result) => next[1]?.(result)) + .catch((err) => next[2]?.(err)); + } else if (this.#active.length === 0) { + if (this.#passive) { + this.bar.stop(); + this.#cachedProgress = null; + } + this.#onComplete?.(); + } + } + } + + updateTotal() { + const bar = this.bar; + bar.total = Math.max(this.#total, this.#estimate ?? 0); + } + + set estimate(e: number) { + this.#estimate = e; + if (this.#cachedProgress) { + this.updateTotal(); + } + } + + async done(o: { method: "success" | "stop" }) { + if (this.#active.length === 0) { + this.#end(o); + return; + } + + const { promise, resolve } = Promise.withResolvers(); + this.#onComplete = resolve; + await promise; + this.#end(o); + } + + #end( + { method = this.#passive ? "stop" : "success" }: { + method: "success" | "stop"; + }, + ) { + const bar = this.#cachedProgress; + if (this.#errors.length > 0) { + if (bar) bar.stop(); + throw new AggregateError( + this.#errors, + this.#errors.length + " jobs failed in '" + this.#name + "'", + ); + } + + if (bar) bar[method](); + } + + get active(): boolean { + return this.#active.length !== 0; + } +} + +const cwd = process.cwd(); +function defaultGetItemText(item: unknown) { + let itemText = ""; + if (typeof item === "string") { + itemText = item; + } else if (typeof item === "object" && item !== null) { + const { path, label, id } = item as any; + itemText = label ?? path ?? id ?? JSON.stringify(item); + } else { + itemText = JSON.stringify(item); + } + + if (itemText.startsWith(cwd)) { + itemText = path.relative(cwd, itemText); + } + return itemText; +} + +export class OnceMap { + private ongoing = new Map>(); + + get(key: string, compute: () => Promise) { + if (this.ongoing.has(key)) { + return this.ongoing.get(key)!; + } + + const result = compute(); + this.ongoing.set(key, result); + return result; + } +} + +interface ARCEValue { + value: T; + [Symbol.dispose]: () => void; +} + +export function RefCountedExpirable( + init: () => Promise, + deinit: (value: T) => void, + expire: number = five_minutes, +): () => Promise> { + let refs = 0; + let item: ARCEValue | null = null; + let loading: Promise> | null = null; + let timer: ReturnType | null = null; + + function deref() { + ASSERT(item !== null); + if (--refs !== 0) return; + ASSERT(timer === null); + timer = setTimeout(() => { + ASSERT(refs === 0); + ASSERT(loading === null); + ASSERT(item !== null); + deinit(item.value); + item = null; + timer = null; + }, expire); + } + + return async function () { + if (timer !== null) { + clearTimeout(timer); + timer = null; + } + if (item !== null) { + refs++; + return item; + } + if (loading !== null) { + refs++; + return loading; + } + const p = Promise.withResolvers>(); + loading = p.promise; + try { + const value = await init(); + item = { value, [Symbol.dispose]: deref }; + refs++; + p.resolve(item); + return item; + } catch (e) { + p.reject(e); + throw e; + } finally { + loading = null; + } + }; +} + +export function once(fn: () => Promise): () => Promise { + let result: T | Promise | null = null; + return async () => { + if (result) return result; + result = await fn(); + return result; + }; +} + +import { Progress } from "@paperclover/console/Progress"; +import { Spinner } from "@paperclover/console/Spinner"; +import * as path from "node:path"; +import process from "node:process"; diff --git a/framework/lib/view.ts b/framework/lib/view.ts index 0a422f6..fe78b43 100644 --- a/framework/lib/view.ts +++ b/framework/lib/view.ts @@ -16,7 +16,14 @@ let scripts: Record = null!; // boundaries, but those were never used. Pages will wait until they // are fully rendered before sending. export async function renderView( - c: hono.Context, + context: hono.Context, + id: string, + props: Record, +) { + return context.html(await renderViewToString(id, { context, ...props })); +} + +export async function renderViewToString( id: string, props: Record, ) { @@ -29,11 +36,11 @@ export async function renderView( inlineCss, layout, meta: metadata, - }: View = views[id]; + }: View = UNWRAP(views[id], `Missing view ${id}`); // -- metadata -- const renderedMetaPromise = Promise.resolve( - typeof metadata === "function" ? metadata({ context: c }) : metadata, + typeof metadata === "function" ? metadata(props) : metadata, ).then((m) => meta.renderMeta(m)); // -- html -- @@ -44,14 +51,17 @@ export async function renderView( }); // -- join document and send -- - return c.html(wrapDocument({ + return wrapDocument({ body, head: await renderedMetaPromise, inlineCss, scripts: joinScripts( - Array.from(sitegen.scripts, (script) => scripts[script]), + Array.from( + sitegen.scripts, + (id) => UNWRAP(scripts[id], `Missing script ${id}`), + ), ), - })); + }); } export function provideViewData(v: typeof views, s: typeof scripts) { @@ -87,5 +97,4 @@ export function wrapDocument({ import * as meta from "./meta.ts"; import type * as hono from "#hono"; import * as engine from "../engine/ssr.ts"; -import type * as css from "../css.ts"; import * as sg from "./sitegen.ts"; diff --git a/framework/test-fixture/Component.marko b/framework/test-fixture/Component.marko new file mode 100644 index 0000000..83ff635 --- /dev/null +++ b/framework/test-fixture/Component.marko @@ -0,0 +1,4 @@ +
+
+ wait(${null}) +
diff --git a/framework/test-fixture/UseComponent.marko b/framework/test-fixture/UseComponent.marko new file mode 100644 index 0000000..4881037 --- /dev/null +++ b/framework/test-fixture/UseComponent.marko @@ -0,0 +1,6 @@ +import Component from './Component.marko'; + +

web page

+ + + diff --git a/framework/watch.ts b/framework/watch.ts index 852fe23..2621b81 100644 --- a/framework/watch.ts +++ b/framework/watch.ts @@ -11,7 +11,7 @@ export async function main() { const watch = new Watch(rebuild); watch.add(...incr.invals.keys()); statusLine(); - // ... an + // ... and then serve it! serve(); function serve() { @@ -41,7 +41,13 @@ export async function main() { files = files.map((file) => path.relative(hot.projectRoot, file)); const changed: string[] = []; for (const file of files) { - if (incr.updateStat(file, fs.statSync(file).mtimeMs)) changed.push(file); + let mtimeMs: number | null = null; + try { + mtimeMs = fs.statSync(file).mtimeMs; + } catch (err: any) { + if (err?.code !== "ENOENT") throw err; + } + if (incr.updateStat(file, mtimeMs)) changed.push(file); } if (changed.length === 0) { console.warn("Files were modified but the 'modify' time did not change."); diff --git a/package-lock.json b/package-lock.json index 2145694..5a90bec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "hls.js": "^1.6.5", "hono": "^4.7.11", "marko": "^6.0.20", + "puppeteer": "^24.10.1", "unique-names-generator": "^4.7.1" }, "devDependencies": { @@ -950,6 +951,45 @@ "strip-ansi": "^7.1.0" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -1008,7 +1048,7 @@ "version": "22.15.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1020,6 +1060,16 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -1047,6 +1097,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -1074,6 +1133,39 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/astring": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", @@ -1083,6 +1175,12 @@ "astring": "bin/astring" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -1093,6 +1191,87 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/browserslist": { "version": "4.25.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", @@ -1125,12 +1304,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001721", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", @@ -1213,6 +1410,54 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chromium-bidi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", + "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/collapse-white-space": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", @@ -1223,6 +1468,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -1248,12 +1511,47 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -1284,6 +1582,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1306,12 +1618,42 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1452169", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz", + "integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==", + "license": "BSD-3-Clause" + }, "node_modules/electron-to-chromium": { "version": "1.5.165", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -1324,6 +1666,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/error-stack-parser": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", @@ -1414,6 +1765,59 @@ "node": ">=6" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-util-attach-comments": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", @@ -1505,12 +1909,56 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1520,6 +1968,44 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -1627,12 +2113,76 @@ "integrity": "sha512-qJiir3ViASVhGdNW4kwM4pQWvDtDLtjwaflEQDooiNzbOPIzoaIzmRFWweFl8GomVhJqXqX0jjrhDED3pmAdzg==", "license": "MIT" }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/inline-style-parser": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -1657,6 +2207,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -1667,6 +2223,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-hexadecimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", @@ -1695,6 +2260,24 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -1707,6 +2290,12 @@ "node": ">=6" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -1746,6 +2335,12 @@ "lasso-caching-fs": "^1.0.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -2566,18 +3161,86 @@ ], "license": "MIT" }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -2603,12 +3266,45 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -2619,6 +3315,88 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.10.1.tgz", + "integrity": "sha512-7T3rfSaaPt5A31VITV5YKQ4wPCCv4aPn8byDaV+9lhDU9v7BWYY4Ncwerw3ZR5mIolrh/PvzGdIDK7yiBth75g==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1452169", + "puppeteer-core": "24.10.1", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.10.1.tgz", + "integrity": "sha512-AE6doA9znmEEps/pC5lc9p0zejCdNLR6UBp3EZ49/15Nbvh+uklXxGox7Qh8/lFGqGVwxInl0TXmsOmIuIMwiQ==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1452169", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/raptor-async": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/raptor-async/-/raptor-async-1.1.3.tgz", @@ -2769,6 +3547,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -2796,6 +3583,44 @@ "semver": "bin/semver.js" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -2834,12 +3659,66 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", "license": "MIT" }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -2887,6 +3766,40 @@ "inline-style-parser": "0.2.4" } }, + "node_modules/tar-fs": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz", + "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -2907,11 +3820,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -2925,7 +3850,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unified": { @@ -3095,12 +4020,132 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.25.64", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz", + "integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 76abb02..565989d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "hls.js": "^1.6.5", "hono": "^4.7.11", "marko": "^6.0.20", + "puppeteer": "^24.10.1", "unique-names-generator": "^4.7.1" }, "devDependencies": { @@ -16,6 +17,7 @@ }, "imports": { "#backend": "./src/backend.ts", + "#debug": "./framework/debug.safe.ts", "#sitegen": "./framework/lib/sitegen.ts", "#sitegen/*": "./framework/lib/*.ts", "#ssr": "./framework/engine/ssr.ts", diff --git a/readme.md b/readme.md index 7fd05dd..be4ae24 100644 --- a/readme.md +++ b/readme.md @@ -3,26 +3,29 @@ this repository contains clover's "sitegen" framework, which is a set of tools that assist building websites. these tools power https://paperclover.net. -- HTML "Server Side Rendering") engine written from scratch. - - A more practical JSX runtime (`class` instead of `className`, etc). - - Transparent integration with [Marko][1] to mix component types. - - MDX support for text-heavy content pages. -- Incremental static site generator and build system +- **HTML "Server Side Rendering") engine written from scratch.** (~500 lines) + - A more practical JSX runtime (`class` instead of `className`, built-in + `clsx`, `html()` helper over `dangerouslySetInnerHTML` prop, etc). + - Integration with [Marko][1] for concisely written components. + - TODO: MDX-like compiler for content-heavy pages like blogs. + - Different languages can be used at the same time. Supports + `async function` components, ``, and custom extensions. +- **Incremental static site generator and build system.** - Build entire production site at start, incremental updates when pages change; Build system state survives coding sessions. - 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. + source-maps and stripped `console.debug` calls. The site you + see locally is the same site you see deployed. - (TODO) Tests, Lints, and Type-checking is run alongside, and only re-runs checks when the files change. For example, changing a component re-tests only pages that use that component and re-lints only the changed file. -- Integrated libraries for building complex, content heavy web sites. +- **Integrated libraries for building complex, content heavy web sites.** - Static asset serving with ETag and build-time compression. - Dynamicly rendered pages with static client. (`#import "#sitegen/view"`) - Databases with a typed SQLite wrapper. (`import "#sitegen/sqlite"`) - TODO: Meta and Open Graph generation. (`export const meta`) - - TODO: Font subsetting tools to reduce -- Built on the battle-tested Node.js runtime. Partial support for Deno and Bun. + - TODO: Font subsetting tools to reduce bytes downloaded by fonts. +- **Built on the battle-tested Node.js runtime.** Partial support for Deno and Bun. [1]: https://next.markojs.com diff --git a/run.js b/run.js index 2a0cba8..f7668cf 100644 --- a/run.js +++ b/run.js @@ -4,18 +4,17 @@ import * as util from "node:util"; import process from "node:process"; // Disable experimental warnings (Type Stripping, etc) -{ - const { emit: originalEmit } = process; - const warnings = ["ExperimentalWarning"]; - process.emit = function (event, error) { - return event === "warning" && warnings.includes(error.name) - ? false - : originalEmit.apply(process, arguments); - }; -} +const { emit: originalEmit } = process; +const warnings = ["ExperimentalWarning"]; +process.emit = function (event, error) { + return event === "warning" && warnings.includes(error.name) + ? false + : originalEmit.apply(process, arguments); +}; // Init hooks const hot = await import("./framework/hot.ts"); +await import("#debug"); const console = hot.load("@paperclover/console"); globalThis.console["log"] = console.info; @@ -24,22 +23,6 @@ globalThis.console.warn = console.warn; globalThis.console.error = console.error; globalThis.console.debug = console.scoped("dbg"); -globalThis.UNWRAP = (t, ...args) => { - if (t == null) { - throw new Error( - args.length > 0 ? util.format(...args) : "UNWRAP(" + t + ")", - ); - } - return t; -}; -globalThis.ASSERT = (t, ...args) => { - if (!t) { - throw new Error( - args.length > 0 ? util.format(...args) : "Assertion Failed", - ); - } -}; - // Load with hooks if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) { if (process.argv.length == 2) { @@ -60,7 +43,11 @@ if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) { process.exit(1); } process.argv = [process.argv[0], ...process.argv.slice(2)]; - hot.load(found).main?.(); + try { + await hot.load(found).main?.(); + } catch (e) { + console.error(util.inspect(e)); + } } export { hot }; diff --git a/src/admin.ts b/src/admin.ts index 3e9c394..f23260d 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -59,6 +59,17 @@ export function hasAdminToken(c: Context) { return token && compareToken(token); } -import * as fs from "node:fs"; +export async function main() { + const key = crypto.randomUUID(); + await fs.writeMkdir(".clover/admin-token.txt", key); + const start = ({ + win32: "start", + darwin: "open", + } as Record)[process.platform] ?? "xdg-open"; + child_process.exec(`${start} http://[::1]:3000/admin/login?key=${key}`); +} + +import * as fs from "#sitegen/fs"; import type { Context, Next } from "hono"; import { serveAsset } from "#sitegen/assets"; +import * as child_process from "node:child_process"; diff --git a/src/backend.ts b/src/backend.ts index 3e6c20f..3cba15e 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -15,6 +15,17 @@ app.route("", require("./q+a/backend.ts").app); app.use(assets.middleware); +if (process.argv.includes("--development")) { + app.onError((err, c) => { + if (err instanceof HTTPException) { + // Get the custom response + return err.getResponse(); + } + + return c.text(util.inspect(err), 500); + }); +} + export default app; async function removeDuplicateSlashes(c: Context, next: Next) { @@ -31,8 +42,10 @@ async function removeDuplicateSlashes(c: Context, next: Next) { } import { type Context, Hono, type Next } from "#hono"; +import { HTTPException } from "hono/http-exception"; import { logger } from "hono/logger"; import { trimTrailingSlash } from "hono/trailing-slash"; import * as assets from "#sitegen/assets"; import * as admin from "./admin.ts"; import { scoped } from "@paperclover/console"; +import * as util from "node:util"; diff --git a/src/blog/pages/25/marko-intro.markodown b/src/blog/pages/25/marko-intro.markodown new file mode 100644 index 0000000..49ef498 --- /dev/null +++ b/src/blog/pages/25/marko-intro.markodown @@ -0,0 +1,283 @@ +export const blog: BlogMeta = { + title: "Marko is the coziest HTML templating language", + desc: "...todo...", + date: "2025-06-13", + draft: true, +}; +export const meta = formatBlogMeta(blob); + +I've been recently playing around [Marko][1], and after adding limited support +for it in my website generator, [sitegen][2], I instantly fell in love with how +minimalistic it is in comparison to JSX, Astro components, and Svelte. + +## Introduction + +If JSX was taking HTML and shoving its syntax into JavaScript, Marko is shoving +JavaScript into HTML. Attributes are JavaScript expressions. + +```marko +
+ // `input` is like props, but in global scope + + + + // Capital letter variables for imported components + +
+ +// ESM `import` / `export` just work as expected. +// I prefer my imports at the end, to highlight the markup. +import MarkdownContent from "./MarkdownContent.marko"; +import { formatTimeNicely } from "../date-helpers.ts"; +``` + +Tags with the `value` attribute have a shorthand, which is used by the built-in +`` for conditional rendering. + +```marko +// Sugar for + + +// and it composes amazingly to the 'if' built-in + + + +``` + +Tags can also return values into the scope for use in the template using `/`, such as `` for unique ID generation. This is available to components that ``. + +``` + + + +
+ + + + + + + + + + + + +import { type PendingQuestion } from "@/q+a/models/PendingQuestion.ts"; +import { Question, QuestionType } from "@/q+a/models/Question.ts"; diff --git a/src/q+a/views/image-embed.marko b/src/q+a/views/image-embed.marko new file mode 100644 index 0000000..25366b8 --- /dev/null +++ b/src/q+a/views/image-embed.marko @@ -0,0 +1,16 @@ +export const meta = { title: "embed image" }; +export interface Input { + question: Question; +} + + + main { padding-top: 11px; } + e- { margin: 0!important } + e- > :first-child { margin-top: 0!important } + e- > :last-child { margin-bottom: 0!important } + + + + + +import { Question } from '@/q+a/models/Question'; diff --git a/src/q+a/views/permalink.marko b/src/q+a/views/permalink.marko new file mode 100644 index 0000000..c66406f --- /dev/null +++ b/src/q+a/views/permalink.marko @@ -0,0 +1,47 @@ +export interface Input { + question: Question; +} + +server export function meta({ context: { req }, question }) { + const isDiscord = req.get("user-agent") + ?.toLowerCase() + .includes("discordbot"); + if (question.type === QuestionType.normal) { + return { + title: "question permalink", + openGraph: { + images: [{ url: `https://paperclover.net/q+a/${q.id}.png` }], + }, + twitter: { card: "summary_large_image" }, + themeColor: isDiscord + ? q.date.getTime() > transitionDate ? "#8c78ff" : "#58ff71" + : undefined, + }; + } +} + + + + +

this page is a permalink to the following question:

+ + +

+ this page is a permalink to a question that + has not yet been answered. +

+

read questions with existing responses.

+ +

+ this page is a permalink to a question, but the question + was deleted instead of answered. maybe it was sent multiple + times, or maybe the question was not a question. who knows. +

+

sorry, sister

+

all questions

+ +

oh dear, this question is in an invalid state

+
${JSON.stringify(question, null, 2)}
+ + +import { Question, QuestionType } from '@/q+a/models/Question.ts'; diff --git a/src/site.ts b/src/site.ts index a5a6df5..b2be442 100644 --- a/src/site.ts +++ b/src/site.ts @@ -3,17 +3,16 @@ // sub-projects like the file viewer in 'file', or the question answer system // in 'q+a'. Each section can define configuration, pages, backend routes, and // contain other files. -interface Section { - root: string; -} - const join = (...paths: string[]) => path.join(import.meta.dirname, ...paths); export const siteSections: Section[] = [ - { root: join("./") }, + { root: join(".") }, { root: join("q+a/") }, { root: join("file-viewer/") }, { root: join("friends/") }, + // { root: join("blog/"), pageBase: "/blog" }, + // { root: join("fiction/"), pageBase: "/fiction" }, ]; import * as path from "node:path"; +import { Section } from "#sitegen"; diff --git a/src/components/Video.tsx b/src/tags/Video.tsx similarity index 100% rename from src/components/Video.tsx rename to src/tags/Video.tsx diff --git a/src/components/video.client.ts b/src/tags/video.client.ts similarity index 100% rename from src/components/video.client.ts rename to src/tags/video.client.ts diff --git a/tsconfig.json b/tsconfig.json index c7e6fda..f164744 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,6 @@ "strict": true, "verbaitimModuleSyntax": true, "target": "es2022" - } + }, + "include": ["framework/**/*", "src/**/*"] }