| author | |
| committer | |
| log | dad822b38eaec73a2696f012cd4e8a4117dd2e87 |
| tree | c68c53bc6e4f35204ad662ebc970f8fc2db7dffe |
| parent | 76133659a0900c85d5f2442f79ab61732f3cb22b |
| signature | Commit is signed but in an unrecognized format. |
42 files changed, 2450 insertions(+), 503 deletions(-)
framework/backend/entry-node.ts+1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import "@paperclover/console/inject"; | 1 | import "@paperclover/console/inject"; |
| 2 | import "#debug"; | ||
| 2 | 3 | ||
| 3 | const protocol = "http"; | 4 | const protocol = "http"; |
| 4 | 5 |
framework/bundle.ts+19-1| ... | @@ -39,6 +39,9 @@ export async function bundleClientJavaScript( | ... | @@ -39,6 +39,9 @@ export async function bundleClientJavaScript( |
| 39 | write: false, | 39 | write: false, |
| 40 | metafile: true, | 40 | metafile: true, |
| 41 | external: ["node_modules/"], | 41 | external: ["node_modules/"], |
| 42 | jsx: "automatic", | ||
| 43 | jsxImportSource: "#ssr", | ||
| 44 | jsxDev: dev, | ||
| 42 | }); | 45 | }); |
| 43 | if (bundle.errors.length || bundle.warnings.length) { | 46 | if (bundle.errors.length || bundle.warnings.length) { |
| 44 | throw new AggregateError( | 47 | throw new AggregateError( |
| ... | @@ -135,11 +138,22 @@ export async function bundleServerJavaScript( | ... | @@ -135,11 +138,22 @@ export async function bundleServerJavaScript( |
| 135 | return ({ | 138 | return ({ |
| 136 | loader: "ts", | 139 | loader: "ts", |
| 137 | contents: cacheEntry.src, | 140 | contents: cacheEntry.src, |
| 141 | resolveDir: path.dirname(file), | ||
| 138 | }); | 142 | }); |
| 139 | }, | 143 | }, |
| 140 | ); | 144 | ); |
| 141 | }, | 145 | }, |
| 142 | }, | 146 | }, |
| 147 | { | ||
| 148 | name: "replace client references", | ||
| 149 | setup(b) { | ||
| 150 | b.onLoad({ filter: /\.tsx?$/ }, async ({ path: file }) => ({ | ||
| 151 | contents: | ||
| 152 | hot.resolveClientRefs(await fs.readFile(file, "utf-8"), file).code, | ||
| 153 | loader: path.extname(file).slice(1) as esbuild.Loader, | ||
| 154 | })); | ||
| 155 | }, | ||
| 156 | }, | ||
| 143 | { | 157 | { |
| 144 | name: "mark css external", | 158 | name: "mark css external", |
| 145 | setup(b) { | 159 | setup(b) { |
| ... | @@ -154,7 +168,7 @@ export async function bundleServerJavaScript( | ... | @@ -154,7 +168,7 @@ export async function bundleServerJavaScript( |
| 154 | }, | 168 | }, |
| 155 | }, | 169 | }, |
| 156 | ]; | 170 | ]; |
| 157 | const { metafile, outputFiles, warnings } = await esbuild.build({ | 171 | const { metafile, outputFiles } = await esbuild.build({ |
| 158 | bundle: true, | 172 | bundle: true, |
| 159 | chunkNames: "c.[hash]", | 173 | chunkNames: "c.[hash]", |
| 160 | entryNames: "server", | 174 | entryNames: "server", |
| ... | @@ -169,6 +183,9 @@ export async function bundleServerJavaScript( | ... | @@ -169,6 +183,9 @@ export async function bundleServerJavaScript( |
| 169 | splitting: true, | 183 | splitting: true, |
| 170 | write: false, | 184 | write: false, |
| 171 | metafile: true, | 185 | metafile: true, |
| 186 | jsx: "automatic", | ||
| 187 | jsxImportSource: "#ssr", | ||
| 188 | jsxDev: false, | ||
| 172 | }); | 189 | }); |
| 173 | 190 | ||
| 174 | const files: Record<string, Buffer> = {}; | 191 | const files: Record<string, Buffer> = {}; |
| ... | @@ -273,3 +290,4 @@ import { | ... | @@ -273,3 +290,4 @@ import { |
| 273 | } from "./esbuild-support.ts"; | 290 | } from "./esbuild-support.ts"; |
| 274 | import { Incremental } from "./incremental.ts"; | 291 | import { Incremental } from "./incremental.ts"; |
| 275 | import * as css from "./css.ts"; | 292 | import * as css from "./css.ts"; |
| 293 | import * as fs from "#sitegen/fs"; |
framework/debug.safe.ts created+17| ... | @@ -0,0 +1,17 @@ | ||
| 1 | globalThis.UNWRAP = (t, ...args) => { | ||
| 2 | if (t == null) { | ||
| 3 | throw new Error( | ||
| 4 | args.length > 0 ? util.format(...args) : "UNWRAP(" + t + ")", | ||
| 5 | ); | ||
| 6 | } | ||
| 7 | return t; | ||
| 8 | }; | ||
| 9 | globalThis.ASSERT = (t, ...args) => { | ||
| 10 | if (!t) { | ||
| 11 | throw new Error( | ||
| 12 | args.length > 0 ? util.format(...args) : "Assertion Failed", | ||
| 13 | ); | ||
| 14 | } | ||
| 15 | }; | ||
| 16 | |||
| 17 | import * as util from "node:util"; | ||
framework/engine/ssr.test.tsx+21| ... | @@ -18,3 +18,24 @@ test("simple tree", (t) => | ... | @@ -18,3 +18,24 @@ test("simple tree", (t) => |
| 18 | ).text, | 18 | ).text, |
| 19 | '<main class="a b"><h1 style=background-color:red>hello world</h1><p>haha</p>1|0|||||</main>', | 19 | '<main class="a b"><h1 style=background-color:red>hello world</h1><p>haha</p>1|0|||||</main>', |
| 20 | )); | 20 | )); |
| 21 | test("unescaped/escaped html", (t) => | ||
| 22 | t.assert.equal( | ||
| 23 | engine.ssrSync(<div>{engine.html("<fuck>")}{"\"&'`<>"}</div>).text, | ||
| 24 | "<div><fuck>&quot;&amp;&#x27;&#x60;&lt;&gt;</div>", | ||
| 25 | )); | ||
| 26 | test("clsx built-in", (t) => | ||
| 27 | t.assert.equal( | ||
| 28 | engine.ssrSync( | ||
| 29 | <> | ||
| 30 | <a class="a" /> | ||
| 31 | <b class={null} /> | ||
| 32 | <c class={undefined} /> | ||
| 33 | <d class={["a", "b", null]} /> | ||
| 34 | <e class={{ a: true, b: false }} /> | ||
| 35 | <e | ||
| 36 | class={[null, "x", { z: true }, [{ m: true }, null, { v: false }]]} | ||
| 37 | /> | ||
| 38 | </>, | ||
| 39 | ).text, | ||
| 40 | '<a class=a></a><b></b><c></c><d class="a b"></d><e class=a></e><e class="x z m"></e>', | ||
| 41 | )); |
framework/esbuild-support.ts+6| ... | @@ -64,6 +64,11 @@ export function projectRelativeResolution(root = process.cwd() + "/src") { | ... | @@ -64,6 +64,11 @@ export function projectRelativeResolution(root = process.cwd() + "/src") { |
| 64 | path: path.resolve(root, id.slice(2)), | 64 | path: path.resolve(root, id.slice(2)), |
| 65 | }; | 65 | }; |
| 66 | }); | 66 | }); |
| 67 | b.onResolve({ filter: /^#/ }, ({ path: id, importer }) => { | ||
| 68 | return { | ||
| 69 | path: hot.resolveFrom(importer, id), | ||
| 70 | }; | ||
| 71 | }); | ||
| 67 | }, | 72 | }, |
| 68 | } satisfies esbuild.Plugin; | 73 | } satisfies esbuild.Plugin; |
| 69 | } | 74 | } |
| ... | @@ -71,3 +76,4 @@ export function projectRelativeResolution(root = process.cwd() + "/src") { | ... | @@ -71,3 +76,4 @@ export function projectRelativeResolution(root = process.cwd() + "/src") { |
| 71 | import * as esbuild from "esbuild"; | 76 | import * as esbuild from "esbuild"; |
| 72 | import * as string from "#sitegen/string"; | 77 | import * as string from "#sitegen/string"; |
| 73 | import * as path from "node:path"; | 78 | import * as path from "node:path"; |
| 79 | import * as hot from "./hot.ts"; |
framework/generate.ts+37-21| ... | @@ -74,26 +74,40 @@ export async function sitegen( | ... | @@ -74,26 +74,40 @@ export async function sitegen( |
| 74 | dir: sectionPath("pages"), | 74 | dir: sectionPath("pages"), |
| 75 | list: pages, | 75 | list: pages, |
| 76 | prefix: "/", | 76 | prefix: "/", |
| 77 | exclude: [".css", ".client.ts", ".client.tsx"], | 77 | include: [".tsx", ".mdx", ".marko"], |
| 78 | exclude: [".client.ts", ".client.tsx"], | ||
| 79 | }, | ||
| 80 | { | ||
| 81 | dir: sectionPath("static"), | ||
| 82 | list: staticFiles, | ||
| 83 | prefix: "/", | ||
| 84 | ext: true, | ||
| 85 | }, | ||
| 86 | { | ||
| 87 | dir: sectionPath("scripts"), | ||
| 88 | list: scripts, | ||
| 89 | prefix: rootPrefix, | ||
| 90 | include: [".client.ts", ".client.tsx"], | ||
| 78 | }, | 91 | }, |
| 79 | { dir: sectionPath("static"), list: staticFiles, prefix: "/", ext: true }, | ||
| 80 | { dir: sectionPath("scripts"), list: scripts, prefix: rootPrefix }, | ||
| 81 | { | 92 | { |
| 82 | dir: sectionPath("views"), | 93 | dir: sectionPath("views"), |
| 83 | list: views, | 94 | list: views, |
| 84 | prefix: rootPrefix, | 95 | prefix: rootPrefix, |
| 85 | exclude: [".css", ".client.ts", ".client.tsx"], | 96 | include: [".tsx", ".mdx", ".marko"], |
| 97 | exclude: [".client.ts", ".client.tsx"], | ||
| 86 | }, | 98 | }, |
| 87 | ]; | 99 | ]; |
| 88 | for (const { dir, list, prefix, exclude = [], ext = false } of kinds) { | 100 | for ( |
| 101 | const { dir, list, prefix, include = [""], exclude = [], ext = false } | ||
| 102 | of kinds | ||
| 103 | ) { | ||
| 89 | const items = fs.readDirRecOptionalSync(dir); | 104 | const items = fs.readDirRecOptionalSync(dir); |
| 90 | item: for (const subPath of items) { | 105 | for (const subPath of items) { |
| 91 | const file = path.join(dir, subPath); | 106 | const file = path.join(dir, subPath); |
| 92 | const stat = fs.statSync(file); | 107 | const stat = fs.statSync(file); |
| 93 | if (stat.isDirectory()) continue; | 108 | if (stat.isDirectory()) continue; |
| 94 | for (const e of exclude) { | 109 | if (!include.some((e) => subPath.endsWith(e))) continue; |
| 95 | if (subPath.endsWith(e)) continue item; | 110 | if (exclude.some((e) => subPath.endsWith(e))) continue; |
| 96 | } | ||
| 97 | const trim = ext | 111 | const trim = ext |
| 98 | ? subPath | 112 | ? subPath |
| 99 | : subPath.slice(0, -path.extname(subPath).length).replaceAll( | 113 | : subPath.slice(0, -path.extname(subPath).length).replaceAll( |
| ... | @@ -216,31 +230,33 @@ export async function sitegen( | ... | @@ -216,31 +230,33 @@ export async function sitegen( |
| 216 | }, | 230 | }, |
| 217 | }); | 231 | }); |
| 218 | } | 232 | } |
| 219 | async function prepareView(view: FileItem) { | 233 | async function prepareView(item: FileItem) { |
| 220 | const module = require(view.file); | 234 | const module = require(item.file); |
| 221 | if (!module.meta) { | 235 | if (!module.meta) { |
| 222 | throw new Error(`${view.file} is missing 'export const meta'`); | 236 | throw new Error(`${item.file} is missing 'export const meta'`); |
| 223 | } | 237 | } |
| 224 | if (!module.default) { | 238 | if (!module.default) { |
| 225 | throw new Error(`${view.file} is missing a default export.`); | 239 | throw new Error(`${item.file} is missing a default export.`); |
| 226 | } | 240 | } |
| 227 | const pageTheme = module.layout?.theme ?? module.theme; | 241 | const pageTheme = module.layout?.theme ?? module.theme; |
| 228 | const theme: css.Theme = { | 242 | const theme: css.Theme = { |
| 229 | ...css.defaultTheme, | 243 | ...css.defaultTheme, |
| 230 | ...pageTheme, | 244 | ...pageTheme, |
| 231 | }; | 245 | }; |
| 232 | const cssImports = hot.getCssImports(view.file) | 246 | const cssImports = Array.from( |
| 233 | .concat("src/global.css") | 247 | new Set([globalCssPath, ...hot.getCssImports(item.file)]), |
| 234 | .map((file) => path.relative(hot.projectSrc, path.resolve(file))); | 248 | (file) => path.relative(hot.projectSrc, file), |
| 249 | ); | ||
| 250 | ensureCssGetsBuilt(cssImports, theme, item.id); | ||
| 235 | incr.put({ | 251 | incr.put({ |
| 236 | kind: "viewMetadata", | 252 | kind: "viewMetadata", |
| 237 | key: view.id, | 253 | key: item.id, |
| 238 | sources: [view.file], | 254 | sources: [item.file], |
| 239 | value: { | 255 | value: { |
| 240 | file: path.relative(hot.projectRoot, view.file), | 256 | file: path.relative(hot.projectRoot, item.file), |
| 241 | cssImports, | 257 | cssImports, |
| 242 | theme, | 258 | theme, |
| 243 | clientRefs: hot.getClientScriptRefs(view.file), | 259 | clientRefs: hot.getClientScriptRefs(item.file), |
| 244 | hasLayout: !!module.layout?.default, | 260 | hasLayout: !!module.layout?.default, |
| 245 | }, | 261 | }, |
| 246 | }); | 262 | }); |
| ... | @@ -422,7 +438,7 @@ function getItemText({ file }: FileItem) { | ... | @@ -422,7 +438,7 @@ function getItemText({ file }: FileItem) { |
| 422 | return path.relative(hot.projectSrc, file).replaceAll("\\", "/"); | 438 | return path.relative(hot.projectSrc, file).replaceAll("\\", "/"); |
| 423 | } | 439 | } |
| 424 | 440 | ||
| 425 | import { OnceMap, Queue } from "./queue.ts"; | 441 | import { OnceMap, Queue } from "#sitegen/async"; |
| 426 | import { Incremental } from "./incremental.ts"; | 442 | import { Incremental } from "./incremental.ts"; |
| 427 | import * as bundle from "./bundle.ts"; | 443 | import * as bundle from "./bundle.ts"; |
| 428 | import * as css from "./css.ts"; | 444 | import * as css from "./css.ts"; |
framework/incremental.ts+2-2| ... | @@ -148,7 +148,7 @@ export class Incremental { | ... | @@ -148,7 +148,7 @@ export class Incremental { |
| 148 | for (const key of map.keys()) { | 148 | for (const key of map.keys()) { |
| 149 | if (!this.round.referenced.has(`${kind}\0${key}`)) { | 149 | if (!this.round.referenced.has(`${kind}\0${key}`)) { |
| 150 | unreferenced.push({ kind: kind as ArtifactKind, key }); | 150 | unreferenced.push({ kind: kind as ArtifactKind, key }); |
| 151 | console.warn("unreferened " + kind + " : " + key); | 151 | this.out[kind as ArtifactKind].delete(key); |
| 152 | } | 152 | } |
| 153 | } | 153 | } |
| 154 | } | 154 | } |
| ... | @@ -652,7 +652,7 @@ const zstd = util.promisify(zlib.zstdCompress); | ... | @@ -652,7 +652,7 @@ const zstd = util.promisify(zlib.zstdCompress); |
| 652 | import * as fs from "#sitegen/fs"; | 652 | import * as fs from "#sitegen/fs"; |
| 653 | import * as zlib from "node:zlib"; | 653 | import * as zlib from "node:zlib"; |
| 654 | import * as util from "node:util"; | 654 | import * as util from "node:util"; |
| 655 | import { Queue } from "./queue.ts"; | 655 | import { Queue } from "#sitegen/async"; |
| 656 | import * as hot from "./hot.ts"; | 656 | import * as hot from "./hot.ts"; |
| 657 | import * as mime from "#sitegen/mime"; | 657 | import * as mime from "#sitegen/mime"; |
| 658 | import * as path from "node:path"; | 658 | import * as path from "node:path"; |
framework/lib/async.ts created+279| ... | @@ -0,0 +1,279 @@ | ||
| 1 | const five_minutes = 5 * 60 * 1000; | ||
| 2 | |||
| 3 | interface QueueOptions<T, R> { | ||
| 4 | name: string; | ||
| 5 | fn: (item: T, spin: Spinner) => Promise<R>; | ||
| 6 | getItemText?: (item: T) => string; | ||
| 7 | maxJobs?: number; | ||
| 8 | passive?: boolean; | ||
| 9 | } | ||
| 10 | |||
| 11 | // Process multiple items in parallel, queue up as many. | ||
| 12 | export class Queue<T, R> { | ||
| 13 | #name: string; | ||
| 14 | #fn: (item: T, spin: Spinner) => Promise<R>; | ||
| 15 | #maxJobs: number; | ||
| 16 | #getItemText: (item: T) => string; | ||
| 17 | #passive: boolean; | ||
| 18 | |||
| 19 | #active: Spinner[] = []; | ||
| 20 | #queue: Array<[T] | [T, (result: R) => void, (err: unknown) => void]> = []; | ||
| 21 | |||
| 22 | #cachedProgress: Progress<{ active: Spinner[] }> | null = null; | ||
| 23 | #done: number = 0; | ||
| 24 | #total: number = 0; | ||
| 25 | #onComplete: (() => void) | null = null; | ||
| 26 | #estimate: number | null = null; | ||
| 27 | #errors: unknown[] = []; | ||
| 28 | |||
| 29 | constructor(options: QueueOptions<T, R>) { | ||
| 30 | this.#name = options.name; | ||
| 31 | this.#fn = options.fn; | ||
| 32 | this.#maxJobs = options.maxJobs ?? 5; | ||
| 33 | this.#getItemText = options.getItemText ?? defaultGetItemText; | ||
| 34 | this.#passive = options.passive ?? false; | ||
| 35 | } | ||
| 36 | |||
| 37 | get bar() { | ||
| 38 | const cached = this.#cachedProgress; | ||
| 39 | if (!cached) { | ||
| 40 | const bar = this.#cachedProgress = new Progress({ | ||
| 41 | spinner: null, | ||
| 42 | text: ({ active }) => { | ||
| 43 | const now = performance.now(); | ||
| 44 | let text = `[${this.#done}/${this.#total}] ${this.#name}`; | ||
| 45 | let n = 0; | ||
| 46 | for (const item of active) { | ||
| 47 | let itemText = "- " + item.format(now); | ||
| 48 | text += `\n` + | ||
| 49 | itemText.slice(0, Math.max(0, process.stdout.columns - 1)); | ||
| 50 | if (n > 10) { | ||
| 51 | text += `\n ... + ${active.length - n} more`; | ||
| 52 | break; | ||
| 53 | } | ||
| 54 | n++; | ||
| 55 | } | ||
| 56 | return text; | ||
| 57 | }, | ||
| 58 | props: { | ||
| 59 | active: [] as Spinner[], | ||
| 60 | }, | ||
| 61 | }); | ||
| 62 | bar.value = 0; | ||
| 63 | return bar; | ||
| 64 | } | ||
| 65 | return cached; | ||
| 66 | } | ||
| 67 | |||
| 68 | add(args: T) { | ||
| 69 | this.#total += 1; | ||
| 70 | this.updateTotal(); | ||
| 71 | if (this.#active.length > this.#maxJobs) { | ||
| 72 | const { promise, resolve, reject } = Promise.withResolvers<R>(); | ||
| 73 | this.#queue.push([args, resolve, reject]); | ||
| 74 | return promise; | ||
| 75 | } | ||
| 76 | return this.#run(args); | ||
| 77 | } | ||
| 78 | |||
| 79 | addMany(items: T[]) { | ||
| 80 | this.#total += items.length; | ||
| 81 | this.updateTotal(); | ||
| 82 | |||
| 83 | const runNowCount = this.#maxJobs - this.#active.length; | ||
| 84 | const runNow = items.slice(0, runNowCount); | ||
| 85 | const runLater = items.slice(runNowCount); | ||
| 86 | this.#queue.push(...runLater.reverse().map<[T]>((x) => [x])); | ||
| 87 | runNow.map((item) => this.#run(item).catch(() => {})); | ||
| 88 | } | ||
| 89 | |||
| 90 | async #run(args: T): Promise<R> { | ||
| 91 | const bar = this.bar; | ||
| 92 | const itemText = this.#getItemText(args); | ||
| 93 | const spinner = new Spinner(itemText); | ||
| 94 | spinner.stop(); | ||
| 95 | const active = this.#active; | ||
| 96 | try { | ||
| 97 | active.unshift(spinner); | ||
| 98 | bar.props = { active }; | ||
| 99 | const result = await this.#fn(args, spinner); | ||
| 100 | this.#done++; | ||
| 101 | return result; | ||
| 102 | } catch (err) { | ||
| 103 | if (err && typeof err === "object") { | ||
| 104 | (err as any).job = itemText; | ||
| 105 | } | ||
| 106 | this.#errors.push(err); | ||
| 107 | throw err; | ||
| 108 | } finally { | ||
| 109 | active.splice(active.indexOf(spinner), 1); | ||
| 110 | bar.props = { active }; | ||
| 111 | bar.value = this.#done; | ||
| 112 | |||
| 113 | // Process next item | ||
| 114 | const next = this.#queue.shift(); | ||
| 115 | if (next) { | ||
| 116 | const args = next[0]; | ||
| 117 | this.#run(args) | ||
| 118 | .then((result) => next[1]?.(result)) | ||
| 119 | .catch((err) => next[2]?.(err)); | ||
| 120 | } else if (this.#active.length === 0) { | ||
| 121 | if (this.#passive) { | ||
| 122 | this.bar.stop(); | ||
| 123 | this.#cachedProgress = null; | ||
| 124 | } | ||
| 125 | this.#onComplete?.(); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | updateTotal() { | ||
| 131 | const bar = this.bar; | ||
| 132 | bar.total = Math.max(this.#total, this.#estimate ?? 0); | ||
| 133 | } | ||
| 134 | |||
| 135 | set estimate(e: number) { | ||
| 136 | this.#estimate = e; | ||
| 137 | if (this.#cachedProgress) { | ||
| 138 | this.updateTotal(); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | async done(o: { method: "success" | "stop" }) { | ||
| 143 | if (this.#active.length === 0) { | ||
| 144 | this.#end(o); | ||
| 145 | return; | ||
| 146 | } | ||
| 147 | |||
| 148 | const { promise, resolve } = Promise.withResolvers<void>(); | ||
| 149 | this.#onComplete = resolve; | ||
| 150 | await promise; | ||
| 151 | this.#end(o); | ||
| 152 | } | ||
| 153 | |||
| 154 | #end( | ||
| 155 | { method = this.#passive ? "stop" : "success" }: { | ||
| 156 | method: "success" | "stop"; | ||
| 157 | }, | ||
| 158 | ) { | ||
| 159 | const bar = this.#cachedProgress; | ||
| 160 | if (this.#errors.length > 0) { | ||
| 161 | if (bar) bar.stop(); | ||
| 162 | throw new AggregateError( | ||
| 163 | this.#errors, | ||
| 164 | this.#errors.length + " jobs failed in '" + this.#name + "'", | ||
| 165 | ); | ||
| 166 | } | ||
| 167 | |||
| 168 | if (bar) bar[method](); | ||
| 169 | } | ||
| 170 | |||
| 171 | get active(): boolean { | ||
| 172 | return this.#active.length !== 0; | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | const cwd = process.cwd(); | ||
| 177 | function defaultGetItemText(item: unknown) { | ||
| 178 | let itemText = ""; | ||
| 179 | if (typeof item === "string") { | ||
| 180 | itemText = item; | ||
| 181 | } else if (typeof item === "object" && item !== null) { | ||
| 182 | const { path, label, id } = item as any; | ||
| 183 | itemText = label ?? path ?? id ?? JSON.stringify(item); | ||
| 184 | } else { | ||
| 185 | itemText = JSON.stringify(item); | ||
| 186 | } | ||
| 187 | |||
| 188 | if (itemText.startsWith(cwd)) { | ||
| 189 | itemText = path.relative(cwd, itemText); | ||
| 190 | } | ||
| 191 | return itemText; | ||
| 192 | } | ||
| 193 | |||
| 194 | export class OnceMap<T> { | ||
| 195 | private ongoing = new Map<string, Promise<T>>(); | ||
| 196 | |||
| 197 | get(key: string, compute: () => Promise<T>) { | ||
| 198 | if (this.ongoing.has(key)) { | ||
| 199 | return this.ongoing.get(key)!; | ||
| 200 | } | ||
| 201 | |||
| 202 | const result = compute(); | ||
| 203 | this.ongoing.set(key, result); | ||
| 204 | return result; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | interface ARCEValue<T> { | ||
| 209 | value: T; | ||
| 210 | [Symbol.dispose]: () => void; | ||
| 211 | } | ||
| 212 | |||
| 213 | export function RefCountedExpirable<T>( | ||
| 214 | init: () => Promise<T>, | ||
| 215 | deinit: (value: T) => void, | ||
| 216 | expire: number = five_minutes, | ||
| 217 | ): () => Promise<ARCEValue<T>> { | ||
| 218 | let refs = 0; | ||
| 219 | let item: ARCEValue<T> | null = null; | ||
| 220 | let loading: Promise<ARCEValue<T>> | null = null; | ||
| 221 | let timer: ReturnType<typeof setTimeout> | null = null; | ||
| 222 | |||
| 223 | function deref() { | ||
| 224 | ASSERT(item !== null); | ||
| 225 | if (--refs !== 0) return; | ||
| 226 | ASSERT(timer === null); | ||
| 227 | timer = setTimeout(() => { | ||
| 228 | ASSERT(refs === 0); | ||
| 229 | ASSERT(loading === null); | ||
| 230 | ASSERT(item !== null); | ||
| 231 | deinit(item.value); | ||
| 232 | item = null; | ||
| 233 | timer = null; | ||
| 234 | }, expire); | ||
| 235 | } | ||
| 236 | |||
| 237 | return async function () { | ||
| 238 | if (timer !== null) { | ||
| 239 | clearTimeout(timer); | ||
| 240 | timer = null; | ||
| 241 | } | ||
| 242 | if (item !== null) { | ||
| 243 | refs++; | ||
| 244 | return item; | ||
| 245 | } | ||
| 246 | if (loading !== null) { | ||
| 247 | refs++; | ||
| 248 | return loading; | ||
| 249 | } | ||
| 250 | const p = Promise.withResolvers<ARCEValue<T>>(); | ||
| 251 | loading = p.promise; | ||
| 252 | try { | ||
| 253 | const value = await init(); | ||
| 254 | item = { value, [Symbol.dispose]: deref }; | ||
| 255 | refs++; | ||
| 256 | p.resolve(item); | ||
| 257 | return item; | ||
| 258 | } catch (e) { | ||
| 259 | p.reject(e); | ||
| 260 | throw e; | ||
| 261 | } finally { | ||
| 262 | loading = null; | ||
| 263 | } | ||
| 264 | }; | ||
| 265 | } | ||
| 266 | |||
| 267 | export function once<T>(fn: () => Promise<T>): () => Promise<T> { | ||
| 268 | let result: T | Promise<T> | null = null; | ||
| 269 | return async () => { | ||
| 270 | if (result) return result; | ||
| 271 | result = await fn(); | ||
| 272 | return result; | ||
| 273 | }; | ||
| 274 | } | ||
| 275 | |||
| 276 | import { Progress } from "@paperclover/console/Progress"; | ||
| 277 | import { Spinner } from "@paperclover/console/Spinner"; | ||
| 278 | import * as path from "node:path"; | ||
| 279 | import process from "node:process"; | ||
framework/lib/view.ts+16-7| ... | @@ -16,7 +16,14 @@ let scripts: Record<string, string> = null!; | ... | @@ -16,7 +16,14 @@ let scripts: Record<string, string> = null!; |
| 16 | // boundaries, but those were never used. Pages will wait until they | 16 | // boundaries, but those were never used. Pages will wait until they |
| 17 | // are fully rendered before sending. | 17 | // are fully rendered before sending. |
| 18 | export async function renderView( | 18 | export async function renderView( |
| 19 | c: hono.Context, | 19 | context: hono.Context, |
| 20 | id: string, | ||
| 21 | props: Record<string, unknown>, | ||
| 22 | ) { | ||
| 23 | return context.html(await renderViewToString(id, { context, ...props })); | ||
| 24 | } | ||
| 25 | |||
| 26 | export async function renderViewToString( | ||
| 20 | id: string, | 27 | id: string, |
| 21 | props: Record<string, unknown>, | 28 | props: Record<string, unknown>, |
| 22 | ) { | 29 | ) { |
| ... | @@ -29,11 +36,11 @@ export async function renderView( | ... | @@ -29,11 +36,11 @@ export async function renderView( |
| 29 | inlineCss, | 36 | inlineCss, |
| 30 | layout, | 37 | layout, |
| 31 | meta: metadata, | 38 | meta: metadata, |
| 32 | }: View = views[id]; | 39 | }: View = UNWRAP(views[id], `Missing view ${id}`); |
| 33 | 40 | ||
| 34 | // -- metadata -- | 41 | // -- metadata -- |
| 35 | const renderedMetaPromise = Promise.resolve( | 42 | const renderedMetaPromise = Promise.resolve( |
| 36 | typeof metadata === "function" ? metadata({ context: c }) : metadata, | 43 | typeof metadata === "function" ? metadata(props) : metadata, |
| 37 | ).then((m) => meta.renderMeta(m)); | 44 | ).then((m) => meta.renderMeta(m)); |
| 38 | 45 | ||
| 39 | // -- html -- | 46 | // -- html -- |
| ... | @@ -44,14 +51,17 @@ export async function renderView( | ... | @@ -44,14 +51,17 @@ export async function renderView( |
| 44 | }); | 51 | }); |
| 45 | 52 | ||
| 46 | // -- join document and send -- | 53 | // -- join document and send -- |
| 47 | return c.html(wrapDocument({ | 54 | return wrapDocument({ |
| 48 | body, | 55 | body, |
| 49 | head: await renderedMetaPromise, | 56 | head: await renderedMetaPromise, |
| 50 | inlineCss, | 57 | inlineCss, |
| 51 | scripts: joinScripts( | 58 | scripts: joinScripts( |
| 52 | Array.from(sitegen.scripts, (script) => scripts[script]), | 59 | Array.from( |
| 60 | sitegen.scripts, | ||
| 61 | (id) => UNWRAP(scripts[id], `Missing script ${id}`), | ||
| 62 | ), | ||
| 53 | ), | 63 | ), |
| 54 | })); | 64 | }); |
| 55 | } | 65 | } |
| 56 | 66 | ||
| 57 | export function provideViewData(v: typeof views, s: typeof scripts) { | 67 | export function provideViewData(v: typeof views, s: typeof scripts) { |
| ... | @@ -87,5 +97,4 @@ export function wrapDocument({ | ... | @@ -87,5 +97,4 @@ export function wrapDocument({ |
| 87 | import * as meta from "./meta.ts"; | 97 | import * as meta from "./meta.ts"; |
| 88 | import type * as hono from "#hono"; | 98 | import type * as hono from "#hono"; |
| 89 | import * as engine from "../engine/ssr.ts"; | 99 | import * as engine from "../engine/ssr.ts"; |
| 90 | import type * as css from "../css.ts"; | ||
| 91 | import * as sg from "./sitegen.ts"; | 100 | import * as sg from "./sitegen.ts"; |
framework/queue.ts deleted-210| ... | @@ -1,210 +0,0 @@ | ||
| 1 | interface QueueOptions<T, R> { | ||
| 2 | name: string; | ||
| 3 | fn: (item: T, spin: Spinner) => Promise<R>; | ||
| 4 | getItemText?: (item: T) => string; | ||
| 5 | maxJobs?: number; | ||
| 6 | passive?: boolean; | ||
| 7 | } | ||
| 8 | |||
| 9 | // Process multiple items in parallel, queue up as many. | ||
| 10 | export class Queue<T, R> { | ||
| 11 | #name: string; | ||
| 12 | #fn: (item: T, spin: Spinner) => Promise<R>; | ||
| 13 | #maxJobs: number; | ||
| 14 | #getItemText: (item: T) => string; | ||
| 15 | #passive: boolean; | ||
| 16 | |||
| 17 | #active: Spinner[] = []; | ||
| 18 | #queue: Array<[T] | [T, (result: R) => void, (err: unknown) => void]> = []; | ||
| 19 | |||
| 20 | #cachedProgress: Progress<{ active: Spinner[] }> | null = null; | ||
| 21 | #done: number = 0; | ||
| 22 | #total: number = 0; | ||
| 23 | #onComplete: (() => void) | null = null; | ||
| 24 | #estimate: number | null = null; | ||
| 25 | #errors: unknown[] = []; | ||
| 26 | |||
| 27 | constructor(options: QueueOptions<T, R>) { | ||
| 28 | this.#name = options.name; | ||
| 29 | this.#fn = options.fn; | ||
| 30 | this.#maxJobs = options.maxJobs ?? 5; | ||
| 31 | this.#getItemText = options.getItemText ?? defaultGetItemText; | ||
| 32 | this.#passive = options.passive ?? false; | ||
| 33 | } | ||
| 34 | |||
| 35 | get bar() { | ||
| 36 | const cached = this.#cachedProgress; | ||
| 37 | if (!cached) { | ||
| 38 | const bar = this.#cachedProgress = new Progress({ | ||
| 39 | spinner: null, | ||
| 40 | text: ({ active }) => { | ||
| 41 | const now = performance.now(); | ||
| 42 | let text = `[${this.#done}/${this.#total}] ${this.#name}`; | ||
| 43 | let n = 0; | ||
| 44 | for (const item of active) { | ||
| 45 | let itemText = "- " + item.format(now); | ||
| 46 | text += `\n` + | ||
| 47 | itemText.slice(0, Math.max(0, process.stdout.columns - 1)); | ||
| 48 | if (n > 10) { | ||
| 49 | text += `\n ... + ${active.length - n} more`; | ||
| 50 | break; | ||
| 51 | } | ||
| 52 | n++; | ||
| 53 | } | ||
| 54 | return text; | ||
| 55 | }, | ||
| 56 | props: { | ||
| 57 | active: [] as Spinner[], | ||
| 58 | }, | ||
| 59 | }); | ||
| 60 | bar.value = 0; | ||
| 61 | return bar; | ||
| 62 | } | ||
| 63 | return cached; | ||
| 64 | } | ||
| 65 | |||
| 66 | add(args: T) { | ||
| 67 | this.#total += 1; | ||
| 68 | this.updateTotal(); | ||
| 69 | if (this.#active.length > this.#maxJobs) { | ||
| 70 | const { promise, resolve, reject } = Promise.withResolvers<R>(); | ||
| 71 | this.#queue.push([args, resolve, reject]); | ||
| 72 | return promise; | ||
| 73 | } | ||
| 74 | return this.#run(args); | ||
| 75 | } | ||
| 76 | |||
| 77 | addMany(items: T[]) { | ||
| 78 | this.#total += items.length; | ||
| 79 | this.updateTotal(); | ||
| 80 | |||
| 81 | const runNowCount = this.#maxJobs - this.#active.length; | ||
| 82 | const runNow = items.slice(0, runNowCount); | ||
| 83 | const runLater = items.slice(runNowCount); | ||
| 84 | this.#queue.push(...runLater.reverse().map<[T]>((x) => [x])); | ||
| 85 | runNow.map((item) => this.#run(item).catch(() => {})); | ||
| 86 | } | ||
| 87 | |||
| 88 | async #run(args: T): Promise<R> { | ||
| 89 | const bar = this.bar; | ||
| 90 | const itemText = this.#getItemText(args); | ||
| 91 | const spinner = new Spinner(itemText); | ||
| 92 | spinner.stop(); | ||
| 93 | const active = this.#active; | ||
| 94 | try { | ||
| 95 | active.unshift(spinner); | ||
| 96 | bar.props = { active }; | ||
| 97 | const result = await this.#fn(args, spinner); | ||
| 98 | this.#done++; | ||
| 99 | return result; | ||
| 100 | } catch (err) { | ||
| 101 | if (err && typeof err === "object") { | ||
| 102 | (err as any).job = itemText; | ||
| 103 | } | ||
| 104 | this.#errors.push(err); | ||
| 105 | throw err; | ||
| 106 | } finally { | ||
| 107 | active.splice(active.indexOf(spinner), 1); | ||
| 108 | bar.props = { active }; | ||
| 109 | bar.value = this.#done; | ||
| 110 | |||
| 111 | // Process next item | ||
| 112 | const next = this.#queue.shift(); | ||
| 113 | if (next) { | ||
| 114 | const args = next[0]; | ||
| 115 | this.#run(args) | ||
| 116 | .then((result) => next[1]?.(result)) | ||
| 117 | .catch((err) => next[2]?.(err)); | ||
| 118 | } else if (this.#active.length === 0) { | ||
| 119 | if (this.#passive) { | ||
| 120 | this.bar.stop(); | ||
| 121 | this.#cachedProgress = null; | ||
| 122 | } | ||
| 123 | this.#onComplete?.(); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | updateTotal() { | ||
| 129 | const bar = this.bar; | ||
| 130 | bar.total = Math.max(this.#total, this.#estimate ?? 0); | ||
| 131 | } | ||
| 132 | |||
| 133 | set estimate(e: number) { | ||
| 134 | this.#estimate = e; | ||
| 135 | if (this.#cachedProgress) { | ||
| 136 | this.updateTotal(); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | async done(o: { method: "success" | "stop" }) { | ||
| 141 | if (this.#active.length === 0) { | ||
| 142 | this.#end(o); | ||
| 143 | return; | ||
| 144 | } | ||
| 145 | |||
| 146 | const { promise, resolve } = Promise.withResolvers<void>(); | ||
| 147 | this.#onComplete = resolve; | ||
| 148 | await promise; | ||
| 149 | this.#end(o); | ||
| 150 | } | ||
| 151 | |||
| 152 | #end( | ||
| 153 | { method = this.#passive ? "stop" : "success" }: { | ||
| 154 | method: "success" | "stop"; | ||
| 155 | }, | ||
| 156 | ) { | ||
| 157 | const bar = this.#cachedProgress; | ||
| 158 | if (this.#errors.length > 0) { | ||
| 159 | if (bar) bar.stop(); | ||
| 160 | throw new AggregateError( | ||
| 161 | this.#errors, | ||
| 162 | this.#errors.length + " jobs failed in '" + this.#name + "'", | ||
| 163 | ); | ||
| 164 | } | ||
| 165 | |||
| 166 | if (bar) bar[method](); | ||
| 167 | } | ||
| 168 | |||
| 169 | get active(): boolean { | ||
| 170 | return this.#active.length !== 0; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | const cwd = process.cwd(); | ||
| 175 | function defaultGetItemText(item: unknown) { | ||
| 176 | let itemText = ""; | ||
| 177 | if (typeof item === "string") { | ||
| 178 | itemText = item; | ||
| 179 | } else if (typeof item === "object" && item !== null) { | ||
| 180 | const { path, label, id } = item as any; | ||
| 181 | itemText = label ?? path ?? id ?? JSON.stringify(item); | ||
| 182 | } else { | ||
| 183 | itemText = JSON.stringify(item); | ||
| 184 | } | ||
| 185 | |||
| 186 | if (itemText.startsWith(cwd)) { | ||
| 187 | itemText = path.relative(cwd, itemText); | ||
| 188 | } | ||
| 189 | return itemText; | ||
| 190 | } | ||
| 191 | |||
| 192 | export class OnceMap<T> { | ||
| 193 | private ongoing = new Map<string, Promise<T>>(); | ||
| 194 | |||
| 195 | get(key: string, compute: () => Promise<T>) { | ||
| 196 | if (this.ongoing.has(key)) { | ||
| 197 | return this.ongoing.get(key)!; | ||
| 198 | } | ||
| 199 | |||
| 200 | const result = compute(); | ||
| 201 | this.ongoing.set(key, result); | ||
| 202 | result.finally(() => this.ongoing.delete(key)); | ||
| 203 | return result; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | import { Progress } from "@paperclover/console/Progress"; | ||
| 208 | import { Spinner } from "@paperclover/console/Spinner"; | ||
| 209 | import * as path from "node:path"; | ||
| 210 | import process from "node:process"; | ||
framework/test-fixture/Component.marko created+4| ... | @@ -0,0 +1,4 @@ | ||
| 1 | <div meow=null /> | ||
| 2 | <div> | ||
| 3 | wait(${null}) | ||
| 4 | </div> | ||
framework/test-fixture/UseComponent.marko created+6| ... | @@ -0,0 +1,6 @@ | ||
| 1 | import Component from './Component.marko'; | ||
| 2 | |||
| 3 | <h1>web page</h1> | ||
| 4 | <if=!false> | ||
| 5 | <Component=null/> | ||
| 6 | </> | ||
framework/watch.ts+8-2| ... | @@ -11,7 +11,7 @@ export async function main() { | ... | @@ -11,7 +11,7 @@ export async function main() { |
| 11 | const watch = new Watch(rebuild); | 11 | const watch = new Watch(rebuild); |
| 12 | watch.add(...incr.invals.keys()); | 12 | watch.add(...incr.invals.keys()); |
| 13 | statusLine(); | 13 | statusLine(); |
| 14 | // ... an | 14 | // ... and then serve it! |
| 15 | serve(); | 15 | serve(); |
| 16 | 16 | ||
| 17 | function serve() { | 17 | function serve() { |
| ... | @@ -41,7 +41,13 @@ export async function main() { | ... | @@ -41,7 +41,13 @@ export async function main() { |
| 41 | files = files.map((file) => path.relative(hot.projectRoot, file)); | 41 | files = files.map((file) => path.relative(hot.projectRoot, file)); |
| 42 | const changed: string[] = []; | 42 | const changed: string[] = []; |
| 43 | for (const file of files) { | 43 | for (const file of files) { |
| 44 | if (incr.updateStat(file, fs.statSync(file).mtimeMs)) changed.push(file); | 44 | let mtimeMs: number | null = null; |
| 45 | try { | ||
| 46 | mtimeMs = fs.statSync(file).mtimeMs; | ||
| 47 | } catch (err: any) { | ||
| 48 | if (err?.code !== "ENOENT") throw err; | ||
| 49 | } | ||
| 50 | if (incr.updateStat(file, mtimeMs)) changed.push(file); | ||
| 45 | } | 51 | } |
| 46 | if (changed.length === 0) { | 52 | if (changed.length === 0) { |
| 47 | console.warn("Files were modified but the 'modify' time did not change."); | 53 | console.warn("Files were modified but the 'modify' time did not change."); |
package-lock.json+1055-10| ... | @@ -12,6 +12,7 @@ | ... | @@ -12,6 +12,7 @@ |
| 12 | "hls.js": "^1.6.5", | 12 | "hls.js": "^1.6.5", |
| 13 | "hono": "^4.7.11", | 13 | "hono": "^4.7.11", |
| 14 | "marko": "^6.0.20", | 14 | "marko": "^6.0.20", |
| 15 | "puppeteer": "^24.10.1", | ||
| 15 | "unique-names-generator": "^4.7.1" | 16 | "unique-names-generator": "^4.7.1" |
| 16 | }, | 17 | }, |
| 17 | "devDependencies": { | 18 | "devDependencies": { |
| ... | @@ -950,6 +951,45 @@ | ... | @@ -950,6 +951,45 @@ |
| 950 | "strip-ansi": "^7.1.0" | 951 | "strip-ansi": "^7.1.0" |
| 951 | } | 952 | } |
| 952 | }, | 953 | }, |
| 954 | "node_modules/@puppeteer/browsers": { | ||
| 955 | "version": "2.10.5", | ||
| 956 | "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", | ||
| 957 | "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", | ||
| 958 | "license": "Apache-2.0", | ||
| 959 | "dependencies": { | ||
| 960 | "debug": "^4.4.1", | ||
| 961 | "extract-zip": "^2.0.1", | ||
| 962 | "progress": "^2.0.3", | ||
| 963 | "proxy-agent": "^6.5.0", | ||
| 964 | "semver": "^7.7.2", | ||
| 965 | "tar-fs": "^3.0.8", | ||
| 966 | "yargs": "^17.7.2" | ||
| 967 | }, | ||
| 968 | "bin": { | ||
| 969 | "browsers": "lib/cjs/main-cli.js" | ||
| 970 | }, | ||
| 971 | "engines": { | ||
| 972 | "node": ">=18" | ||
| 973 | } | ||
| 974 | }, | ||
| 975 | "node_modules/@puppeteer/browsers/node_modules/semver": { | ||
| 976 | "version": "7.7.2", | ||
| 977 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", | ||
| 978 | "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", | ||
| 979 | "license": "ISC", | ||
| 980 | "bin": { | ||
| 981 | "semver": "bin/semver.js" | ||
| 982 | }, | ||
| 983 | "engines": { | ||
| 984 | "node": ">=10" | ||
| 985 | } | ||
| 986 | }, | ||
| 987 | "node_modules/@tootallnate/quickjs-emscripten": { | ||
| 988 | "version": "0.23.0", | ||
| 989 | "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", | ||
| 990 | "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", | ||
| 991 | "license": "MIT" | ||
| 992 | }, | ||
| 953 | "node_modules/@types/debug": { | 993 | "node_modules/@types/debug": { |
| 954 | "version": "4.1.12", | 994 | "version": "4.1.12", |
| 955 | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", | 995 | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", |
| ... | @@ -1008,7 +1048,7 @@ | ... | @@ -1008,7 +1048,7 @@ |
| 1008 | "version": "22.15.30", | 1048 | "version": "22.15.30", |
| 1009 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", | 1049 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", |
| 1010 | "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", | 1050 | "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", |
| 1011 | "dev": true, | 1051 | "devOptional": true, |
| 1012 | "license": "MIT", | 1052 | "license": "MIT", |
| 1013 | "dependencies": { | 1053 | "dependencies": { |
| 1014 | "undici-types": "~6.21.0" | 1054 | "undici-types": "~6.21.0" |
| ... | @@ -1020,6 +1060,16 @@ | ... | @@ -1020,6 +1060,16 @@ |
| 1020 | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", | 1060 | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| 1021 | "license": "MIT" | 1061 | "license": "MIT" |
| 1022 | }, | 1062 | }, |
| 1063 | "node_modules/@types/yauzl": { | ||
| 1064 | "version": "2.10.3", | ||
| 1065 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", | ||
| 1066 | "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", | ||
| 1067 | "license": "MIT", | ||
| 1068 | "optional": true, | ||
| 1069 | "dependencies": { | ||
| 1070 | "@types/node": "*" | ||
| 1071 | } | ||
| 1072 | }, | ||
| 1023 | "node_modules/@ungap/structured-clone": { | 1073 | "node_modules/@ungap/structured-clone": { |
| 1024 | "version": "1.3.0", | 1074 | "version": "1.3.0", |
| 1025 | "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", | 1075 | "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", |
| ... | @@ -1047,6 +1097,15 @@ | ... | @@ -1047,6 +1097,15 @@ |
| 1047 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" | 1097 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" |
| 1048 | } | 1098 | } |
| 1049 | }, | 1099 | }, |
| 1100 | "node_modules/agent-base": { | ||
| 1101 | "version": "7.1.3", | ||
| 1102 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", | ||
| 1103 | "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", | ||
| 1104 | "license": "MIT", | ||
| 1105 | "engines": { | ||
| 1106 | "node": ">= 14" | ||
| 1107 | } | ||
| 1108 | }, | ||
| 1050 | "node_modules/ansi-escapes": { | 1109 | "node_modules/ansi-escapes": { |
| 1051 | "version": "7.0.0", | 1110 | "version": "7.0.0", |
| 1052 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", | 1111 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", |
| ... | @@ -1074,6 +1133,39 @@ | ... | @@ -1074,6 +1133,39 @@ |
| 1074 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" | 1133 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" |
| 1075 | } | 1134 | } |
| 1076 | }, | 1135 | }, |
| 1136 | "node_modules/ansi-styles": { | ||
| 1137 | "version": "4.3.0", | ||
| 1138 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", | ||
| 1139 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", | ||
| 1140 | "license": "MIT", | ||
| 1141 | "dependencies": { | ||
| 1142 | "color-convert": "^2.0.1" | ||
| 1143 | }, | ||
| 1144 | "engines": { | ||
| 1145 | "node": ">=8" | ||
| 1146 | }, | ||
| 1147 | "funding": { | ||
| 1148 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" | ||
| 1149 | } | ||
| 1150 | }, | ||
| 1151 | "node_modules/argparse": { | ||
| 1152 | "version": "2.0.1", | ||
| 1153 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", | ||
| 1154 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", | ||
| 1155 | "license": "Python-2.0" | ||
| 1156 | }, | ||
| 1157 | "node_modules/ast-types": { | ||
| 1158 | "version": "0.13.4", | ||
| 1159 | "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", | ||
| 1160 | "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", | ||
| 1161 | "license": "MIT", | ||
| 1162 | "dependencies": { | ||
| 1163 | "tslib": "^2.0.1" | ||
| 1164 | }, | ||
| 1165 | "engines": { | ||
| 1166 | "node": ">=4" | ||
| 1167 | } | ||
| 1168 | }, | ||
| 1077 | "node_modules/astring": { | 1169 | "node_modules/astring": { |
| 1078 | "version": "1.9.0", | 1170 | "version": "1.9.0", |
| 1079 | "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", | 1171 | "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", |
| ... | @@ -1083,6 +1175,12 @@ | ... | @@ -1083,6 +1175,12 @@ |
| 1083 | "astring": "bin/astring" | 1175 | "astring": "bin/astring" |
| 1084 | } | 1176 | } |
| 1085 | }, | 1177 | }, |
| 1178 | "node_modules/b4a": { | ||
| 1179 | "version": "1.6.7", | ||
| 1180 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", | ||
| 1181 | "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", | ||
| 1182 | "license": "Apache-2.0" | ||
| 1183 | }, | ||
| 1086 | "node_modules/bail": { | 1184 | "node_modules/bail": { |
| 1087 | "version": "2.0.2", | 1185 | "version": "2.0.2", |
| 1088 | "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", | 1186 | "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", |
| ... | @@ -1093,6 +1191,87 @@ | ... | @@ -1093,6 +1191,87 @@ |
| 1093 | "url": "https://github.com/sponsors/wooorm" | 1191 | "url": "https://github.com/sponsors/wooorm" |
| 1094 | } | 1192 | } |
| 1095 | }, | 1193 | }, |
| 1194 | "node_modules/bare-events": { | ||
| 1195 | "version": "2.5.4", | ||
| 1196 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", | ||
| 1197 | "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", | ||
| 1198 | "license": "Apache-2.0", | ||
| 1199 | "optional": true | ||
| 1200 | }, | ||
| 1201 | "node_modules/bare-fs": { | ||
| 1202 | "version": "4.1.5", | ||
| 1203 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", | ||
| 1204 | "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", | ||
| 1205 | "license": "Apache-2.0", | ||
| 1206 | "optional": true, | ||
| 1207 | "dependencies": { | ||
| 1208 | "bare-events": "^2.5.4", | ||
| 1209 | "bare-path": "^3.0.0", | ||
| 1210 | "bare-stream": "^2.6.4" | ||
| 1211 | }, | ||
| 1212 | "engines": { | ||
| 1213 | "bare": ">=1.16.0" | ||
| 1214 | }, | ||
| 1215 | "peerDependencies": { | ||
| 1216 | "bare-buffer": "*" | ||
| 1217 | }, | ||
| 1218 | "peerDependenciesMeta": { | ||
| 1219 | "bare-buffer": { | ||
| 1220 | "optional": true | ||
| 1221 | } | ||
| 1222 | } | ||
| 1223 | }, | ||
| 1224 | "node_modules/bare-os": { | ||
| 1225 | "version": "3.6.1", | ||
| 1226 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", | ||
| 1227 | "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", | ||
| 1228 | "license": "Apache-2.0", | ||
| 1229 | "optional": true, | ||
| 1230 | "engines": { | ||
| 1231 | "bare": ">=1.14.0" | ||
| 1232 | } | ||
| 1233 | }, | ||
| 1234 | "node_modules/bare-path": { | ||
| 1235 | "version": "3.0.0", | ||
| 1236 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | ||
| 1237 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | ||
| 1238 | "license": "Apache-2.0", | ||
| 1239 | "optional": true, | ||
| 1240 | "dependencies": { | ||
| 1241 | "bare-os": "^3.0.1" | ||
| 1242 | } | ||
| 1243 | }, | ||
| 1244 | "node_modules/bare-stream": { | ||
| 1245 | "version": "2.6.5", | ||
| 1246 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", | ||
| 1247 | "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", | ||
| 1248 | "license": "Apache-2.0", | ||
| 1249 | "optional": true, | ||
| 1250 | "dependencies": { | ||
| 1251 | "streamx": "^2.21.0" | ||
| 1252 | }, | ||
| 1253 | "peerDependencies": { | ||
| 1254 | "bare-buffer": "*", | ||
| 1255 | "bare-events": "*" | ||
| 1256 | }, | ||
| 1257 | "peerDependenciesMeta": { | ||
| 1258 | "bare-buffer": { | ||
| 1259 | "optional": true | ||
| 1260 | }, | ||
| 1261 | "bare-events": { | ||
| 1262 | "optional": true | ||
| 1263 | } | ||
| 1264 | } | ||
| 1265 | }, | ||
| 1266 | "node_modules/basic-ftp": { | ||
| 1267 | "version": "5.0.5", | ||
| 1268 | "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", | ||
| 1269 | "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", | ||
| 1270 | "license": "MIT", | ||
| 1271 | "engines": { | ||
| 1272 | "node": ">=10.0.0" | ||
| 1273 | } | ||
| 1274 | }, | ||
| 1096 | "node_modules/browserslist": { | 1275 | "node_modules/browserslist": { |
| 1097 | "version": "4.25.0", | 1276 | "version": "4.25.0", |
| 1098 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", | 1277 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", |
| ... | @@ -1125,12 +1304,30 @@ | ... | @@ -1125,12 +1304,30 @@ |
| 1125 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" | 1304 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" |
| 1126 | } | 1305 | } |
| 1127 | }, | 1306 | }, |
| 1307 | "node_modules/buffer-crc32": { | ||
| 1308 | "version": "0.2.13", | ||
| 1309 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", | ||
| 1310 | "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", | ||
| 1311 | "license": "MIT", | ||
| 1312 | "engines": { | ||
| 1313 | "node": "*" | ||
| 1314 | } | ||
| 1315 | }, | ||
| 1128 | "node_modules/buffer-from": { | 1316 | "node_modules/buffer-from": { |
| 1129 | "version": "1.1.2", | 1317 | "version": "1.1.2", |
| 1130 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", | 1318 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", |
| 1131 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", | 1319 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", |
| 1132 | "license": "MIT" | 1320 | "license": "MIT" |
| 1133 | }, | 1321 | }, |
| 1322 | "node_modules/callsites": { | ||
| 1323 | "version": "3.1.0", | ||
| 1324 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", | ||
| 1325 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", | ||
| 1326 | "license": "MIT", | ||
| 1327 | "engines": { | ||
| 1328 | "node": ">=6" | ||
| 1329 | } | ||
| 1330 | }, | ||
| 1134 | "node_modules/caniuse-lite": { | 1331 | "node_modules/caniuse-lite": { |
| 1135 | "version": "1.0.30001721", | 1332 | "version": "1.0.30001721", |
| 1136 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", | 1333 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", |
| ... | @@ -1213,6 +1410,54 @@ | ... | @@ -1213,6 +1410,54 @@ |
| 1213 | "url": "https://github.com/sponsors/wooorm" | 1410 | "url": "https://github.com/sponsors/wooorm" |
| 1214 | } | 1411 | } |
| 1215 | }, | 1412 | }, |
| 1413 | "node_modules/chromium-bidi": { | ||
| 1414 | "version": "5.1.0", | ||
| 1415 | "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", | ||
| 1416 | "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", | ||
| 1417 | "license": "Apache-2.0", | ||
| 1418 | "dependencies": { | ||
| 1419 | "mitt": "^3.0.1", | ||
| 1420 | "zod": "^3.24.1" | ||
| 1421 | }, | ||
| 1422 | "peerDependencies": { | ||
| 1423 | "devtools-protocol": "*" | ||
| 1424 | } | ||
| 1425 | }, | ||
| 1426 | "node_modules/cliui": { | ||
| 1427 | "version": "8.0.1", | ||
| 1428 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", | ||
| 1429 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", | ||
| 1430 | "license": "ISC", | ||
| 1431 | "dependencies": { | ||
| 1432 | "string-width": "^4.2.0", | ||
| 1433 | "strip-ansi": "^6.0.1", | ||
| 1434 | "wrap-ansi": "^7.0.0" | ||
| 1435 | }, | ||
| 1436 | "engines": { | ||
| 1437 | "node": ">=12" | ||
| 1438 | } | ||
| 1439 | }, | ||
| 1440 | "node_modules/cliui/node_modules/ansi-regex": { | ||
| 1441 | "version": "5.0.1", | ||
| 1442 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||
| 1443 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", | ||
| 1444 | "license": "MIT", | ||
| 1445 | "engines": { | ||
| 1446 | "node": ">=8" | ||
| 1447 | } | ||
| 1448 | }, | ||
| 1449 | "node_modules/cliui/node_modules/strip-ansi": { | ||
| 1450 | "version": "6.0.1", | ||
| 1451 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||
| 1452 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||
| 1453 | "license": "MIT", | ||
| 1454 | "dependencies": { | ||
| 1455 | "ansi-regex": "^5.0.1" | ||
| 1456 | }, | ||
| 1457 | "engines": { | ||
| 1458 | "node": ">=8" | ||
| 1459 | } | ||
| 1460 | }, | ||
| 1216 | "node_modules/collapse-white-space": { | 1461 | "node_modules/collapse-white-space": { |
| 1217 | "version": "2.1.0", | 1462 | "version": "2.1.0", |
| 1218 | "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", | 1463 | "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", |
| ... | @@ -1223,6 +1468,24 @@ | ... | @@ -1223,6 +1468,24 @@ |
| 1223 | "url": "https://github.com/sponsors/wooorm" | 1468 | "url": "https://github.com/sponsors/wooorm" |
| 1224 | } | 1469 | } |
| 1225 | }, | 1470 | }, |
| 1471 | "node_modules/color-convert": { | ||
| 1472 | "version": "2.0.1", | ||
| 1473 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", | ||
| 1474 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", | ||
| 1475 | "license": "MIT", | ||
| 1476 | "dependencies": { | ||
| 1477 | "color-name": "~1.1.4" | ||
| 1478 | }, | ||
| 1479 | "engines": { | ||
| 1480 | "node": ">=7.0.0" | ||
| 1481 | } | ||
| 1482 | }, | ||
| 1483 | "node_modules/color-name": { | ||
| 1484 | "version": "1.1.4", | ||
| 1485 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", | ||
| 1486 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", | ||
| 1487 | "license": "MIT" | ||
| 1488 | }, | ||
| 1226 | "node_modules/comma-separated-tokens": { | 1489 | "node_modules/comma-separated-tokens": { |
| 1227 | "version": "2.0.3", | 1490 | "version": "2.0.3", |
| 1228 | "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", | 1491 | "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", |
| ... | @@ -1248,12 +1511,47 @@ | ... | @@ -1248,12 +1511,47 @@ |
| 1248 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | 1511 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", |
| 1249 | "license": "MIT" | 1512 | "license": "MIT" |
| 1250 | }, | 1513 | }, |
| 1514 | "node_modules/cosmiconfig": { | ||
| 1515 | "version": "9.0.0", | ||
| 1516 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", | ||
| 1517 | "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", | ||
| 1518 | "license": "MIT", | ||
| 1519 | "dependencies": { | ||
| 1520 | "env-paths": "^2.2.1", | ||
| 1521 | "import-fresh": "^3.3.0", | ||
| 1522 | "js-yaml": "^4.1.0", | ||
| 1523 | "parse-json": "^5.2.0" | ||
| 1524 | }, | ||
| 1525 | "engines": { | ||
| 1526 | "node": ">=14" | ||
| 1527 | }, | ||
| 1528 | "funding": { | ||
| 1529 | "url": "https://github.com/sponsors/d-fischer" | ||
| 1530 | }, | ||
| 1531 | "peerDependencies": { | ||
| 1532 | "typescript": ">=4.9.5" | ||
| 1533 | }, | ||
| 1534 | "peerDependenciesMeta": { | ||
| 1535 | "typescript": { | ||
| 1536 | "optional": true | ||
| 1537 | } | ||
| 1538 | } | ||
| 1539 | }, | ||
| 1251 | "node_modules/csstype": { | 1540 | "node_modules/csstype": { |
| 1252 | "version": "3.1.3", | 1541 | "version": "3.1.3", |
| 1253 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", | 1542 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", |
| 1254 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", | 1543 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", |
| 1255 | "license": "MIT" | 1544 | "license": "MIT" |
| 1256 | }, | 1545 | }, |
| 1546 | "node_modules/data-uri-to-buffer": { | ||
| 1547 | "version": "6.0.2", | ||
| 1548 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", | ||
| 1549 | "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", | ||
| 1550 | "license": "MIT", | ||
| 1551 | "engines": { | ||
| 1552 | "node": ">= 14" | ||
| 1553 | } | ||
| 1554 | }, | ||
| 1257 | "node_modules/debug": { | 1555 | "node_modules/debug": { |
| 1258 | "version": "4.4.1", | 1556 | "version": "4.4.1", |
| 1259 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", | 1557 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", |
| ... | @@ -1284,6 +1582,20 @@ | ... | @@ -1284,6 +1582,20 @@ |
| 1284 | "url": "https://github.com/sponsors/wooorm" | 1582 | "url": "https://github.com/sponsors/wooorm" |
| 1285 | } | 1583 | } |
| 1286 | }, | 1584 | }, |
| 1585 | "node_modules/degenerator": { | ||
| 1586 | "version": "5.0.1", | ||
| 1587 | "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", | ||
| 1588 | "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", | ||
| 1589 | "license": "MIT", | ||
| 1590 | "dependencies": { | ||
| 1591 | "ast-types": "^0.13.4", | ||
| 1592 | "escodegen": "^2.1.0", | ||
| 1593 | "esprima": "^4.0.1" | ||
| 1594 | }, | ||
| 1595 | "engines": { | ||
| 1596 | "node": ">= 14" | ||
| 1597 | } | ||
| 1598 | }, | ||
| 1287 | "node_modules/dequal": { | 1599 | "node_modules/dequal": { |
| 1288 | "version": "2.0.3", | 1600 | "version": "2.0.3", |
| 1289 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", | 1601 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", |
| ... | @@ -1306,12 +1618,42 @@ | ... | @@ -1306,12 +1618,42 @@ |
| 1306 | "url": "https://github.com/sponsors/wooorm" | 1618 | "url": "https://github.com/sponsors/wooorm" |
| 1307 | } | 1619 | } |
| 1308 | }, | 1620 | }, |
| 1621 | "node_modules/devtools-protocol": { | ||
| 1622 | "version": "0.0.1452169", | ||
| 1623 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz", | ||
| 1624 | "integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==", | ||
| 1625 | "license": "BSD-3-Clause" | ||
| 1626 | }, | ||
| 1309 | "node_modules/electron-to-chromium": { | 1627 | "node_modules/electron-to-chromium": { |
| 1310 | "version": "1.5.165", | 1628 | "version": "1.5.165", |
| 1311 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", | 1629 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", |
| 1312 | "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", | 1630 | "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", |
| 1313 | "license": "ISC" | 1631 | "license": "ISC" |
| 1314 | }, | 1632 | }, |
| 1633 | "node_modules/emoji-regex": { | ||
| 1634 | "version": "8.0.0", | ||
| 1635 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | ||
| 1636 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", | ||
| 1637 | "license": "MIT" | ||
| 1638 | }, | ||
| 1639 | "node_modules/end-of-stream": { | ||
| 1640 | "version": "1.4.4", | ||
| 1641 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", | ||
| 1642 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", | ||
| 1643 | "license": "MIT", | ||
| 1644 | "dependencies": { | ||
| 1645 | "once": "^1.4.0" | ||
| 1646 | } | ||
| 1647 | }, | ||
| 1648 | "node_modules/env-paths": { | ||
| 1649 | "version": "2.2.1", | ||
| 1650 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", | ||
| 1651 | "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", | ||
| 1652 | "license": "MIT", | ||
| 1653 | "engines": { | ||
| 1654 | "node": ">=6" | ||
| 1655 | } | ||
| 1656 | }, | ||
| 1315 | "node_modules/environment": { | 1657 | "node_modules/environment": { |
| 1316 | "version": "1.1.0", | 1658 | "version": "1.1.0", |
| 1317 | "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", | 1659 | "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", |
| ... | @@ -1324,6 +1666,15 @@ | ... | @@ -1324,6 +1666,15 @@ |
| 1324 | "url": "https://github.com/sponsors/sindresorhus" | 1666 | "url": "https://github.com/sponsors/sindresorhus" |
| 1325 | } | 1667 | } |
| 1326 | }, | 1668 | }, |
| 1669 | "node_modules/error-ex": { | ||
| 1670 | "version": "1.3.2", | ||
| 1671 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", | ||
| 1672 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", | ||
| 1673 | "license": "MIT", | ||
| 1674 | "dependencies": { | ||
| 1675 | "is-arrayish": "^0.2.1" | ||
| 1676 | } | ||
| 1677 | }, | ||
| 1327 | "node_modules/error-stack-parser": { | 1678 | "node_modules/error-stack-parser": { |
| 1328 | "version": "2.1.4", | 1679 | "version": "2.1.4", |
| 1329 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | 1680 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", |
| ... | @@ -1414,6 +1765,59 @@ | ... | @@ -1414,6 +1765,59 @@ |
| 1414 | "node": ">=6" | 1765 | "node": ">=6" |
| 1415 | } | 1766 | } |
| 1416 | }, | 1767 | }, |
| 1768 | "node_modules/escodegen": { | ||
| 1769 | "version": "2.1.0", | ||
| 1770 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", | ||
| 1771 | "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", | ||
| 1772 | "license": "BSD-2-Clause", | ||
| 1773 | "dependencies": { | ||
| 1774 | "esprima": "^4.0.1", | ||
| 1775 | "estraverse": "^5.2.0", | ||
| 1776 | "esutils": "^2.0.2" | ||
| 1777 | }, | ||
| 1778 | "bin": { | ||
| 1779 | "escodegen": "bin/escodegen.js", | ||
| 1780 | "esgenerate": "bin/esgenerate.js" | ||
| 1781 | }, | ||
| 1782 | "engines": { | ||
| 1783 | "node": ">=6.0" | ||
| 1784 | }, | ||
| 1785 | "optionalDependencies": { | ||
| 1786 | "source-map": "~0.6.1" | ||
| 1787 | } | ||
| 1788 | }, | ||
| 1789 | "node_modules/escodegen/node_modules/source-map": { | ||
| 1790 | "version": "0.6.1", | ||
| 1791 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", | ||
| 1792 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", | ||
| 1793 | "license": "BSD-3-Clause", | ||
| 1794 | "optional": true, | ||
| 1795 | "engines": { | ||
| 1796 | "node": ">=0.10.0" | ||
| 1797 | } | ||
| 1798 | }, | ||
| 1799 | "node_modules/esprima": { | ||
| 1800 | "version": "4.0.1", | ||
| 1801 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", | ||
| 1802 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", | ||
| 1803 | "license": "BSD-2-Clause", | ||
| 1804 | "bin": { | ||
| 1805 | "esparse": "bin/esparse.js", | ||
| 1806 | "esvalidate": "bin/esvalidate.js" | ||
| 1807 | }, | ||
| 1808 | "engines": { | ||
| 1809 | "node": ">=4" | ||
| 1810 | } | ||
| 1811 | }, | ||
| 1812 | "node_modules/estraverse": { | ||
| 1813 | "version": "5.3.0", | ||
| 1814 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", | ||
| 1815 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", | ||
| 1816 | "license": "BSD-2-Clause", | ||
| 1817 | "engines": { | ||
| 1818 | "node": ">=4.0" | ||
| 1819 | } | ||
| 1820 | }, | ||
| 1417 | "node_modules/estree-util-attach-comments": { | 1821 | "node_modules/estree-util-attach-comments": { |
| 1418 | "version": "3.0.0", | 1822 | "version": "3.0.0", |
| 1419 | "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", | 1823 | "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", |
| ... | @@ -1505,12 +1909,56 @@ | ... | @@ -1505,12 +1909,56 @@ |
| 1505 | "@types/estree": "^1.0.0" | 1909 | "@types/estree": "^1.0.0" |
| 1506 | } | 1910 | } |
| 1507 | }, | 1911 | }, |
| 1912 | "node_modules/esutils": { | ||
| 1913 | "version": "2.0.3", | ||
| 1914 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", | ||
| 1915 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", | ||
| 1916 | "license": "BSD-2-Clause", | ||
| 1917 | "engines": { | ||
| 1918 | "node": ">=0.10.0" | ||
| 1919 | } | ||
| 1920 | }, | ||
| 1508 | "node_modules/extend": { | 1921 | "node_modules/extend": { |
| 1509 | "version": "3.0.2", | 1922 | "version": "3.0.2", |
| 1510 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", | 1923 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", |
| 1511 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", | 1924 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", |
| 1512 | "license": "MIT" | 1925 | "license": "MIT" |
| 1513 | }, | 1926 | }, |
| 1927 | "node_modules/extract-zip": { | ||
| 1928 | "version": "2.0.1", | ||
| 1929 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", | ||
| 1930 | "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", | ||
| 1931 | "license": "BSD-2-Clause", | ||
| 1932 | "dependencies": { | ||
| 1933 | "debug": "^4.1.1", | ||
| 1934 | "get-stream": "^5.1.0", | ||
| 1935 | "yauzl": "^2.10.0" | ||
| 1936 | }, | ||
| 1937 | "bin": { | ||
| 1938 | "extract-zip": "cli.js" | ||
| 1939 | }, | ||
| 1940 | "engines": { | ||
| 1941 | "node": ">= 10.17.0" | ||
| 1942 | }, | ||
| 1943 | "optionalDependencies": { | ||
| 1944 | "@types/yauzl": "^2.9.1" | ||
| 1945 | } | ||
| 1946 | }, | ||
| 1947 | "node_modules/fast-fifo": { | ||
| 1948 | "version": "1.3.2", | ||
| 1949 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | ||
| 1950 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | ||
| 1951 | "license": "MIT" | ||
| 1952 | }, | ||
| 1953 | "node_modules/fd-slicer": { | ||
| 1954 | "version": "1.1.0", | ||
| 1955 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", | ||
| 1956 | "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", | ||
| 1957 | "license": "MIT", | ||
| 1958 | "dependencies": { | ||
| 1959 | "pend": "~1.2.0" | ||
| 1960 | } | ||
| 1961 | }, | ||
| 1514 | "node_modules/gensync": { | 1962 | "node_modules/gensync": { |
| 1515 | "version": "1.0.0-beta.2", | 1963 | "version": "1.0.0-beta.2", |
| 1516 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", | 1964 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", |
| ... | @@ -1520,6 +1968,44 @@ | ... | @@ -1520,6 +1968,44 @@ |
| 1520 | "node": ">=6.9.0" | 1968 | "node": ">=6.9.0" |
| 1521 | } | 1969 | } |
| 1522 | }, | 1970 | }, |
| 1971 | "node_modules/get-caller-file": { | ||
| 1972 | "version": "2.0.5", | ||
| 1973 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", | ||
| 1974 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", | ||
| 1975 | "license": "ISC", | ||
| 1976 | "engines": { | ||
| 1977 | "node": "6.* || 8.* || >= 10.*" | ||
| 1978 | } | ||
| 1979 | }, | ||
| 1980 | "node_modules/get-stream": { | ||
| 1981 | "version": "5.2.0", | ||
| 1982 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", | ||
| 1983 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", | ||
| 1984 | "license": "MIT", | ||
| 1985 | "dependencies": { | ||
| 1986 | "pump": "^3.0.0" | ||
| 1987 | }, | ||
| 1988 | "engines": { | ||
| 1989 | "node": ">=8" | ||
| 1990 | }, | ||
| 1991 | "funding": { | ||
| 1992 | "url": "https://github.com/sponsors/sindresorhus" | ||
| 1993 | } | ||
| 1994 | }, | ||
| 1995 | "node_modules/get-uri": { | ||
| 1996 | "version": "6.0.4", | ||
| 1997 | "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", | ||
| 1998 | "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", | ||
| 1999 | "license": "MIT", | ||
| 2000 | "dependencies": { | ||
| 2001 | "basic-ftp": "^5.0.2", | ||
| 2002 | "data-uri-to-buffer": "^6.0.2", | ||
| 2003 | "debug": "^4.3.4" | ||
| 2004 | }, | ||
| 2005 | "engines": { | ||
| 2006 | "node": ">= 14" | ||
| 2007 | } | ||
| 2008 | }, | ||
| 1523 | "node_modules/globals": { | 2009 | "node_modules/globals": { |
| 1524 | "version": "11.12.0", | 2010 | "version": "11.12.0", |
| 1525 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", | 2011 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", |
| ... | @@ -1618,21 +2104,85 @@ | ... | @@ -1618,21 +2104,85 @@ |
| 1618 | "integrity": "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ==", | 2104 | "integrity": "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ==", |
| 1619 | "license": "MIT", | 2105 | "license": "MIT", |
| 1620 | "engines": { | 2106 | "engines": { |
| 1621 | "node": ">=16.9.0" | 2107 | "node": ">=16.9.0" |
| 2108 | } | ||
| 2109 | }, | ||
| 2110 | "node_modules/htmljs-parser": { | ||
| 2111 | "version": "5.5.4", | ||
| 2112 | "resolved": "https://registry.npmjs.org/htmljs-parser/-/htmljs-parser-5.5.4.tgz", | ||
| 2113 | "integrity": "sha512-qJiir3ViASVhGdNW4kwM4pQWvDtDLtjwaflEQDooiNzbOPIzoaIzmRFWweFl8GomVhJqXqX0jjrhDED3pmAdzg==", | ||
| 2114 | "license": "MIT" | ||
| 2115 | }, | ||
| 2116 | "node_modules/http-proxy-agent": { | ||
| 2117 | "version": "7.0.2", | ||
| 2118 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", | ||
| 2119 | "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", | ||
| 2120 | "license": "MIT", | ||
| 2121 | "dependencies": { | ||
| 2122 | "agent-base": "^7.1.0", | ||
| 2123 | "debug": "^4.3.4" | ||
| 2124 | }, | ||
| 2125 | "engines": { | ||
| 2126 | "node": ">= 14" | ||
| 2127 | } | ||
| 2128 | }, | ||
| 2129 | "node_modules/https-proxy-agent": { | ||
| 2130 | "version": "7.0.6", | ||
| 2131 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", | ||
| 2132 | "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", | ||
| 2133 | "license": "MIT", | ||
| 2134 | "dependencies": { | ||
| 2135 | "agent-base": "^7.1.2", | ||
| 2136 | "debug": "4" | ||
| 2137 | }, | ||
| 2138 | "engines": { | ||
| 2139 | "node": ">= 14" | ||
| 2140 | } | ||
| 2141 | }, | ||
| 2142 | "node_modules/import-fresh": { | ||
| 2143 | "version": "3.3.1", | ||
| 2144 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", | ||
| 2145 | "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", | ||
| 2146 | "license": "MIT", | ||
| 2147 | "dependencies": { | ||
| 2148 | "parent-module": "^1.0.0", | ||
| 2149 | "resolve-from": "^4.0.0" | ||
| 2150 | }, | ||
| 2151 | "engines": { | ||
| 2152 | "node": ">=6" | ||
| 2153 | }, | ||
| 2154 | "funding": { | ||
| 2155 | "url": "https://github.com/sponsors/sindresorhus" | ||
| 2156 | } | ||
| 2157 | }, | ||
| 2158 | "node_modules/import-fresh/node_modules/resolve-from": { | ||
| 2159 | "version": "4.0.0", | ||
| 2160 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", | ||
| 2161 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", | ||
| 2162 | "license": "MIT", | ||
| 2163 | "engines": { | ||
| 2164 | "node": ">=4" | ||
| 1622 | } | 2165 | } |
| 1623 | }, | 2166 | }, |
| 1624 | "node_modules/htmljs-parser": { | ||
| 1625 | "version": "5.5.4", | ||
| 1626 | "resolved": "https://registry.npmjs.org/htmljs-parser/-/htmljs-parser-5.5.4.tgz", | ||
| 1627 | "integrity": "sha512-qJiir3ViASVhGdNW4kwM4pQWvDtDLtjwaflEQDooiNzbOPIzoaIzmRFWweFl8GomVhJqXqX0jjrhDED3pmAdzg==", | ||
| 1628 | "license": "MIT" | ||
| 1629 | }, | ||
| 1630 | "node_modules/inline-style-parser": { | 2167 | "node_modules/inline-style-parser": { |
| 1631 | "version": "0.2.4", | 2168 | "version": "0.2.4", |
| 1632 | "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", | 2169 | "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", |
| 1633 | "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", | 2170 | "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", |
| 1634 | "license": "MIT" | 2171 | "license": "MIT" |
| 1635 | }, | 2172 | }, |
| 2173 | "node_modules/ip-address": { | ||
| 2174 | "version": "9.0.5", | ||
| 2175 | "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", | ||
| 2176 | "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", | ||
| 2177 | "license": "MIT", | ||
| 2178 | "dependencies": { | ||
| 2179 | "jsbn": "1.1.0", | ||
| 2180 | "sprintf-js": "^1.1.3" | ||
| 2181 | }, | ||
| 2182 | "engines": { | ||
| 2183 | "node": ">= 12" | ||
| 2184 | } | ||
| 2185 | }, | ||
| 1636 | "node_modules/is-alphabetical": { | 2186 | "node_modules/is-alphabetical": { |
| 1637 | "version": "2.0.1", | 2187 | "version": "2.0.1", |
| 1638 | "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", | 2188 | "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", |
| ... | @@ -1657,6 +2207,12 @@ | ... | @@ -1657,6 +2207,12 @@ |
| 1657 | "url": "https://github.com/sponsors/wooorm" | 2207 | "url": "https://github.com/sponsors/wooorm" |
| 1658 | } | 2208 | } |
| 1659 | }, | 2209 | }, |
| 2210 | "node_modules/is-arrayish": { | ||
| 2211 | "version": "0.2.1", | ||
| 2212 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", | ||
| 2213 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", | ||
| 2214 | "license": "MIT" | ||
| 2215 | }, | ||
| 1660 | "node_modules/is-decimal": { | 2216 | "node_modules/is-decimal": { |
| 1661 | "version": "2.0.1", | 2217 | "version": "2.0.1", |
| 1662 | "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", | 2218 | "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", |
| ... | @@ -1667,6 +2223,15 @@ | ... | @@ -1667,6 +2223,15 @@ |
| 1667 | "url": "https://github.com/sponsors/wooorm" | 2223 | "url": "https://github.com/sponsors/wooorm" |
| 1668 | } | 2224 | } |
| 1669 | }, | 2225 | }, |
| 2226 | "node_modules/is-fullwidth-code-point": { | ||
| 2227 | "version": "3.0.0", | ||
| 2228 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", | ||
| 2229 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", | ||
| 2230 | "license": "MIT", | ||
| 2231 | "engines": { | ||
| 2232 | "node": ">=8" | ||
| 2233 | } | ||
| 2234 | }, | ||
| 1670 | "node_modules/is-hexadecimal": { | 2235 | "node_modules/is-hexadecimal": { |
| 1671 | "version": "2.0.1", | 2236 | "version": "2.0.1", |
| 1672 | "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", | 2237 | "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", |
| ... | @@ -1695,6 +2260,24 @@ | ... | @@ -1695,6 +2260,24 @@ |
| 1695 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", | 2260 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", |
| 1696 | "license": "MIT" | 2261 | "license": "MIT" |
| 1697 | }, | 2262 | }, |
| 2263 | "node_modules/js-yaml": { | ||
| 2264 | "version": "4.1.0", | ||
| 2265 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", | ||
| 2266 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", | ||
| 2267 | "license": "MIT", | ||
| 2268 | "dependencies": { | ||
| 2269 | "argparse": "^2.0.1" | ||
| 2270 | }, | ||
| 2271 | "bin": { | ||
| 2272 | "js-yaml": "bin/js-yaml.js" | ||
| 2273 | } | ||
| 2274 | }, | ||
| 2275 | "node_modules/jsbn": { | ||
| 2276 | "version": "1.1.0", | ||
| 2277 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", | ||
| 2278 | "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", | ||
| 2279 | "license": "MIT" | ||
| 2280 | }, | ||
| 1698 | "node_modules/jsesc": { | 2281 | "node_modules/jsesc": { |
| 1699 | "version": "3.1.0", | 2282 | "version": "3.1.0", |
| 1700 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", | 2283 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", |
| ... | @@ -1707,6 +2290,12 @@ | ... | @@ -1707,6 +2290,12 @@ |
| 1707 | "node": ">=6" | 2290 | "node": ">=6" |
| 1708 | } | 2291 | } |
| 1709 | }, | 2292 | }, |
| 2293 | "node_modules/json-parse-even-better-errors": { | ||
| 2294 | "version": "2.3.1", | ||
| 2295 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", | ||
| 2296 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", | ||
| 2297 | "license": "MIT" | ||
| 2298 | }, | ||
| 1710 | "node_modules/json5": { | 2299 | "node_modules/json5": { |
| 1711 | "version": "2.2.3", | 2300 | "version": "2.2.3", |
| 1712 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", | 2301 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", |
| ... | @@ -1746,6 +2335,12 @@ | ... | @@ -1746,6 +2335,12 @@ |
| 1746 | "lasso-caching-fs": "^1.0.0" | 2335 | "lasso-caching-fs": "^1.0.0" |
| 1747 | } | 2336 | } |
| 1748 | }, | 2337 | }, |
| 2338 | "node_modules/lines-and-columns": { | ||
| 2339 | "version": "1.2.4", | ||
| 2340 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", | ||
| 2341 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", | ||
| 2342 | "license": "MIT" | ||
| 2343 | }, | ||
| 1749 | "node_modules/longest-streak": { | 2344 | "node_modules/longest-streak": { |
| 1750 | "version": "3.1.0", | 2345 | "version": "3.1.0", |
| 1751 | "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", | 2346 | "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", |
| ... | @@ -2566,18 +3161,86 @@ | ... | @@ -2566,18 +3161,86 @@ |
| 2566 | ], | 3161 | ], |
| 2567 | "license": "MIT" | 3162 | "license": "MIT" |
| 2568 | }, | 3163 | }, |
| 3164 | "node_modules/mitt": { | ||
| 3165 | "version": "3.0.1", | ||
| 3166 | "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", | ||
| 3167 | "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", | ||
| 3168 | "license": "MIT" | ||
| 3169 | }, | ||
| 2569 | "node_modules/ms": { | 3170 | "node_modules/ms": { |
| 2570 | "version": "2.1.3", | 3171 | "version": "2.1.3", |
| 2571 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", | 3172 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", |
| 2572 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", | 3173 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", |
| 2573 | "license": "MIT" | 3174 | "license": "MIT" |
| 2574 | }, | 3175 | }, |
| 3176 | "node_modules/netmask": { | ||
| 3177 | "version": "2.0.2", | ||
| 3178 | "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", | ||
| 3179 | "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", | ||
| 3180 | "license": "MIT", | ||
| 3181 | "engines": { | ||
| 3182 | "node": ">= 0.4.0" | ||
| 3183 | } | ||
| 3184 | }, | ||
| 2575 | "node_modules/node-releases": { | 3185 | "node_modules/node-releases": { |
| 2576 | "version": "2.0.19", | 3186 | "version": "2.0.19", |
| 2577 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", | 3187 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", |
| 2578 | "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", | 3188 | "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", |
| 2579 | "license": "MIT" | 3189 | "license": "MIT" |
| 2580 | }, | 3190 | }, |
| 3191 | "node_modules/once": { | ||
| 3192 | "version": "1.4.0", | ||
| 3193 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | ||
| 3194 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", | ||
| 3195 | "license": "ISC", | ||
| 3196 | "dependencies": { | ||
| 3197 | "wrappy": "1" | ||
| 3198 | } | ||
| 3199 | }, | ||
| 3200 | "node_modules/pac-proxy-agent": { | ||
| 3201 | "version": "7.2.0", | ||
| 3202 | "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", | ||
| 3203 | "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", | ||
| 3204 | "license": "MIT", | ||
| 3205 | "dependencies": { | ||
| 3206 | "@tootallnate/quickjs-emscripten": "^0.23.0", | ||
| 3207 | "agent-base": "^7.1.2", | ||
| 3208 | "debug": "^4.3.4", | ||
| 3209 | "get-uri": "^6.0.1", | ||
| 3210 | "http-proxy-agent": "^7.0.0", | ||
| 3211 | "https-proxy-agent": "^7.0.6", | ||
| 3212 | "pac-resolver": "^7.0.1", | ||
| 3213 | "socks-proxy-agent": "^8.0.5" | ||
| 3214 | }, | ||
| 3215 | "engines": { | ||
| 3216 | "node": ">= 14" | ||
| 3217 | } | ||
| 3218 | }, | ||
| 3219 | "node_modules/pac-resolver": { | ||
| 3220 | "version": "7.0.1", | ||
| 3221 | "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", | ||
| 3222 | "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", | ||
| 3223 | "license": "MIT", | ||
| 3224 | "dependencies": { | ||
| 3225 | "degenerator": "^5.0.0", | ||
| 3226 | "netmask": "^2.0.2" | ||
| 3227 | }, | ||
| 3228 | "engines": { | ||
| 3229 | "node": ">= 14" | ||
| 3230 | } | ||
| 3231 | }, | ||
| 3232 | "node_modules/parent-module": { | ||
| 3233 | "version": "1.0.1", | ||
| 3234 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", | ||
| 3235 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", | ||
| 3236 | "license": "MIT", | ||
| 3237 | "dependencies": { | ||
| 3238 | "callsites": "^3.0.0" | ||
| 3239 | }, | ||
| 3240 | "engines": { | ||
| 3241 | "node": ">=6" | ||
| 3242 | } | ||
| 3243 | }, | ||
| 2581 | "node_modules/parse-entities": { | 3244 | "node_modules/parse-entities": { |
| 2582 | "version": "4.0.2", | 3245 | "version": "4.0.2", |
| 2583 | "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", | 3246 | "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", |
| ... | @@ -2603,12 +3266,45 @@ | ... | @@ -2603,12 +3266,45 @@ |
| 2603 | "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", | 3266 | "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", |
| 2604 | "license": "MIT" | 3267 | "license": "MIT" |
| 2605 | }, | 3268 | }, |
| 3269 | "node_modules/parse-json": { | ||
| 3270 | "version": "5.2.0", | ||
| 3271 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", | ||
| 3272 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", | ||
| 3273 | "license": "MIT", | ||
| 3274 | "dependencies": { | ||
| 3275 | "@babel/code-frame": "^7.0.0", | ||
| 3276 | "error-ex": "^1.3.1", | ||
| 3277 | "json-parse-even-better-errors": "^2.3.0", | ||
| 3278 | "lines-and-columns": "^1.1.6" | ||
| 3279 | }, | ||
| 3280 | "engines": { | ||
| 3281 | "node": ">=8" | ||
| 3282 | }, | ||
| 3283 | "funding": { | ||
| 3284 | "url": "https://github.com/sponsors/sindresorhus" | ||
| 3285 | } | ||
| 3286 | }, | ||
| 3287 | "node_modules/pend": { | ||
| 3288 | "version": "1.2.0", | ||
| 3289 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", | ||
| 3290 | "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", | ||
| 3291 | "license": "MIT" | ||
| 3292 | }, | ||
| 2606 | "node_modules/picocolors": { | 3293 | "node_modules/picocolors": { |
| 2607 | "version": "1.1.1", | 3294 | "version": "1.1.1", |
| 2608 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", | 3295 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", |
| 2609 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", | 3296 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", |
| 2610 | "license": "ISC" | 3297 | "license": "ISC" |
| 2611 | }, | 3298 | }, |
| 3299 | "node_modules/progress": { | ||
| 3300 | "version": "2.0.3", | ||
| 3301 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", | ||
| 3302 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", | ||
| 3303 | "license": "MIT", | ||
| 3304 | "engines": { | ||
| 3305 | "node": ">=0.4.0" | ||
| 3306 | } | ||
| 3307 | }, | ||
| 2612 | "node_modules/property-information": { | 3308 | "node_modules/property-information": { |
| 2613 | "version": "7.1.0", | 3309 | "version": "7.1.0", |
| 2614 | "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", | 3310 | "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", |
| ... | @@ -2619,6 +3315,88 @@ | ... | @@ -2619,6 +3315,88 @@ |
| 2619 | "url": "https://github.com/sponsors/wooorm" | 3315 | "url": "https://github.com/sponsors/wooorm" |
| 2620 | } | 3316 | } |
| 2621 | }, | 3317 | }, |
| 3318 | "node_modules/proxy-agent": { | ||
| 3319 | "version": "6.5.0", | ||
| 3320 | "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", | ||
| 3321 | "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", | ||
| 3322 | "license": "MIT", | ||
| 3323 | "dependencies": { | ||
| 3324 | "agent-base": "^7.1.2", | ||
| 3325 | "debug": "^4.3.4", | ||
| 3326 | "http-proxy-agent": "^7.0.1", | ||
| 3327 | "https-proxy-agent": "^7.0.6", | ||
| 3328 | "lru-cache": "^7.14.1", | ||
| 3329 | "pac-proxy-agent": "^7.1.0", | ||
| 3330 | "proxy-from-env": "^1.1.0", | ||
| 3331 | "socks-proxy-agent": "^8.0.5" | ||
| 3332 | }, | ||
| 3333 | "engines": { | ||
| 3334 | "node": ">= 14" | ||
| 3335 | } | ||
| 3336 | }, | ||
| 3337 | "node_modules/proxy-agent/node_modules/lru-cache": { | ||
| 3338 | "version": "7.18.3", | ||
| 3339 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", | ||
| 3340 | "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", | ||
| 3341 | "license": "ISC", | ||
| 3342 | "engines": { | ||
| 3343 | "node": ">=12" | ||
| 3344 | } | ||
| 3345 | }, | ||
| 3346 | "node_modules/proxy-from-env": { | ||
| 3347 | "version": "1.1.0", | ||
| 3348 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", | ||
| 3349 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", | ||
| 3350 | "license": "MIT" | ||
| 3351 | }, | ||
| 3352 | "node_modules/pump": { | ||
| 3353 | "version": "3.0.2", | ||
| 3354 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", | ||
| 3355 | "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", | ||
| 3356 | "license": "MIT", | ||
| 3357 | "dependencies": { | ||
| 3358 | "end-of-stream": "^1.1.0", | ||
| 3359 | "once": "^1.3.1" | ||
| 3360 | } | ||
| 3361 | }, | ||
| 3362 | "node_modules/puppeteer": { | ||
| 3363 | "version": "24.10.1", | ||
| 3364 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.10.1.tgz", | ||
| 3365 | "integrity": "sha512-7T3rfSaaPt5A31VITV5YKQ4wPCCv4aPn8byDaV+9lhDU9v7BWYY4Ncwerw3ZR5mIolrh/PvzGdIDK7yiBth75g==", | ||
| 3366 | "hasInstallScript": true, | ||
| 3367 | "license": "Apache-2.0", | ||
| 3368 | "dependencies": { | ||
| 3369 | "@puppeteer/browsers": "2.10.5", | ||
| 3370 | "chromium-bidi": "5.1.0", | ||
| 3371 | "cosmiconfig": "^9.0.0", | ||
| 3372 | "devtools-protocol": "0.0.1452169", | ||
| 3373 | "puppeteer-core": "24.10.1", | ||
| 3374 | "typed-query-selector": "^2.12.0" | ||
| 3375 | }, | ||
| 3376 | "bin": { | ||
| 3377 | "puppeteer": "lib/cjs/puppeteer/node/cli.js" | ||
| 3378 | }, | ||
| 3379 | "engines": { | ||
| 3380 | "node": ">=18" | ||
| 3381 | } | ||
| 3382 | }, | ||
| 3383 | "node_modules/puppeteer-core": { | ||
| 3384 | "version": "24.10.1", | ||
| 3385 | "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.10.1.tgz", | ||
| 3386 | "integrity": "sha512-AE6doA9znmEEps/pC5lc9p0zejCdNLR6UBp3EZ49/15Nbvh+uklXxGox7Qh8/lFGqGVwxInl0TXmsOmIuIMwiQ==", | ||
| 3387 | "license": "Apache-2.0", | ||
| 3388 | "dependencies": { | ||
| 3389 | "@puppeteer/browsers": "2.10.5", | ||
| 3390 | "chromium-bidi": "5.1.0", | ||
| 3391 | "debug": "^4.4.1", | ||
| 3392 | "devtools-protocol": "0.0.1452169", | ||
| 3393 | "typed-query-selector": "^2.12.0", | ||
| 3394 | "ws": "^8.18.2" | ||
| 3395 | }, | ||
| 3396 | "engines": { | ||
| 3397 | "node": ">=18" | ||
| 3398 | } | ||
| 3399 | }, | ||
| 2622 | "node_modules/raptor-async": { | 3400 | "node_modules/raptor-async": { |
| 2623 | "version": "1.1.3", | 3401 | "version": "1.1.3", |
| 2624 | "resolved": "https://registry.npmjs.org/raptor-async/-/raptor-async-1.1.3.tgz", | 3402 | "resolved": "https://registry.npmjs.org/raptor-async/-/raptor-async-1.1.3.tgz", |
| ... | @@ -2769,6 +3547,15 @@ | ... | @@ -2769,6 +3547,15 @@ |
| 2769 | "url": "https://opencollective.com/unified" | 3547 | "url": "https://opencollective.com/unified" |
| 2770 | } | 3548 | } |
| 2771 | }, | 3549 | }, |
| 3550 | "node_modules/require-directory": { | ||
| 3551 | "version": "2.1.1", | ||
| 3552 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", | ||
| 3553 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", | ||
| 3554 | "license": "MIT", | ||
| 3555 | "engines": { | ||
| 3556 | "node": ">=0.10.0" | ||
| 3557 | } | ||
| 3558 | }, | ||
| 2772 | "node_modules/resolve-from": { | 3559 | "node_modules/resolve-from": { |
| 2773 | "version": "5.0.0", | 3560 | "version": "5.0.0", |
| 2774 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", | 3561 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", |
| ... | @@ -2796,6 +3583,44 @@ | ... | @@ -2796,6 +3583,44 @@ |
| 2796 | "semver": "bin/semver.js" | 3583 | "semver": "bin/semver.js" |
| 2797 | } | 3584 | } |
| 2798 | }, | 3585 | }, |
| 3586 | "node_modules/smart-buffer": { | ||
| 3587 | "version": "4.2.0", | ||
| 3588 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", | ||
| 3589 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", | ||
| 3590 | "license": "MIT", | ||
| 3591 | "engines": { | ||
| 3592 | "node": ">= 6.0.0", | ||
| 3593 | "npm": ">= 3.0.0" | ||
| 3594 | } | ||
| 3595 | }, | ||
| 3596 | "node_modules/socks": { | ||
| 3597 | "version": "2.8.5", | ||
| 3598 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", | ||
| 3599 | "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", | ||
| 3600 | "license": "MIT", | ||
| 3601 | "dependencies": { | ||
| 3602 | "ip-address": "^9.0.5", | ||
| 3603 | "smart-buffer": "^4.2.0" | ||
| 3604 | }, | ||
| 3605 | "engines": { | ||
| 3606 | "node": ">= 10.0.0", | ||
| 3607 | "npm": ">= 3.0.0" | ||
| 3608 | } | ||
| 3609 | }, | ||
| 3610 | "node_modules/socks-proxy-agent": { | ||
| 3611 | "version": "8.0.5", | ||
| 3612 | "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", | ||
| 3613 | "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", | ||
| 3614 | "license": "MIT", | ||
| 3615 | "dependencies": { | ||
| 3616 | "agent-base": "^7.1.2", | ||
| 3617 | "debug": "^4.3.4", | ||
| 3618 | "socks": "^2.8.3" | ||
| 3619 | }, | ||
| 3620 | "engines": { | ||
| 3621 | "node": ">= 14" | ||
| 3622 | } | ||
| 3623 | }, | ||
| 2799 | "node_modules/source-map": { | 3624 | "node_modules/source-map": { |
| 2800 | "version": "0.7.4", | 3625 | "version": "0.7.4", |
| 2801 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", | 3626 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", |
| ... | @@ -2834,12 +3659,66 @@ | ... | @@ -2834,12 +3659,66 @@ |
| 2834 | "url": "https://github.com/sponsors/wooorm" | 3659 | "url": "https://github.com/sponsors/wooorm" |
| 2835 | } | 3660 | } |
| 2836 | }, | 3661 | }, |
| 3662 | "node_modules/sprintf-js": { | ||
| 3663 | "version": "1.1.3", | ||
| 3664 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", | ||
| 3665 | "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", | ||
| 3666 | "license": "BSD-3-Clause" | ||
| 3667 | }, | ||
| 2837 | "node_modules/stackframe": { | 3668 | "node_modules/stackframe": { |
| 2838 | "version": "1.3.4", | 3669 | "version": "1.3.4", |
| 2839 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | 3670 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", |
| 2840 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | 3671 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", |
| 2841 | "license": "MIT" | 3672 | "license": "MIT" |
| 2842 | }, | 3673 | }, |
| 3674 | "node_modules/streamx": { | ||
| 3675 | "version": "2.22.1", | ||
| 3676 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", | ||
| 3677 | "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", | ||
| 3678 | "license": "MIT", | ||
| 3679 | "dependencies": { | ||
| 3680 | "fast-fifo": "^1.3.2", | ||
| 3681 | "text-decoder": "^1.1.0" | ||
| 3682 | }, | ||
| 3683 | "optionalDependencies": { | ||
| 3684 | "bare-events": "^2.2.0" | ||
| 3685 | } | ||
| 3686 | }, | ||
| 3687 | "node_modules/string-width": { | ||
| 3688 | "version": "4.2.3", | ||
| 3689 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||
| 3690 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | ||
| 3691 | "license": "MIT", | ||
| 3692 | "dependencies": { | ||
| 3693 | "emoji-regex": "^8.0.0", | ||
| 3694 | "is-fullwidth-code-point": "^3.0.0", | ||
| 3695 | "strip-ansi": "^6.0.1" | ||
| 3696 | }, | ||
| 3697 | "engines": { | ||
| 3698 | "node": ">=8" | ||
| 3699 | } | ||
| 3700 | }, | ||
| 3701 | "node_modules/string-width/node_modules/ansi-regex": { | ||
| 3702 | "version": "5.0.1", | ||
| 3703 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||
| 3704 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", | ||
| 3705 | "license": "MIT", | ||
| 3706 | "engines": { | ||
| 3707 | "node": ">=8" | ||
| 3708 | } | ||
| 3709 | }, | ||
| 3710 | "node_modules/string-width/node_modules/strip-ansi": { | ||
| 3711 | "version": "6.0.1", | ||
| 3712 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||
| 3713 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||
| 3714 | "license": "MIT", | ||
| 3715 | "dependencies": { | ||
| 3716 | "ansi-regex": "^5.0.1" | ||
| 3717 | }, | ||
| 3718 | "engines": { | ||
| 3719 | "node": ">=8" | ||
| 3720 | } | ||
| 3721 | }, | ||
| 2843 | "node_modules/stringify-entities": { | 3722 | "node_modules/stringify-entities": { |
| 2844 | "version": "4.0.4", | 3723 | "version": "4.0.4", |
| 2845 | "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", | 3724 | "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", |
| ... | @@ -2887,6 +3766,40 @@ | ... | @@ -2887,6 +3766,40 @@ |
| 2887 | "inline-style-parser": "0.2.4" | 3766 | "inline-style-parser": "0.2.4" |
| 2888 | } | 3767 | } |
| 2889 | }, | 3768 | }, |
| 3769 | "node_modules/tar-fs": { | ||
| 3770 | "version": "3.0.9", | ||
| 3771 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz", | ||
| 3772 | "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==", | ||
| 3773 | "license": "MIT", | ||
| 3774 | "dependencies": { | ||
| 3775 | "pump": "^3.0.0", | ||
| 3776 | "tar-stream": "^3.1.5" | ||
| 3777 | }, | ||
| 3778 | "optionalDependencies": { | ||
| 3779 | "bare-fs": "^4.0.1", | ||
| 3780 | "bare-path": "^3.0.0" | ||
| 3781 | } | ||
| 3782 | }, | ||
| 3783 | "node_modules/tar-stream": { | ||
| 3784 | "version": "3.1.7", | ||
| 3785 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", | ||
| 3786 | "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", | ||
| 3787 | "license": "MIT", | ||
| 3788 | "dependencies": { | ||
| 3789 | "b4a": "^1.6.4", | ||
| 3790 | "fast-fifo": "^1.2.0", | ||
| 3791 | "streamx": "^2.15.0" | ||
| 3792 | } | ||
| 3793 | }, | ||
| 3794 | "node_modules/text-decoder": { | ||
| 3795 | "version": "1.2.3", | ||
| 3796 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", | ||
| 3797 | "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", | ||
| 3798 | "license": "Apache-2.0", | ||
| 3799 | "dependencies": { | ||
| 3800 | "b4a": "^1.6.4" | ||
| 3801 | } | ||
| 3802 | }, | ||
| 2890 | "node_modules/trim-lines": { | 3803 | "node_modules/trim-lines": { |
| 2891 | "version": "3.0.1", | 3804 | "version": "3.0.1", |
| 2892 | "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", | 3805 | "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", |
| ... | @@ -2907,11 +3820,23 @@ | ... | @@ -2907,11 +3820,23 @@ |
| 2907 | "url": "https://github.com/sponsors/wooorm" | 3820 | "url": "https://github.com/sponsors/wooorm" |
| 2908 | } | 3821 | } |
| 2909 | }, | 3822 | }, |
| 3823 | "node_modules/tslib": { | ||
| 3824 | "version": "2.8.1", | ||
| 3825 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", | ||
| 3826 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", | ||
| 3827 | "license": "0BSD" | ||
| 3828 | }, | ||
| 3829 | "node_modules/typed-query-selector": { | ||
| 3830 | "version": "2.12.0", | ||
| 3831 | "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", | ||
| 3832 | "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", | ||
| 3833 | "license": "MIT" | ||
| 3834 | }, | ||
| 2910 | "node_modules/typescript": { | 3835 | "node_modules/typescript": { |
| 2911 | "version": "5.8.3", | 3836 | "version": "5.8.3", |
| 2912 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", | 3837 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", |
| 2913 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", | 3838 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", |
| 2914 | "dev": true, | 3839 | "devOptional": true, |
| 2915 | "license": "Apache-2.0", | 3840 | "license": "Apache-2.0", |
| 2916 | "bin": { | 3841 | "bin": { |
| 2917 | "tsc": "bin/tsc", | 3842 | "tsc": "bin/tsc", |
| ... | @@ -2925,7 +3850,7 @@ | ... | @@ -2925,7 +3850,7 @@ |
| 2925 | "version": "6.21.0", | 3850 | "version": "6.21.0", |
| 2926 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", | 3851 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", |
| 2927 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", | 3852 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", |
| 2928 | "dev": true, | 3853 | "devOptional": true, |
| 2929 | "license": "MIT" | 3854 | "license": "MIT" |
| 2930 | }, | 3855 | }, |
| 2931 | "node_modules/unified": { | 3856 | "node_modules/unified": { |
| ... | @@ -3095,12 +4020,132 @@ | ... | @@ -3095,12 +4020,132 @@ |
| 3095 | "url": "https://opencollective.com/unified" | 4020 | "url": "https://opencollective.com/unified" |
| 3096 | } | 4021 | } |
| 3097 | }, | 4022 | }, |
| 4023 | "node_modules/wrap-ansi": { | ||
| 4024 | "version": "7.0.0", | ||
| 4025 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", | ||
| 4026 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", | ||
| 4027 | "license": "MIT", | ||
| 4028 | "dependencies": { | ||
| 4029 | "ansi-styles": "^4.0.0", | ||
| 4030 | "string-width": "^4.1.0", | ||
| 4031 | "strip-ansi": "^6.0.0" | ||
| 4032 | }, | ||
| 4033 | "engines": { | ||
| 4034 | "node": ">=10" | ||
| 4035 | }, | ||
| 4036 | "funding": { | ||
| 4037 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" | ||
| 4038 | } | ||
| 4039 | }, | ||
| 4040 | "node_modules/wrap-ansi/node_modules/ansi-regex": { | ||
| 4041 | "version": "5.0.1", | ||
| 4042 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||
| 4043 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", | ||
| 4044 | "license": "MIT", | ||
| 4045 | "engines": { | ||
| 4046 | "node": ">=8" | ||
| 4047 | } | ||
| 4048 | }, | ||
| 4049 | "node_modules/wrap-ansi/node_modules/strip-ansi": { | ||
| 4050 | "version": "6.0.1", | ||
| 4051 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||
| 4052 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||
| 4053 | "license": "MIT", | ||
| 4054 | "dependencies": { | ||
| 4055 | "ansi-regex": "^5.0.1" | ||
| 4056 | }, | ||
| 4057 | "engines": { | ||
| 4058 | "node": ">=8" | ||
| 4059 | } | ||
| 4060 | }, | ||
| 4061 | "node_modules/wrappy": { | ||
| 4062 | "version": "1.0.2", | ||
| 4063 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | ||
| 4064 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", | ||
| 4065 | "license": "ISC" | ||
| 4066 | }, | ||
| 4067 | "node_modules/ws": { | ||
| 4068 | "version": "8.18.2", | ||
| 4069 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", | ||
| 4070 | "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", | ||
| 4071 | "license": "MIT", | ||
| 4072 | "engines": { | ||
| 4073 | "node": ">=10.0.0" | ||
| 4074 | }, | ||
| 4075 | "peerDependencies": { | ||
| 4076 | "bufferutil": "^4.0.1", | ||
| 4077 | "utf-8-validate": ">=5.0.2" | ||
| 4078 | }, | ||
| 4079 | "peerDependenciesMeta": { | ||
| 4080 | "bufferutil": { | ||
| 4081 | "optional": true | ||
| 4082 | }, | ||
| 4083 | "utf-8-validate": { | ||
| 4084 | "optional": true | ||
| 4085 | } | ||
| 4086 | } | ||
| 4087 | }, | ||
| 4088 | "node_modules/y18n": { | ||
| 4089 | "version": "5.0.8", | ||
| 4090 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", | ||
| 4091 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", | ||
| 4092 | "license": "ISC", | ||
| 4093 | "engines": { | ||
| 4094 | "node": ">=10" | ||
| 4095 | } | ||
| 4096 | }, | ||
| 3098 | "node_modules/yallist": { | 4097 | "node_modules/yallist": { |
| 3099 | "version": "3.1.1", | 4098 | "version": "3.1.1", |
| 3100 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", | 4099 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", |
| 3101 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", | 4100 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", |
| 3102 | "license": "ISC" | 4101 | "license": "ISC" |
| 3103 | }, | 4102 | }, |
| 4103 | "node_modules/yargs": { | ||
| 4104 | "version": "17.7.2", | ||
| 4105 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", | ||
| 4106 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", | ||
| 4107 | "license": "MIT", | ||
| 4108 | "dependencies": { | ||
| 4109 | "cliui": "^8.0.1", | ||
| 4110 | "escalade": "^3.1.1", | ||
| 4111 | "get-caller-file": "^2.0.5", | ||
| 4112 | "require-directory": "^2.1.1", | ||
| 4113 | "string-width": "^4.2.3", | ||
| 4114 | "y18n": "^5.0.5", | ||
| 4115 | "yargs-parser": "^21.1.1" | ||
| 4116 | }, | ||
| 4117 | "engines": { | ||
| 4118 | "node": ">=12" | ||
| 4119 | } | ||
| 4120 | }, | ||
| 4121 | "node_modules/yargs-parser": { | ||
| 4122 | "version": "21.1.1", | ||
| 4123 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", | ||
| 4124 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", | ||
| 4125 | "license": "ISC", | ||
| 4126 | "engines": { | ||
| 4127 | "node": ">=12" | ||
| 4128 | } | ||
| 4129 | }, | ||
| 4130 | "node_modules/yauzl": { | ||
| 4131 | "version": "2.10.0", | ||
| 4132 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", | ||
| 4133 | "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", | ||
| 4134 | "license": "MIT", | ||
| 4135 | "dependencies": { | ||
| 4136 | "buffer-crc32": "~0.2.3", | ||
| 4137 | "fd-slicer": "~1.1.0" | ||
| 4138 | } | ||
| 4139 | }, | ||
| 4140 | "node_modules/zod": { | ||
| 4141 | "version": "3.25.64", | ||
| 4142 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz", | ||
| 4143 | "integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==", | ||
| 4144 | "license": "MIT", | ||
| 4145 | "funding": { | ||
| 4146 | "url": "https://github.com/sponsors/colinhacks" | ||
| 4147 | } | ||
| 4148 | }, | ||
| 3104 | "node_modules/zwitch": { | 4149 | "node_modules/zwitch": { |
| 3105 | "version": "2.0.4", | 4150 | "version": "2.0.4", |
| 3106 | "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", | 4151 | "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", |
package.json+2| ... | @@ -8,6 +8,7 @@ | ... | @@ -8,6 +8,7 @@ |
| 8 | "hls.js": "^1.6.5", | 8 | "hls.js": "^1.6.5", |
| 9 | "hono": "^4.7.11", | 9 | "hono": "^4.7.11", |
| 10 | "marko": "^6.0.20", | 10 | "marko": "^6.0.20", |
| 11 | "puppeteer": "^24.10.1", | ||
| 11 | "unique-names-generator": "^4.7.1" | 12 | "unique-names-generator": "^4.7.1" |
| 12 | }, | 13 | }, |
| 13 | "devDependencies": { | 14 | "devDependencies": { |
| ... | @@ -16,6 +17,7 @@ | ... | @@ -16,6 +17,7 @@ |
| 16 | }, | 17 | }, |
| 17 | "imports": { | 18 | "imports": { |
| 18 | "#backend": "./src/backend.ts", | 19 | "#backend": "./src/backend.ts", |
| 20 | "#debug": "./framework/debug.safe.ts", | ||
| 19 | "#sitegen": "./framework/lib/sitegen.ts", | 21 | "#sitegen": "./framework/lib/sitegen.ts", |
| 20 | "#sitegen/*": "./framework/lib/*.ts", | 22 | "#sitegen/*": "./framework/lib/*.ts", |
| 21 | "#ssr": "./framework/engine/ssr.ts", | 23 | "#ssr": "./framework/engine/ssr.ts", |
readme.md+13-10| ... | @@ -3,26 +3,29 @@ | ... | @@ -3,26 +3,29 @@ |
| 3 | this repository contains clover's "sitegen" framework, which is a set of tools | 3 | this repository contains clover's "sitegen" framework, which is a set of tools |
| 4 | that assist building websites. these tools power https://paperclover.net. | 4 | that assist building websites. these tools power https://paperclover.net. |
| 5 | 5 | ||
| 6 | - HTML "Server Side Rendering") engine written from scratch. | 6 | - **HTML "Server Side Rendering") engine written from scratch.** (~500 lines) |
| 7 | - A more practical JSX runtime (`class` instead of `className`, etc). | 7 | - A more practical JSX runtime (`class` instead of `className`, built-in |
| 8 | - Transparent integration with [Marko][1] to mix component types. | 8 | `clsx`, `html()` helper over `dangerouslySetInnerHTML` prop, etc). |
| 9 | - MDX support for text-heavy content pages. | 9 | - Integration with [Marko][1] for concisely written components. |
| 10 | - Incremental static site generator and build system | 10 | - TODO: MDX-like compiler for content-heavy pages like blogs. |
| 11 | - Different languages can be used at the same time. Supports | ||
| 12 | `async function` components, `<Suspense />`, and custom extensions. | ||
| 13 | - **Incremental static site generator and build system.** | ||
| 11 | - Build entire production site at start, incremental updates when pages | 14 | - Build entire production site at start, incremental updates when pages |
| 12 | change; Build system state survives coding sessions. | 15 | change; Build system state survives coding sessions. |
| 13 | - The only difference in development and production mode is hidden | 16 | - The only difference in development and production mode is hidden |
| 14 | source-maps and stripped assertions and `console.debug` calls. The site | 17 | source-maps and stripped `console.debug` calls. The site you |
| 15 | you see locally is the site you see deployed. | 18 | see locally is the same site you see deployed. |
| 16 | - (TODO) Tests, Lints, and Type-checking is run alongside, and only re-runs | 19 | - (TODO) Tests, Lints, and Type-checking is run alongside, and only re-runs |
| 17 | checks when the files change. For example, changing a component re-tests | 20 | checks when the files change. For example, changing a component re-tests |
| 18 | only pages that use that component and re-lints only the changed file. | 21 | only pages that use that component and re-lints only the changed file. |
| 19 | - Integrated libraries for building complex, content heavy web sites. | 22 | - **Integrated libraries for building complex, content heavy web sites.** |
| 20 | - Static asset serving with ETag and build-time compression. | 23 | - Static asset serving with ETag and build-time compression. |
| 21 | - Dynamicly rendered pages with static client. (`#import "#sitegen/view"`) | 24 | - Dynamicly rendered pages with static client. (`#import "#sitegen/view"`) |
| 22 | - Databases with a typed SQLite wrapper. (`import "#sitegen/sqlite"`) | 25 | - Databases with a typed SQLite wrapper. (`import "#sitegen/sqlite"`) |
| 23 | - TODO: Meta and Open Graph generation. (`export const meta`) | 26 | - TODO: Meta and Open Graph generation. (`export const meta`) |
| 24 | - TODO: Font subsetting tools to reduce | 27 | - TODO: Font subsetting tools to reduce bytes downloaded by fonts. |
| 25 | - Built on the battle-tested Node.js runtime. Partial support for Deno and Bun. | 28 | - **Built on the battle-tested Node.js runtime.** Partial support for Deno and Bun. |
| 26 | 29 | ||
| 27 | [1]: https://next.markojs.com | 30 | [1]: https://next.markojs.com |
| 28 | 31 |
run.js+13-26| ... | @@ -4,18 +4,17 @@ import * as util from "node:util"; | ... | @@ -4,18 +4,17 @@ import * as util from "node:util"; |
| 4 | import process from "node:process"; | 4 | import process from "node:process"; |
| 5 | 5 | ||
| 6 | // Disable experimental warnings (Type Stripping, etc) | 6 | // Disable experimental warnings (Type Stripping, etc) |
| 7 | { | 7 | const { emit: originalEmit } = process; |
| 8 | const { emit: originalEmit } = process; | 8 | const warnings = ["ExperimentalWarning"]; |
| 9 | const warnings = ["ExperimentalWarning"]; | 9 | process.emit = function (event, error) { |
| 10 | process.emit = function (event, error) { | 10 | return event === "warning" && warnings.includes(error.name) |
| 11 | return event === "warning" && warnings.includes(error.name) | 11 | ? false |
| 12 | ? false | 12 | : originalEmit.apply(process, arguments); |
| 13 | : originalEmit.apply(process, arguments); | 13 | }; |
| 14 | }; | ||
| 15 | } | ||
| 16 | 14 | ||
| 17 | // Init hooks | 15 | // Init hooks |
| 18 | const hot = await import("./framework/hot.ts"); | 16 | const hot = await import("./framework/hot.ts"); |
| 17 | await import("#debug"); | ||
| 19 | 18 | ||
| 20 | const console = hot.load("@paperclover/console"); | 19 | const console = hot.load("@paperclover/console"); |
| 21 | globalThis.console["log"] = console.info; | 20 | globalThis.console["log"] = console.info; |
| ... | @@ -24,22 +23,6 @@ globalThis.console.warn = console.warn; | ... | @@ -24,22 +23,6 @@ globalThis.console.warn = console.warn; |
| 24 | globalThis.console.error = console.error; | 23 | globalThis.console.error = console.error; |
| 25 | globalThis.console.debug = console.scoped("dbg"); | 24 | globalThis.console.debug = console.scoped("dbg"); |
| 26 | 25 | ||
| 27 | globalThis.UNWRAP = (t, ...args) => { | ||
| 28 | if (t == null) { | ||
| 29 | throw new Error( | ||
| 30 | args.length > 0 ? util.format(...args) : "UNWRAP(" + t + ")", | ||
| 31 | ); | ||
| 32 | } | ||
| 33 | return t; | ||
| 34 | }; | ||
| 35 | globalThis.ASSERT = (t, ...args) => { | ||
| 36 | if (!t) { | ||
| 37 | throw new Error( | ||
| 38 | args.length > 0 ? util.format(...args) : "Assertion Failed", | ||
| 39 | ); | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | |||
| 43 | // Load with hooks | 26 | // Load with hooks |
| 44 | if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) { | 27 | if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) { |
| 45 | if (process.argv.length == 2) { | 28 | if (process.argv.length == 2) { |
| ... | @@ -60,7 +43,11 @@ if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) { | ... | @@ -60,7 +43,11 @@ if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) { |
| 60 | process.exit(1); | 43 | process.exit(1); |
| 61 | } | 44 | } |
| 62 | process.argv = [process.argv[0], ...process.argv.slice(2)]; | 45 | process.argv = [process.argv[0], ...process.argv.slice(2)]; |
| 63 | hot.load(found).main?.(); | 46 | try { |
| 47 | await hot.load(found).main?.(); | ||
| 48 | } catch (e) { | ||
| 49 | console.error(util.inspect(e)); | ||
| 50 | } | ||
| 64 | } | 51 | } |
| 65 | 52 | ||
| 66 | export { hot }; | 53 | export { hot }; |
src/admin.ts+12-1| ... | @@ -59,6 +59,17 @@ export function hasAdminToken(c: Context) { | ... | @@ -59,6 +59,17 @@ export function hasAdminToken(c: Context) { |
| 59 | return token && compareToken(token); | 59 | return token && compareToken(token); |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | import * as fs from "node:fs"; | 62 | export async function main() { |
| 63 | const key = crypto.randomUUID(); | ||
| 64 | await fs.writeMkdir(".clover/admin-token.txt", key); | ||
| 65 | const start = ({ | ||
| 66 | win32: "start", | ||
| 67 | darwin: "open", | ||
| 68 | } as Record<string, string>)[process.platform] ?? "xdg-open"; | ||
| 69 | child_process.exec(`${start} http://[::1]:3000/admin/login?key=${key}`); | ||
| 70 | } | ||
| 71 | |||
| 72 | import * as fs from "#sitegen/fs"; | ||
| 63 | import type { Context, Next } from "hono"; | 73 | import type { Context, Next } from "hono"; |
| 64 | import { serveAsset } from "#sitegen/assets"; | 74 | import { serveAsset } from "#sitegen/assets"; |
| 75 | import * as child_process from "node:child_process"; |
src/backend.ts+13| ... | @@ -15,6 +15,17 @@ app.route("", require("./q+a/backend.ts").app); | ... | @@ -15,6 +15,17 @@ app.route("", require("./q+a/backend.ts").app); |
| 15 | 15 | ||
| 16 | app.use(assets.middleware); | 16 | app.use(assets.middleware); |
| 17 | 17 | ||
| 18 | if (process.argv.includes("--development")) { | ||
| 19 | app.onError((err, c) => { | ||
| 20 | if (err instanceof HTTPException) { | ||
| 21 | // Get the custom response | ||
| 22 | return err.getResponse(); | ||
| 23 | } | ||
| 24 | |||
| 25 | return c.text(util.inspect(err), 500); | ||
| 26 | }); | ||
| 27 | } | ||
| 28 | |||
| 18 | export default app; | 29 | export default app; |
| 19 | 30 | ||
| 20 | async function removeDuplicateSlashes(c: Context, next: Next) { | 31 | async function removeDuplicateSlashes(c: Context, next: Next) { |
| ... | @@ -31,8 +42,10 @@ async function removeDuplicateSlashes(c: Context, next: Next) { | ... | @@ -31,8 +42,10 @@ async function removeDuplicateSlashes(c: Context, next: Next) { |
| 31 | } | 42 | } |
| 32 | 43 | ||
| 33 | import { type Context, Hono, type Next } from "#hono"; | 44 | import { type Context, Hono, type Next } from "#hono"; |
| 45 | import { HTTPException } from "hono/http-exception"; | ||
| 34 | import { logger } from "hono/logger"; | 46 | import { logger } from "hono/logger"; |
| 35 | import { trimTrailingSlash } from "hono/trailing-slash"; | 47 | import { trimTrailingSlash } from "hono/trailing-slash"; |
| 36 | import * as assets from "#sitegen/assets"; | 48 | import * as assets from "#sitegen/assets"; |
| 37 | import * as admin from "./admin.ts"; | 49 | import * as admin from "./admin.ts"; |
| 38 | import { scoped } from "@paperclover/console"; | 50 | import { scoped } from "@paperclover/console"; |
| 51 | import * as util from "node:util"; |
src/blog/pages/25/marko-intro.markodown created+283| ... | @@ -0,0 +1,283 @@ | ||
| 1 | export const blog: BlogMeta = { | ||
| 2 | title: "Marko is the coziest HTML templating language", | ||
| 3 | desc: "...todo...", | ||
| 4 | date: "2025-06-13", | ||
| 5 | draft: true, | ||
| 6 | }; | ||
| 7 | export const meta = formatBlogMeta(blob); | ||
| 8 | |||
| 9 | I've been recently playing around [Marko][1], and after adding limited support | ||
| 10 | for it in my website generator, [sitegen][2], I instantly fell in love with how | ||
| 11 | minimalistic it is in comparison to JSX, Astro components, and Svelte. | ||
| 12 | |||
| 13 | ## Introduction | ||
| 14 | |||
| 15 | If JSX was taking HTML and shoving its syntax into JavaScript, Marko is shoving | ||
| 16 | JavaScript into HTML. Attributes are JavaScript expressions. | ||
| 17 | |||
| 18 | ```marko | ||
| 19 | <div> | ||
| 20 | // `input` is like props, but in global scope | ||
| 21 | <time datetime=input.date.toISOString()> | ||
| 22 | // Interpolation with JS template string syntax | ||
| 23 | ${formatTimeNicely(input.date)} | ||
| 24 | </time> | ||
| 25 | <div> | ||
| 26 | <a href=`/users/${input.user.id}`>${input.user.name}</a> | ||
| 27 | </div> | ||
| 28 | |||
| 29 | // Capital letter variables for imported components | ||
| 30 | <MarkdownContent message=input.message /> | ||
| 31 | </div> | ||
| 32 | |||
| 33 | // ESM `import` / `export` just work as expected. | ||
| 34 | // I prefer my imports at the end, to highlight the markup. | ||
| 35 | import MarkdownContent from "./MarkdownContent.marko"; | ||
| 36 | import { formatTimeNicely } from "../date-helpers.ts"; | ||
| 37 | ``` | ||
| 38 | |||
| 39 | Tags with the `value` attribute have a shorthand, which is used by the built-in | ||
| 40 | `<if>` for conditional rendering. | ||
| 41 | |||
| 42 | ```marko | ||
| 43 | // Sugar for <input value="string" /> | ||
| 44 | <input="string" /> | ||
| 45 | |||
| 46 | // and it composes amazingly to the 'if' built-in | ||
| 47 | <if=input.user> | ||
| 48 | <UserProfile=input.user /> | ||
| 49 | </if> | ||
| 50 | ``` | ||
| 51 | |||
| 52 | Tags can also return values into the scope for use in the template using `/`, such as `<id>` for unique ID generation. This is available to components that `<return=output/>`. | ||
| 53 | |||
| 54 | ``` | ||
| 55 | <id/uniqueId /> | ||
| 56 | |||
| 57 | <input id=uniqueId type="checkbox" name="allow_trans_rights" /> | ||
| 58 | <label for=uniqueId>click me!</> | ||
| 59 | // ^ oh, you can also omit the | ||
| 60 | // closing tag name if you want. | ||
| 61 | ``` | ||
| 62 | |||
| 63 | It's important that I started with the two forms of "Tag I/O": `=` for input | ||
| 64 | and `/` for output. With those building blocks, we introduce local variables | ||
| 65 | with `const` | ||
| 66 | |||
| 67 | ``` | ||
| 68 | <const/rendered = markdownToHtml(input.value) /> | ||
| 69 | |||
| 70 | // This is how you insert raw HTML to the document | ||
| 71 | <inline-html=rendered /> | ||
| 72 | |||
| 73 | // It supports all of the cozy destructuring syntax JS has | ||
| 74 | <const/{ id, name } = user /> | ||
| 75 | ``` | ||
| 76 | |||
| 77 | Unlike JSX, when you pass content within a tag (`input.content` instead of | ||
| 78 | JSX's `children`), instead of it being a JSX element, it is actually a | ||
| 79 | function. This means that the `for` tag can render the content multiple times. | ||
| 80 | |||
| 81 | ``` | ||
| 82 | <ul> | ||
| 83 | <for from=1 to=10> | ||
| 84 | // Renders a new random number for each iteration. | ||
| 85 | <li>${Math.random()}</li> | ||
| 86 | </> | ||
| 87 | </ul> | ||
| 88 | ``` | ||
| 89 | |||
| 90 | Since `content` is a function, it can take arguments. This is done with `|` | ||
| 91 | |||
| 92 | ``` | ||
| 93 | <h1>my friends</h1> | ||
| 94 | <ul> | ||
| 95 | // I tend to omit the closing tag names for the built-in control | ||
| 96 | // flow tags, but I keep them for HTML tags. It's kinda like how | ||
| 97 | // in JavaScript you just write `}` to close your `if`s and loops. | ||
| 98 | // | ||
| 99 | // Anyways <for> also has 'of' | ||
| 100 | <for|item| of=user.friends> | ||
| 101 | <li class="friend">${item.name}</li> | ||
| 102 | </> | ||
| 103 | |||
| 104 | // They support the same syntax JavaScript function params allows, | ||
| 105 | // so you can have destructuring here too, and multiple params. | ||
| 106 | <for|{ name }, index| of=user.friends> | ||
| 107 | // By the way you can also use emmet-style class and ID shorthands. | ||
| 108 | <li.friend>My #${index + 1} friend is ${name}</li> | ||
| 109 | </> | ||
| 110 | </ul> | ||
| 111 | ``` | ||
| 112 | |||
| 113 | Instead of named slots, Marko has attribute tags. These are more powerful than | ||
| 114 | slots since they are functions, and can also act as sugar for more complicated | ||
| 115 | attributes. | ||
| 116 | |||
| 117 | ``` | ||
| 118 | <Layout title="Welcome"> | ||
| 119 | <@header variant="big"> | ||
| 120 | <h1>the next big thing</h1> | ||
| 121 | </@header> | ||
| 122 | |||
| 123 | <p>body text...</p> | ||
| 124 | </Layout> | ||
| 125 | |||
| 126 | // The `input` variable inside of <Layout /> is: | ||
| 127 | // | ||
| 128 | // { | ||
| 129 | // title: "Welcome", | ||
| 130 | // header: { | ||
| 131 | // content: /* function rendering "<h1>the next big thing</h1>" */, | ||
| 132 | // variant: "big", | ||
| 133 | // }, | ||
| 134 | // content: /* function rendering "<p>body text</p>" */ | ||
| 135 | // } | ||
| 136 | ``` | ||
| 137 | |||
| 138 | This layout could be implemented as such: | ||
| 139 | |||
| 140 | ```marko | ||
| 141 | <main> | ||
| 142 | <if=input.header /> | ||
| 143 | <const/{ ...headerProps, content }=input.header /> | ||
| 144 | <header ...headerProps> | ||
| 145 | // Instead of assigning to a variable with a capital letter, | ||
| 146 | // template interpolation works on tag names. This can also | ||
| 147 | // be a string to render the native HTML tag of that kind. | ||
| 148 | <${content} /> | ||
| 149 | </header> | ||
| 150 | <hr /> | ||
| 151 | </> | ||
| 152 | |||
| 153 | <${input.content} /> | ||
| 154 | </main> | ||
| 155 | ``` | ||
| 156 | |||
| 157 | The last syntax feature missing is calling a tag with parameters. That is done | ||
| 158 | just like a regular function call, with '('. | ||
| 159 | |||
| 160 | ``` | ||
| 161 | <Something(item, index) /> | ||
| 162 | ``` | ||
| 163 | |||
| 164 | In fact, attributes can just be sugar over this syntax; _this technically isn't | ||
| 165 | true but it's close enough for the example_ | ||
| 166 | |||
| 167 | ``` | ||
| 168 | <SpecialButton type="submit" class="red" /> | ||
| 169 | |||
| 170 | // is equal to | ||
| 171 | |||
| 172 | <SpecialButton({ type: "submit", class: "red" }) /> | ||
| 173 | ``` | ||
| 174 | |||
| 175 | All of the above is about how Marko's syntax works, and how it performs HTML | ||
| 176 | generation with components. Marko also allows interactive components, but an | ||
| 177 | explaination of that is beyond the scope of this page, mostly since I have not | ||
| 178 | used it. A brief example of it, modified from their documentation. | ||
| 179 | |||
| 180 | ```marko | ||
| 181 | // Reactive variables with <let/> just work... | ||
| 182 | <let/basicCounter=0 /> | ||
| 183 | <button onClick() { basicCounter += 1 }>${basicCounter}</button> | ||
| 184 | // ...but a counter is boring. | ||
| 185 | |||
| 186 | <let/todos=[ | ||
| 187 | { id: 0, text: "Learn Marko" }, | ||
| 188 | { id: 1, text: "Make a Website" }, | ||
| 189 | ]/> | ||
| 190 | |||
| 191 | // 'by' is like React JSX's "key" property, but it's optional. | ||
| 192 | <ul><for|todo, i| of=todos by=(todo => todo.id)> | ||
| 193 | <li.todo> | ||
| 194 | // this variable remains stable even if the list | ||
| 195 | // re-orders, because 'by' was specified. | ||
| 196 | <let/done=false/> | ||
| 197 | <label> | ||
| 198 | <span>${todo.text}</span> | ||
| 199 | // ':=' creates a two-way reactive binding, | ||
| 200 | // (it passes a callback for `checkedChanged`) | ||
| 201 | <input type="checkbox" checked:=done /> | ||
| 202 | </label> | ||
| 203 | <button | ||
| 204 | title="delete" | ||
| 205 | disabled=!done | ||
| 206 | onClick() { | ||
| 207 | todos = todos.toSpliced(i, 1); | ||
| 208 | } | ||
| 209 | > &times; </button> | ||
| 210 | </li> | ||
| 211 | </></ul> | ||
| 212 | |||
| 213 | // Form example | ||
| 214 | <let/nextId=2/> | ||
| 215 | <form onSubmit(e) { | ||
| 216 | e.preventDefault(); | ||
| 217 | todos = todos.concat({ | ||
| 218 | id: nextId++, | ||
| 219 | // HTMLFormElement exposes all its named input | ||
| 220 | // elements as extra properties on the object. | ||
| 221 | text: e.target.text.value, | ||
| 222 | }); | ||
| 223 | // And you can clear it with 'reset()' | ||
| 224 | e.target.reset(); | ||
| 225 | }> | ||
| 226 | // We don't 'onChange' like a React loser. The form | ||
| 227 | // value can be read in the submit event like normal. | ||
| 228 | <input name="text" placeholder="Another Item"> | ||
| 229 | <button type="submit">Add</button> | ||
| 230 | </form> | ||
| 231 | ``` | ||
| 232 | |||
| 233 | ## Usage on `paperclover.net` | ||
| 234 | |||
| 235 | TODO: document a lot of feedback, how i embedded Marko | ||
| 236 | |||
| 237 | My website uses statically generated HTML. That is why I have not needed to use | ||
| 238 | reactive variables. My generator doesn't even try compiling components | ||
| 239 | client-side. | ||
| 240 | |||
| 241 | Here is the actual component used to render [questions on the clover q+a][/q+a]. | ||
| 242 | |||
| 243 | ```marko | ||
| 244 | // Renders a `Question` entry including its markdown body. | ||
| 245 | export interface Input { | ||
| 246 | question: Question; | ||
| 247 | admin?: boolean; | ||
| 248 | } | ||
| 249 | |||
| 250 | // 2024-12-31 05:00:00 EST | ||
| 251 | export const transitionDate = 1735639200000; | ||
| 252 | |||
| 253 | <const/{ question, admin } = input /> | ||
| 254 | <const/{ id, date, text } = question/> | ||
| 255 | |||
| 256 | <${"e-"} | ||
| 257 | f=(date > transitionDate ? true : undefined) | ||
| 258 | id=admin ? `q${id}` : undefined | ||
| 259 | > | ||
| 260 | <if=admin> | ||
| 261 | <a | ||
| 262 | style="margin-right: 0.5rem" | ||
| 263 | href=`/admin/q+a/${id}` | ||
| 264 | >[EDIT]</a> | ||
| 265 | </> | ||
| 266 | <a> | ||
| 267 | <time | ||
| 268 | datetime=formatQuestionISOTimestamp(date) | ||
| 269 | >${formatQuestionTimestamp(date)}</time> | ||
| 270 | </a> | ||
| 271 | |||
| 272 | <CloverMarkdown ...{ text } /> | ||
| 273 | </> | ||
| 274 | |||
| 275 | // this singleton script will make all the '<time>' tags clickable. | ||
| 276 | client import "./clickable-links.client.ts"; | ||
| 277 | |||
| 278 | import type { Question } from "@/q+a/models/Question.ts"; | ||
| 279 | import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts"; | ||
| 280 | import { CloverMarkdown } from "@/q+a/clover-markdown.tsx"; | ||
| 281 | ``` | ||
| 282 | |||
| 283 | import { type BlogMeta, formatBlogMeta } from '@/blog/helpers.ts'; | ||
src/components/Video.tsx deleted-57| ... | @@ -1,57 +0,0 @@ | ||
| 1 | import * as path from "node:path"; | ||
| 2 | import { addScript } from "#sitegen"; | ||
| 3 | import { PrecomputedBlurhash } from "./blurhash.tsx"; | ||
| 4 | import "./Video.css"; | ||
| 5 | export namespace Video { | ||
| 6 | export interface Props { | ||
| 7 | title: string; | ||
| 8 | width: number; | ||
| 9 | height: number; | ||
| 10 | sources: string[]; | ||
| 11 | downloads: string[]; | ||
| 12 | poster?: string; | ||
| 13 | posterHash?: string; | ||
| 14 | borderless?: boolean; | ||
| 15 | } | ||
| 16 | } | ||
| 17 | function PrecomputedBlurhash({ hash }: { hash: string }) { | ||
| 18 | export function Video( | ||
| 19 | { title, sources, height, poster, posterHash, width, borderless }: | ||
| 20 | Video.Props, | ||
| 21 | ) { | ||
| 22 | addScript("./video.client.ts"); | ||
| 23 | return ( | ||
| 24 | <figure class={`video ${borderless ? "borderless" : ""}`}> | ||
| 25 | <figcaption>{title}</figcaption> | ||
| 26 | {/* posterHash && <PrecomputedBlurhash hash={posterHash} /> */} | ||
| 27 | {poster && <img src={poster} alt="waterfalls" />} | ||
| 28 | <video | ||
| 29 | controls | ||
| 30 | preload="none" | ||
| 31 | style={`width:100%;background:transparent;aspect-ratio:${ | ||
| 32 | simplifyFraction(width, height) | ||
| 33 | }`} | ||
| 34 | poster="data:null" | ||
| 35 | > | ||
| 36 | {sources.map((src) => ( | ||
| 37 | <source | ||
| 38 | src={src} | ||
| 39 | type={contentTypeFromExt(src)} | ||
| 40 | /> | ||
| 41 | ))} | ||
| 42 | </video> | ||
| 43 | </figure> | ||
| 44 | ); | ||
| 45 | } | ||
| 46 | export function contentTypeFromExt(src: string) { | ||
| 47 | if (src.endsWith(".m3u8")) return "application/x-mpegURL"; | ||
| 48 | if (src.endsWith(".webm")) return "video/webm"; | ||
| 49 | if (src.endsWith(".mp4")) return "video/mp4"; | ||
| 50 | if (src.endsWith(".ogg")) return "video/ogg"; | ||
| 51 | throw new Error("Unknown video extension: " + path.extname(src)); | ||
| 52 | } | ||
| 53 | const gcd = (a: number, b: number): number => b ? gcd(b, a % b) : a; | ||
| 54 | function simplifyFraction(n: number, d: number) { | ||
| 55 | const divisor = gcd(n, d); | ||
| 56 | return `${n / divisor}/${d / divisor}`; | ||
| 57 | } | ||
src/components/video.client.ts deleted-29| ... | @@ -1,29 +0,0 @@ | ||
| 1 | import type Hls from "hls.js"; | ||
| 2 | |||
| 3 | let cachedHls: null | Promise<typeof Hls | false> | typeof Hls | false = null; | ||
| 4 | |||
| 5 | const loadHls = () => | ||
| 6 | cachedHls ?? (cachedHls = ( | ||
| 7 | // @ts-ignore | ||
| 8 | +"HLS POLYFILL", import("hls.js") | ||
| 9 | ).then((m, y = m.default) => cachedHls = y.isSupported() && y)); | ||
| 10 | |||
| 11 | const createVideoPlayer = async (videoElement: HTMLVideoElement) => { | ||
| 12 | const sources = videoElement.querySelectorAll("source"); | ||
| 13 | for (let i = 0; i < sources.length; i++) { | ||
| 14 | const { src, type } = sources[i]; | ||
| 15 | if (videoElement.canPlayType(type)) { | ||
| 16 | videoElement.src = src; | ||
| 17 | return; | ||
| 18 | } | ||
| 19 | |||
| 20 | if (type === "application/x-mpegURL" && await loadHls()) { | ||
| 21 | const hls = new (cachedHls as typeof Hls)(); | ||
| 22 | hls.loadSource(src); | ||
| 23 | hls.attachMedia(videoElement); | ||
| 24 | return; | ||
| 25 | } | ||
| 26 | } | ||
| 27 | }; | ||
| 28 | |||
| 29 | document.querySelectorAll("video").forEach(createVideoPlayer); | ||
src/pages/404.mdx deleted-6| ... | @@ -1,6 +0,0 @@ | ||
| 1 | export const meta = { title: 'oh no,,,' }; | ||
| 2 | |||
| 3 | # oh dear | ||
| 4 | |||
| 5 | sound the alarms | ||
| 6 | |||
src/q+a/backend.ts+3-3| ... | @@ -104,12 +104,12 @@ async function sendSuccess(c: Context, date: Date) { | ... | @@ -104,12 +104,12 @@ async function sendSuccess(c: Context, date: Date) { |
| 104 | id: formatQuestionId(date), | 104 | id: formatQuestionId(date), |
| 105 | }, { status: 200 }); | 105 | }, { status: 200 }); |
| 106 | } | 106 | } |
| 107 | c.res = await renderView(c, "qa_success", { | 107 | c.res = await renderView(c, "q+a/success", { |
| 108 | permalink: `https://paperclover.net/q+a/${formatQuestionId(date)}`, | 108 | permalink: `https://paperclover.net/q+a/${formatQuestionId(date)}`, |
| 109 | }); | 109 | }); |
| 110 | } | 110 | } |
| 111 | // Question Permalink | 111 | // Question Permalink |
| 112 | app.get("/q+a/:id", (c, next) => { | 112 | app.get("/q+a/:id", async (c, next) => { |
| 113 | // from deadname era, the seconds used to be in the url. | 113 | // from deadname era, the seconds used to be in the url. |
| 114 | // this was removed so that the url can be crafted by hand. | 114 | // this was removed so that the url can be crafted by hand. |
| 115 | let id = c.req.param("id"); | 115 | let id = c.req.param("id"); |
| ... | @@ -138,7 +138,7 @@ app.get("/admin/q+a", async (c) => { | ... | @@ -138,7 +138,7 @@ app.get("/admin/q+a", async (c) => { |
| 138 | return serveAsset(c, "/admin/q+a", 200); | 138 | return serveAsset(c, "/admin/q+a", 200); |
| 139 | }); | 139 | }); |
| 140 | app.get("/admin/q+a/inbox", async (c) => { | 140 | app.get("/admin/q+a/inbox", async (c) => { |
| 141 | return renderView(c, "qa_backend_inbox", {}); | 141 | return renderView(c, "q+a/backend-inbox", {}); |
| 142 | }); | 142 | }); |
| 143 | app.delete("/admin/q+a/:id", async (c, next) => { | 143 | app.delete("/admin/q+a/:id", async (c, next) => { |
| 144 | const id = c.req.param("id"); | 144 | const id = c.req.param("id"); |
src/q+a/components/Question.marko deleted-37| ... | @@ -1,37 +0,0 @@ | ||
| 1 | // Renders a `Question` entry including its markdown body. | ||
| 2 | export interface Input { | ||
| 3 | question: Question; | ||
| 4 | admin?: boolean; | ||
| 5 | } | ||
| 6 | |||
| 7 | // 2024-12-31 05:00:00 EST | ||
| 8 | static const transitionDate = 1735639200000; | ||
| 9 | |||
| 10 | <const/{ question, admin } = input /> | ||
| 11 | <const/{ id, date } = question/> | ||
| 12 | |||
| 13 | <${"e-"} | ||
| 14 | f=(date > transitionDate ? true : undefined) | ||
| 15 | id=admin ? `q${id}` : undefined | ||
| 16 | > | ||
| 17 | <if=admin> | ||
| 18 | <a | ||
| 19 | style="margin-right: 0.5rem" | ||
| 20 | href=`/admin/q+a/${id}` | ||
| 21 | >[EDIT]</a> | ||
| 22 | </> | ||
| 23 | <a> | ||
| 24 | <time | ||
| 25 | datetime=formatQuestionISOTimestamp(date) | ||
| 26 | >${formatQuestionTimestamp(date)}</time> | ||
| 27 | </a> | ||
| 28 | |||
| 29 | <CloverMarkdown text=question.text /> | ||
| 30 | </> | ||
| 31 | |||
| 32 | // this singleton script will make all the '<time>' tags clickable. | ||
| 33 | client import "./clickable-links.client.ts"; | ||
| 34 | |||
| 35 | import type { Question } from "@/q+a/models/Question.ts"; | ||
| 36 | import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts"; | ||
| 37 | import { CloverMarkdown } from "@/q+a/clover-markdown"; | ||
src/q+a/components/QuestionForm.marko deleted-13| ... | @@ -1,13 +0,0 @@ | ||
| 1 | <form action="/q+a" method="POST"> | ||
| 2 | <textarea | ||
| 3 | name="text" | ||
| 4 | placeholder="ask clover a question..." | ||
| 5 | required | ||
| 6 | minlength="1" | ||
| 7 | maxlength="10000" | ||
| 8 | /> | ||
| 9 | <div aria-hidden class="title">ask a question</div> | ||
| 10 | <button type="submit">send</button> | ||
| 11 | <div class="disabled-button">send</div> | ||
| 12 | </form> | ||
| 13 | |||
src/q+a/components/clickable-links.client.ts deleted-57| ... | @@ -1,57 +0,0 @@ | ||
| 1 | async function qaDate(el: HTMLAnchorElement) { | ||
| 2 | if (el.hasAttribute("data-copy-state")) return; | ||
| 3 | const time = el.children[0] as HTMLTimeElement; | ||
| 4 | const prevText = time.textContent; | ||
| 5 | const url = `${window.location.origin}/q+a/${ | ||
| 6 | time.dateTime.replace(/[^\d]/g, "").slice(2, 12) | ||
| 7 | }`; | ||
| 8 | el.setAttribute("data-copy-state", "1"); | ||
| 9 | try { | ||
| 10 | await navigator.clipboard.writeText(url); | ||
| 11 | time.textContent = "copied permalink to question"; | ||
| 12 | time.classList.add("winner"); | ||
| 13 | } catch (error) { | ||
| 14 | time.textContent = "failed to copy :("; | ||
| 15 | } | ||
| 16 | setTimeout(() => { | ||
| 17 | el.removeAttribute("data-copy-state"); | ||
| 18 | time.textContent = prevText; | ||
| 19 | time.classList.remove("winner"); | ||
| 20 | }, 1500); | ||
| 21 | } | ||
| 22 | requestAnimationFrame(() => { | ||
| 23 | // Make every link clickable | ||
| 24 | document.querySelectorAll("e-").forEach((questionRoot) => | ||
| 25 | (questionRoot.querySelector("a:not([href])") as HTMLAnchorElement).href = | ||
| 26 | "#" | ||
| 27 | ); | ||
| 28 | // Avoid attaching 1500+ event listeners | ||
| 29 | document.querySelector("main")!.addEventListener( | ||
| 30 | "click", | ||
| 31 | (event, element = event.target as HTMLAnchorElement) => { | ||
| 32 | if ( | ||
| 33 | !(event.button || | ||
| 34 | event.which != 1 || | ||
| 35 | event.metaKey || | ||
| 36 | event.ctrlKey || | ||
| 37 | event.shiftKey || | ||
| 38 | event.altKey || | ||
| 39 | event.defaultPrevented) | ||
| 40 | ) { | ||
| 41 | while (element && element !== document.body) { | ||
| 42 | if ( | ||
| 43 | element.nodeName.toUpperCase() === "A" && | ||
| 44 | element.getAttribute("href") === "#" && | ||
| 45 | element.parentElement?.tagName === "E-" | ||
| 46 | ) { | ||
| 47 | event.preventDefault(); | ||
| 48 | qaDate(element); | ||
| 49 | return; | ||
| 50 | } | ||
| 51 | element = | ||
| 52 | (element.assignedSlot ?? element.parentNode) as HTMLAnchorElement; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | }, | ||
| 56 | ); | ||
| 57 | }); | ||
src/q+a/image.tsx created+82| ... | @@ -0,0 +1,82 @@ | ||
| 1 | const width = 768; | ||
| 2 | const cacheImageDir = path.resolve(".clover/question_images"); | ||
| 3 | |||
| 4 | // Cached browser session | ||
| 5 | const getBrowser = RefCountedExpirable( | ||
| 6 | () => | ||
| 7 | puppeteer.launch({ | ||
| 8 | // headless: false, | ||
| 9 | args: ["--no-sandbox", "--disable-setuid-sandbox"], | ||
| 10 | }), | ||
| 11 | (b) => b.close(), | ||
| 12 | ); | ||
| 13 | |||
| 14 | export async function renderQuestionImage(question: Question) { | ||
| 15 | const html = await renderViewToString("q+a/embed-image", { question }); | ||
| 16 | |||
| 17 | // this browser session will be reused if multiple images are generated | ||
| 18 | // either at the same time or within a 5-minute time span. the dispose | ||
| 19 | // symbol | ||
| 20 | using sharedBrowser = await getBrowser(); | ||
| 21 | const b = sharedBrowser.value; | ||
| 22 | |||
| 23 | const p = await b.newPage(); | ||
| 24 | await p.setViewport({ width, height: 400 }); | ||
| 25 | await p.setContent(html); | ||
| 26 | try { | ||
| 27 | await p.waitForNetworkIdle({ idleTime: 100, timeout: 500 }); | ||
| 28 | } catch (e) {} | ||
| 29 | const height = await p.evaluate(() => { | ||
| 30 | const e = document.querySelector("main")!; | ||
| 31 | return e.getBoundingClientRect().height; | ||
| 32 | }); | ||
| 33 | const buf = await p.screenshot({ | ||
| 34 | path: "screenshot.png", | ||
| 35 | type: "png", | ||
| 36 | captureBeyondViewport: true, | ||
| 37 | clip: { x: 0, width, y: 0, height: height, scale: 1.5 }, | ||
| 38 | }); | ||
| 39 | await p.close(); | ||
| 40 | |||
| 41 | return Buffer.from(buf); | ||
| 42 | } | ||
| 43 | |||
| 44 | export async function getQuestionImage( | ||
| 45 | question: Question, | ||
| 46 | headOnly: boolean, | ||
| 47 | ): Promise<Response> { | ||
| 48 | const hash = crypto.createHash("sha1") | ||
| 49 | .update(question.qmid + question.type + question.text) | ||
| 50 | .digest("hex"); | ||
| 51 | |||
| 52 | const headers = { | ||
| 53 | "Content-Type": "image/png", | ||
| 54 | "Cache-Control": "public, max-age=31536000", | ||
| 55 | "ETag": `"${hash}"`, | ||
| 56 | "Last-Modified": question.date.toUTCString(), | ||
| 57 | }; | ||
| 58 | |||
| 59 | if (headOnly) { | ||
| 60 | return new Response(null, { headers }); | ||
| 61 | } | ||
| 62 | |||
| 63 | const cachedFilePath = path.join(cacheImageDir, `/${hash}.png`); | ||
| 64 | let buf: Buffer; | ||
| 65 | try { | ||
| 66 | buf = await fs.readFile(cachedFilePath); | ||
| 67 | } catch (e: any) { | ||
| 68 | if (e.code !== "ENOENT") throw e; | ||
| 69 | buf = await renderQuestionImage(question); | ||
| 70 | fs.writeMkdir(cachedFilePath, buf).catch(() => {}); | ||
| 71 | } | ||
| 72 | |||
| 73 | return new Response(buf, { headers }); | ||
| 74 | } | ||
| 75 | |||
| 76 | import * as crypto from "node:crypto"; | ||
| 77 | import * as fs from "#sitegen/fs"; | ||
| 78 | import * as path from "node:path"; | ||
| 79 | import * as puppeteer from "puppeteer"; | ||
| 80 | import { Question } from "@/q+a/models/Question.ts"; | ||
| 81 | import { RefCountedExpirable } from "#sitegen/async"; | ||
| 82 | import { renderViewToString } from "#sitegen/view"; | ||
src/q+a/pages/q+a.marko+3-5| ... | @@ -12,15 +12,13 @@ export const meta: Metadata = { | ... | @@ -12,15 +12,13 @@ export const meta: Metadata = { |
| 12 | <const/questions = [...Question.getAll()] /> | 12 | <const/questions = [...Question.getAll()] /> |
| 13 | 13 | ||
| 14 | <if=!admin> | 14 | <if=!admin> |
| 15 | <QuestionForm /> | 15 | <question-form /> |
| 16 | </> | 16 | </> |
| 17 | <for|q| of=questions> | 17 | <for|question| of=questions> |
| 18 | <QuestionRender question=q admin=admin /> | 18 | <question ...{ question, admin } /> |
| 19 | </> | 19 | </> |
| 20 | <footer> | 20 | <footer> |
| 21 | fun fact: clover has answered ${questions.length} questions | 21 | fun fact: clover has answered ${questions.length} questions |
| 22 | </footer> | 22 | </footer> |
| 23 | 23 | ||
| 24 | import { Question } from "@/q+a/models/Question.ts"; | 24 | import { Question } from "@/q+a/models/Question.ts"; |
| 25 | import { QuestionForm } from "@/q+a/components/QuestionForm.marko"; | ||
| 26 | import QuestionRender from '@/q+a/components/Question.marko'; |
src/q+a/scripts/editor.client.tsx created+115| ... | @@ -0,0 +1,115 @@ | ||
| 1 | import { basicSetup, EditorState, EditorView } from "codemirror"; | ||
| 2 | import { ssrSync } from "#ssr"; | ||
| 3 | // @ts-ignore | ||
| 4 | import type { ScriptPayload } from "../views/editor.marko"; | ||
| 5 | // @ts-ignore | ||
| 6 | import QuestionRender from "@/q+a/components/Question.marko"; | ||
| 7 | |||
| 8 | declare const payload: ScriptPayload; | ||
| 9 | const date = new Date(payload.date); | ||
| 10 | |||
| 11 | const main = document.getElementById("edit-grid")! as HTMLDivElement; | ||
| 12 | const preview = document.getElementById("preview")! as HTMLDivElement; | ||
| 13 | |||
| 14 | function updatePreview(text: string) { | ||
| 15 | preview.innerHTML = ssrSync( | ||
| 16 | <QuestionRender | ||
| 17 | question={{ | ||
| 18 | id: payload.id, | ||
| 19 | qmid: payload.qmid, | ||
| 20 | text: text, | ||
| 21 | type: payload.type, | ||
| 22 | date, | ||
| 23 | }} | ||
| 24 | />, | ||
| 25 | ).text; | ||
| 26 | } | ||
| 27 | |||
| 28 | const startState = EditorState.create({ | ||
| 29 | doc: payload.text, | ||
| 30 | extensions: [ | ||
| 31 | basicSetup, | ||
| 32 | EditorView.darkTheme.of(true), | ||
| 33 | EditorView.updateListener.of((update) => { | ||
| 34 | if (update.docChanged) { | ||
| 35 | updatePreview(update.state.doc.toString()); | ||
| 36 | } | ||
| 37 | }), | ||
| 38 | EditorView.lineWrapping, | ||
| 39 | ], | ||
| 40 | // selection: EditorSelection.create([ | ||
| 41 | // EditorSelection.cursor(0), | ||
| 42 | // ], 0), | ||
| 43 | }); | ||
| 44 | |||
| 45 | const view = new EditorView({ | ||
| 46 | state: startState, | ||
| 47 | parent: document.getElementById("editor")!, | ||
| 48 | }); | ||
| 49 | view.focus(); | ||
| 50 | |||
| 51 | (globalThis as any).onCommitQuestion = wrapAction(async () => { | ||
| 52 | const text = view.state.doc.toString(); | ||
| 53 | const res = await fetch(`/admin/q+a/${payload.id}`, { | ||
| 54 | method: "PATCH", | ||
| 55 | headers: { | ||
| 56 | Accept: "application/json", | ||
| 57 | "Content-Type": "application/json", | ||
| 58 | }, | ||
| 59 | body: JSON.stringify({ | ||
| 60 | text, | ||
| 61 | type: payload.type, | ||
| 62 | }), | ||
| 63 | }); | ||
| 64 | if (!res.ok) { | ||
| 65 | throw new Error("Failed to update question, status: " + res.status); | ||
| 66 | } | ||
| 67 | if (location.search.includes("return=inbox")) { | ||
| 68 | location.href = "/admin/q+a/inbox"; | ||
| 69 | } else { | ||
| 70 | location.href = "/q+a#q" + payload.id; | ||
| 71 | } | ||
| 72 | }); | ||
| 73 | (globalThis as any).onDelete = wrapAction(async () => { | ||
| 74 | if (confirm("Are you sure you want to delete this question?")) { | ||
| 75 | const res = await fetch(`/admin/q+a/${payload.id}`, { | ||
| 76 | method: "DELETE", | ||
| 77 | headers: { | ||
| 78 | Accept: "application/json", | ||
| 79 | }, | ||
| 80 | }); | ||
| 81 | if (!res.ok) { | ||
| 82 | throw new Error("Failed to delete question, status: " + res.status); | ||
| 83 | } | ||
| 84 | location.href = document.referrer || "/admin/q+a"; | ||
| 85 | } | ||
| 86 | }); | ||
| 87 | (globalThis as any).onTypeChange = () => { | ||
| 88 | payload.type = parseInt( | ||
| 89 | (document.getElementById("type") as HTMLSelectElement).value, | ||
| 90 | ); | ||
| 91 | updatePreview(view.state.doc.toString()); | ||
| 92 | }; | ||
| 93 | |||
| 94 | function wrapAction(cb: () => Promise<void>) { | ||
| 95 | return async () => { | ||
| 96 | main.style.opacity = "0.5"; | ||
| 97 | main.style.pointerEvents = "none"; | ||
| 98 | const inputs = main.querySelectorAll("button,select,input") as NodeListOf< | ||
| 99 | HTMLButtonElement | ||
| 100 | >; | ||
| 101 | inputs.forEach((b) => { | ||
| 102 | b.disabled = true; | ||
| 103 | }); | ||
| 104 | try { | ||
| 105 | await cb(); | ||
| 106 | } catch (e: any) { | ||
| 107 | main.style.opacity = "1"; | ||
| 108 | main.style.pointerEvents = "auto"; | ||
| 109 | inputs.forEach((b) => { | ||
| 110 | b.disabled = false; | ||
| 111 | }); | ||
| 112 | alert(e.message); | ||
| 113 | } | ||
| 114 | }; | ||
| 115 | } | ||
src/q+a/tags/clickable-links.client.ts created+57| ... | @@ -0,0 +1,57 @@ | ||
| 1 | async function qaDate(el: HTMLAnchorElement) { | ||
| 2 | if (el.hasAttribute("data-copy-state")) return; | ||
| 3 | const time = el.children[0] as HTMLTimeElement; | ||
| 4 | const prevText = time.textContent; | ||
| 5 | const url = `${window.location.origin}/q+a/${ | ||
| 6 | time.dateTime.replace(/[^\d]/g, "").slice(2, 12) | ||
| 7 | }`; | ||
| 8 | el.setAttribute("data-copy-state", "1"); | ||
| 9 | try { | ||
| 10 | await navigator.clipboard.writeText(url); | ||
| 11 | time.textContent = "copied permalink to question"; | ||
| 12 | time.classList.add("winner"); | ||
| 13 | } catch (error) { | ||
| 14 | time.textContent = "failed to copy :("; | ||
| 15 | } | ||
| 16 | setTimeout(() => { | ||
| 17 | el.removeAttribute("data-copy-state"); | ||
| 18 | time.textContent = prevText; | ||
| 19 | time.classList.remove("winner"); | ||
| 20 | }, 1500); | ||
| 21 | } | ||
| 22 | requestAnimationFrame(() => { | ||
| 23 | // Make every link clickable | ||
| 24 | document.querySelectorAll("e-").forEach((questionRoot) => | ||
| 25 | (questionRoot.querySelector("a:not([href])") as HTMLAnchorElement).href = | ||
| 26 | "#" | ||
| 27 | ); | ||
| 28 | // Avoid attaching 1500+ event listeners | ||
| 29 | document.querySelector("main")!.addEventListener( | ||
| 30 | "click", | ||
| 31 | (event, element = event.target as HTMLAnchorElement) => { | ||
| 32 | if ( | ||
| 33 | !(event.button || | ||
| 34 | event.which != 1 || | ||
| 35 | event.metaKey || | ||
| 36 | event.ctrlKey || | ||
| 37 | event.shiftKey || | ||
| 38 | event.altKey || | ||
| 39 | event.defaultPrevented) | ||
| 40 | ) { | ||
| 41 | while (element && element !== document.body) { | ||
| 42 | if ( | ||
| 43 | element.nodeName.toUpperCase() === "A" && | ||
| 44 | element.getAttribute("href") === "#" && | ||
| 45 | element.parentElement?.tagName === "E-" | ||
| 46 | ) { | ||
| 47 | event.preventDefault(); | ||
| 48 | qaDate(element); | ||
| 49 | return; | ||
| 50 | } | ||
| 51 | element = | ||
| 52 | (element.assignedSlot ?? element.parentNode) as HTMLAnchorElement; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | }, | ||
| 56 | ); | ||
| 57 | }); | ||
src/q+a/tags/question-form.marko created+13| ... | @@ -0,0 +1,13 @@ | ||
| 1 | <form action="/q+a" method="POST"> | ||
| 2 | <textarea | ||
| 3 | name="text" | ||
| 4 | placeholder="ask clover a question..." | ||
| 5 | required | ||
| 6 | minlength="1" | ||
| 7 | maxlength="10000" | ||
| 8 | /> | ||
| 9 | <div aria-hidden class="title">ask a question</div> | ||
| 10 | <button type="submit">send</button> | ||
| 11 | <div class="disabled-button">send</div> | ||
| 12 | </form> | ||
| 13 | |||
src/q+a/tags/question.marko created+37| ... | @@ -0,0 +1,37 @@ | ||
| 1 | // Renders a `Question` entry including its markdown body. | ||
| 2 | export interface Input { | ||
| 3 | question: Question; | ||
| 4 | admin?: boolean; | ||
| 5 | } | ||
| 6 | |||
| 7 | // 2024-12-31 05:00:00 EST | ||
| 8 | export const transitionDate = 1735639200000; | ||
| 9 | |||
| 10 | <const/{ question, admin } = input /> | ||
| 11 | <const/{ id, date } = question/> | ||
| 12 | |||
| 13 | <${"e-"} | ||
| 14 | f=(date > transitionDate ? true : undefined) | ||
| 15 | id=admin ? `q${id}` : undefined | ||
| 16 | > | ||
| 17 | <if=admin> | ||
| 18 | <a | ||
| 19 | style="margin-right: 0.5rem" | ||
| 20 | href=`/admin/q+a/${id}` | ||
| 21 | >[EDIT]</a> | ||
| 22 | </> | ||
| 23 | <a> | ||
| 24 | <time | ||
| 25 | datetime=formatQuestionISOTimestamp(date) | ||
| 26 | >${formatQuestionTimestamp(date)}</time> | ||
| 27 | </a> | ||
| 28 | |||
| 29 | <CloverMarkdown text=question.text /> | ||
| 30 | </> | ||
| 31 | |||
| 32 | // this singleton script will make all the '<time>' tags clickable. | ||
| 33 | client import "./clickable-links.client.ts"; | ||
| 34 | |||
| 35 | import type { Question } from "@/q+a/models/Question.ts"; | ||
| 36 | import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts"; | ||
| 37 | import { CloverMarkdown } from "@/q+a/clover-markdown.tsx"; | ||
src/q+a/views/backend-inbox.client.ts+74| ... | @@ -0,0 +1,74 @@ | ... | @@ -0,0 +1,74 @@ |
| 1 | // @ts-ignore | ||
| 2 | globalThis.onReply = (id: string) => { | ||
| 3 | location.href = `/admin/q+a/${id}?return=inbox`; | ||
| 4 | }; | ||
| 5 | // @ts-ignore | ||
| 6 | globalThis.onDelete = async (id: string) => { | ||
| 7 | const div = document.querySelector(`[data-q="${id}"]`) as HTMLDivElement; | ||
| 8 | if (!div) return alert("Question not found"); | ||
| 9 | |||
| 10 | // Pending State | ||
| 11 | div.style.opacity = "0.5"; | ||
| 12 | div.style.pointerEvents = "none"; | ||
| 13 | div?.querySelectorAll("button").forEach((b) => { | ||
| 14 | b.disabled = true; | ||
| 15 | }); | ||
| 16 | |||
| 17 | try { | ||
| 18 | const resp = await fetch(`/admin/q+a/${id}`, { | ||
| 19 | method: "DELETE", | ||
| 20 | headers: { | ||
| 21 | Accept: "application/json", | ||
| 22 | }, | ||
| 23 | }); | ||
| 24 | if (resp.status !== 200) { | ||
| 25 | throw new Error("Failed to delete question, status: " + resp.status); | ||
| 26 | } | ||
| 27 | } catch (e: any) { | ||
| 28 | div.style.opacity = "1"; | ||
| 29 | div.style.pointerEvents = "auto"; | ||
| 30 | div?.querySelectorAll("button").forEach((b) => { | ||
| 31 | b.disabled = false; | ||
| 32 | }); | ||
| 33 | return alert(e.message); | ||
| 34 | } | ||
| 35 | |||
| 36 | div.remove(); | ||
| 37 | }; | ||
| 38 | // @ts-ignore | ||
| 39 | globalThis.onDeleteFull = async (id: string) => { | ||
| 40 | const div = document.querySelector(`[data-q="${id}"]`) as HTMLDivElement; | ||
| 41 | if (!div) return alert("Question not found"); | ||
| 42 | |||
| 43 | // Confirmation | ||
| 44 | if (!confirm("Are you sure you want to delete this question?")) return; | ||
| 45 | |||
| 46 | // Pending State | ||
| 47 | div.style.opacity = "0.5"; | ||
| 48 | div.style.pointerEvents = "none"; | ||
| 49 | div?.querySelectorAll("button").forEach((b) => { | ||
| 50 | b.disabled = true; | ||
| 51 | }); | ||
| 52 | |||
| 53 | try { | ||
| 54 | const resp = await fetch(`/admin/q+a/${id}`, { | ||
| 55 | method: "DELETE", | ||
| 56 | headers: { | ||
| 57 | Accept: "application/json", | ||
| 58 | "X-Delete-Full": "true", | ||
| 59 | }, | ||
| 60 | }); | ||
| 61 | if (resp.status !== 200) { | ||
| 62 | throw new Error("Failed to delete question, status: " + resp.status); | ||
| 63 | } | ||
| 64 | } catch (e: any) { | ||
| 65 | div.style.opacity = "1"; | ||
| 66 | div.style.pointerEvents = "auto"; | ||
| 67 | div?.querySelectorAll("button").forEach((b) => { | ||
| 68 | b.disabled = false; | ||
| 69 | }); | ||
| 70 | return alert(e.message); | ||
| 71 | } | ||
| 72 | |||
| 73 | div.remove(); | ||
| 74 | }; |
src/q+a/views/editor.css created+39| ... | @@ -0,0 +1,39 @@ | ||
| 1 | #edit-grid { | ||
| 2 | display: grid; | ||
| 3 | grid-template-columns: 1fr 80ch; | ||
| 4 | grid-template-rows: 3rem 1fr; | ||
| 5 | grid-gap: 1em; | ||
| 6 | height: 100vh; | ||
| 7 | |||
| 8 | button { | ||
| 9 | margin-right: 1rem; | ||
| 10 | } | ||
| 11 | main { | ||
| 12 | padding: 0; | ||
| 13 | margin: 0; | ||
| 14 | } | ||
| 15 | } | ||
| 16 | #topleft, #topright { | ||
| 17 | padding: 1rem; | ||
| 18 | } | ||
| 19 | #topleft { | ||
| 20 | padding-right: 0rem; | ||
| 21 | } | ||
| 22 | #topright { | ||
| 23 | padding-left: 0rem; | ||
| 24 | } | ||
| 25 | #preview { | ||
| 26 | overflow-y: auto; | ||
| 27 | e- { | ||
| 28 | margin-top: 0; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | #editor { | ||
| 32 | background-color: #303030; | ||
| 33 | overflow-y: scroll; | ||
| 34 | height: 100%; | ||
| 35 | } | ||
| 36 | .cm-scroller { | ||
| 37 | overflow-y: auto; | ||
| 38 | height: 100%; | ||
| 39 | } | ||
src/q+a/views/editor.marko created+65| ... | @@ -0,0 +1,65 @@ | ||
| 1 | import "./editor.css"; | ||
| 2 | export { theme } from "@/q+a/layout.tsx"; | ||
| 3 | |||
| 4 | export const meta = { title: "question editor" }; | ||
| 5 | |||
| 6 | export interface Input { | ||
| 7 | question: Question; | ||
| 8 | pendingInfo: PendingQuestion.JSON | null; | ||
| 9 | } | ||
| 10 | |||
| 11 | export interface ScriptPayload { | ||
| 12 | id: string; | ||
| 13 | qmid: string; | ||
| 14 | text: string; | ||
| 15 | type: Question.Type; | ||
| 16 | date: string; | ||
| 17 | } | ||
| 18 | |||
| 19 | <const/{ question } = input /> | ||
| 20 | |||
| 21 | <const/payload= { | ||
| 22 | id: question.id, | ||
| 23 | qmid: question.qmid, | ||
| 24 | text: question.text, | ||
| 25 | type: question.type, | ||
| 26 | date: question.date.toISOString(), | ||
| 27 | } satisfies ScriptPayload/> | ||
| 28 | |||
| 29 | <div#edit-grid.qa> | ||
| 30 | <div#topleft> | ||
| 31 | <button | ||
| 32 | onclick="location.href='/admin/q+a'" | ||
| 33 | style={ color: "magenta" } | ||
| 34 | > | ||
| 35 | all | ||
| 36 | </button> | ||
| 37 | <button | ||
| 38 | onclick="location.href='/admin/q+a/inbox'" | ||
| 39 | style={ color: "dodgerblue" } | ||
| 40 | > | ||
| 41 | inbox | ||
| 42 | </button> | ||
| 43 | </div> | ||
| 44 | <div#topright> | ||
| 45 | <button onclick="onCommitQuestion()" style="color:lime"> | ||
| 46 | update | ||
| 47 | </button> | ||
| 48 | <button onclick="onDelete()" style={ color: "red" }> | ||
| 49 | reject | ||
| 50 | </button> | ||
| 51 | <select id="type" onchange="onTypeChange()" value=question.type> | ||
| 52 | <option value=QuestionType.normal>normal</> | ||
| 53 | <option value=QuestionType.annotation>annotation</> | ||
| 54 | <option value=QuestionType.reject>reject</> | ||
| 55 | </select> | ||
| 56 | </div> | ||
| 57 | <div#editor /> | ||
| 58 | <main#preview> | ||
| 59 | <question ...{question} /> | ||
| 60 | </main> | ||
| 61 | </div> | ||
| 62 | <html-script src="/js/edit_frontend.js" type="module" /> | ||
| 63 | |||
| 64 | import { type PendingQuestion } from "@/q+a/models/PendingQuestion.ts"; | ||
| 65 | import { Question, QuestionType } from "@/q+a/models/Question.ts"; | ||
src/q+a/views/image-embed.marko created+16| ... | @@ -0,0 +1,16 @@ | ||
| 1 | export const meta = { title: "embed image" }; | ||
| 2 | export interface Input { | ||
| 3 | question: Question; | ||
| 4 | } | ||
| 5 | |||
| 6 | <html-style> | ||
| 7 | main { padding-top: 11px; } | ||
| 8 | e- { margin: 0!important } | ||
| 9 | e- > :first-child { margin-top: 0!important } | ||
| 10 | e- > :last-child { margin-bottom: 0!important } | ||
| 11 | </html-style> | ||
| 12 | <main.qa> | ||
| 13 | <question question=input.question /> | ||
| 14 | </main> | ||
| 15 | |||
| 16 | import { Question } from '@/q+a/models/Question'; | ||
src/q+a/views/permalink.marko created+47| ... | @@ -0,0 +1,47 @@ | ||
| 1 | export interface Input { | ||
| 2 | question: Question; | ||
| 3 | } | ||
| 4 | |||
| 5 | server export function meta({ context: { req }, question }) { | ||
| 6 | const isDiscord = req.get("user-agent") | ||
| 7 | ?.toLowerCase() | ||
| 8 | .includes("discordbot"); | ||
| 9 | if (question.type === QuestionType.normal) { | ||
| 10 | return { | ||
| 11 | title: "question permalink", | ||
| 12 | openGraph: { | ||
| 13 | images: [{ url: `https://paperclover.net/q+a/${q.id}.png` }], | ||
| 14 | }, | ||
| 15 | twitter: { card: "summary_large_image" }, | ||
| 16 | themeColor: isDiscord | ||
| 17 | ? q.date.getTime() > transitionDate ? "#8c78ff" : "#58ff71" | ||
| 18 | : undefined, | ||
| 19 | }; | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | <const/{ question }=input/> | ||
| 24 | <const/{ type }=question/> | ||
| 25 | <if=type==QuestionType.normal> | ||
| 26 | <p>this page is a permalink to the following question:</p> | ||
| 27 | <question ...{question} /> | ||
| 28 | </><else if=type==QuestionType.pending> | ||
| 29 | <p> | ||
| 30 | this page is a permalink to a question that | ||
| 31 | has not yet been answered. | ||
| 32 | </p> | ||
| 33 | <p><a href="/q+a">read questions with existing responses</a>.</p> | ||
| 34 | </><else if=type==QuestionType.reject> | ||
| 35 | <p> | ||
| 36 | this page is a permalink to a question, but the question | ||
| 37 | was deleted instead of answered. maybe it was sent multiple | ||
| 38 | times, or maybe the question was not a question. who knows. | ||
| 39 | </p> | ||
| 40 | <p>sorry, sister</p> | ||
| 41 | <p><a href="/q+a">all questions</a></p> | ||
| 42 | </><else> | ||
| 43 | <p>oh dear, this question is in an invalid state</p> | ||
| 44 | <pre>${JSON.stringify(question, null, 2)}</pre> | ||
| 45 | </> | ||
| 46 | |||
| 47 | import { Question, QuestionType } from '@/q+a/models/Question.ts'; | ||
src/site.ts+4-5| ... | @@ -3,17 +3,16 @@ | ... | @@ -3,17 +3,16 @@ |
| 3 | // sub-projects like the file viewer in 'file', or the question answer system | 3 | // sub-projects like the file viewer in 'file', or the question answer system |
| 4 | // in 'q+a'. Each section can define configuration, pages, backend routes, and | 4 | // in 'q+a'. Each section can define configuration, pages, backend routes, and |
| 5 | // contain other files. | 5 | // contain other files. |
| 6 | interface Section { | ||
| 7 | root: string; | ||
| 8 | } | ||
| 9 | |||
| 10 | const join = (...paths: string[]) => path.join(import.meta.dirname, ...paths); | 6 | const join = (...paths: string[]) => path.join(import.meta.dirname, ...paths); |
| 11 | 7 | ||
| 12 | export const siteSections: Section[] = [ | 8 | export const siteSections: Section[] = [ |
| 13 | { root: join("./") }, | 9 | { root: join(".") }, |
| 14 | { root: join("q+a/") }, | 10 | { root: join("q+a/") }, |
| 15 | { root: join("file-viewer/") }, | 11 | { root: join("file-viewer/") }, |
| 16 | { root: join("friends/") }, | 12 | { root: join("friends/") }, |
| 13 | // { root: join("blog/"), pageBase: "/blog" }, | ||
| 14 | // { root: join("fiction/"), pageBase: "/fiction" }, | ||
| 17 | ]; | 15 | ]; |
| 18 | 16 | ||
| 19 | import * as path from "node:path"; | 17 | import * as path from "node:path"; |
| 18 | import { Section } from "#sitegen"; |
src/tags/Video.tsx created+57| ... | @@ -0,0 +1,57 @@ | ||
| 1 | import * as path from "node:path"; | ||
| 2 | import { addScript } from "#sitegen"; | ||
| 3 | import { PrecomputedBlurhash } from "./blurhash.tsx"; | ||
| 4 | import "./Video.css"; | ||
| 5 | export namespace Video { | ||
| 6 | export interface Props { | ||
| 7 | title: string; | ||
| 8 | width: number; | ||
| 9 | height: number; | ||
| 10 | sources: string[]; | ||
| 11 | downloads: string[]; | ||
| 12 | poster?: string; | ||
| 13 | posterHash?: string; | ||
| 14 | borderless?: boolean; | ||
| 15 | } | ||
| 16 | } | ||
| 17 | function PrecomputedBlurhash({ hash }: { hash: string }) { | ||
| 18 | export function Video( | ||
| 19 | { title, sources, height, poster, posterHash, width, borderless }: | ||
| 20 | Video.Props, | ||
| 21 | ) { | ||
| 22 | addScript("./video.client.ts"); | ||
| 23 | return ( | ||
| 24 | <figure class={`video ${borderless ? "borderless" : ""}`}> | ||
| 25 | <figcaption>{title}</figcaption> | ||
| 26 | {/* posterHash && <PrecomputedBlurhash hash={posterHash} /> */} | ||
| 27 | {poster && <img src={poster} alt="waterfalls" />} | ||
| 28 | <video | ||
| 29 | controls | ||
| 30 | preload="none" | ||
| 31 | style={`width:100%;background:transparent;aspect-ratio:${ | ||
| 32 | simplifyFraction(width, height) | ||
| 33 | }`} | ||
| 34 | poster="data:null" | ||
| 35 | > | ||
| 36 | {sources.map((src) => ( | ||
| 37 | <source | ||
| 38 | src={src} | ||
| 39 | type={contentTypeFromExt(src)} | ||
| 40 | /> | ||
| 41 | ))} | ||
| 42 | </video> | ||
| 43 | </figure> | ||
| 44 | ); | ||
| 45 | } | ||
| 46 | export function contentTypeFromExt(src: string) { | ||
| 47 | if (src.endsWith(".m3u8")) return "application/x-mpegURL"; | ||
| 48 | if (src.endsWith(".webm")) return "video/webm"; | ||
| 49 | if (src.endsWith(".mp4")) return "video/mp4"; | ||
| 50 | if (src.endsWith(".ogg")) return "video/ogg"; | ||
| 51 | throw new Error("Unknown video extension: " + path.extname(src)); | ||
| 52 | } | ||
| 53 | const gcd = (a: number, b: number): number => b ? gcd(b, a % b) : a; | ||
| 54 | function simplifyFraction(n: number, d: number) { | ||
| 55 | const divisor = gcd(n, d); | ||
| 56 | return `${n / divisor}/${d / divisor}`; | ||
| 57 | } | ||
src/tags/video.client.ts created+29| ... | @@ -0,0 +1,29 @@ | ||
| 1 | import type Hls from "hls.js"; | ||
| 2 | |||
| 3 | let cachedHls: null | Promise<typeof Hls | false> | typeof Hls | false = null; | ||
| 4 | |||
| 5 | const loadHls = () => | ||
| 6 | cachedHls ?? (cachedHls = ( | ||
| 7 | // @ts-ignore | ||
| 8 | +"HLS POLYFILL", import("hls.js") | ||
| 9 | ).then((m, y = m.default) => cachedHls = y.isSupported() && y)); | ||
| 10 | |||
| 11 | const createVideoPlayer = async (videoElement: HTMLVideoElement) => { | ||
| 12 | const sources = videoElement.querySelectorAll("source"); | ||
| 13 | for (let i = 0; i < sources.length; i++) { | ||
| 14 | const { src, type } = sources[i]; | ||
| 15 | if (videoElement.canPlayType(type)) { | ||
| 16 | videoElement.src = src; | ||
| 17 | return; | ||
| 18 | } | ||
| 19 | |||
| 20 | if (type === "application/x-mpegURL" && await loadHls()) { | ||
| 21 | const hls = new (cachedHls as typeof Hls)(); | ||
| 22 | hls.loadSource(src); | ||
| 23 | hls.attachMedia(videoElement); | ||
| 24 | return; | ||
| 25 | } | ||
| 26 | } | ||
| 27 | }; | ||
| 28 | |||
| 29 | document.querySelectorAll("video").forEach(createVideoPlayer); | ||
tsconfig.json+2-1| ... | @@ -15,5 +15,6 @@ | ... | @@ -15,5 +15,6 @@ |
| 15 | "strict": true, | 15 | "strict": true, |
| 16 | "verbaitimModuleSyntax": true, | 16 | "verbaitimModuleSyntax": true, |
| 17 | "target": "es2022" | 17 | "target": "es2022" |
| 18 | } | 18 | }, |
| 19 | "include": ["framework/**/*", "src/**/*"] | ||
| 19 | } | 20 | } |