├── example ├── .gitignore ├── css │ ├── main.css │ └── another-css-file.css ├── README.md ├── web │ └── index.html ├── deno.jsonc └── src │ └── main.tsx ├── jsx-codegen ├── deno.json └── main.ts ├── src ├── mod.ts ├── safely.ts ├── router.ts ├── esbuild │ ├── plugins.ts │ └── mod.ts ├── signal.ts ├── jsx.ts └── elem.ts ├── .editorconfig ├── .prettierrc ├── deno.json ├── README.md └── deno.lock /example/.gitignore: -------------------------------------------------------------------------------- 1 | /web/dist 2 | -------------------------------------------------------------------------------- /example/css/main.css: -------------------------------------------------------------------------------- 1 | @import url(./another-css-file.css); 2 | -------------------------------------------------------------------------------- /example/css/another-css-file.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: sans-serif; 3 | } 4 | -------------------------------------------------------------------------------- /jsx-codegen/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "lock": false, 3 | "imports": { 4 | "typescript": "npm:typescript@^5.8.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./elem.ts"; 2 | export * from "./router.ts"; 3 | export * from "./safely.ts"; 4 | export * from "./signal.ts"; 5 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # aftercare example 2 | 3 | ```shell 4 | $ deno task dev 5 | Serving on http://127.0.0.1:3000/ ... 6 | $ deno task build 7 | $ # [check ./web/dist] 8 | ``` 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": true, 5 | "arrowParens": "avoid", 6 | "printWidth": 96, 7 | "trailingComma": "all", 8 | "overrides": [ 9 | { 10 | "files": "*.jsonc", 11 | "options": { 12 | "trailingComma": "none" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/safely.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | export function safely( 3 | f: (...args: Args) => R, 4 | ): (...args: Args) => [ok: R, err: undefined] | [ok: undefined, err: Error] { 5 | return (...args) => { 6 | try { 7 | return [f(...args), undefined] as const; 8 | } catch (err) { 9 | return [undefined, err as Error] as const; 10 | } 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@char/aftercare", 3 | "version": "0.4.2", 4 | "license": "WTFPL", 5 | "exports": { 6 | ".": "./src/mod.ts", 7 | "./esbuild": "./src/esbuild/mod.ts", 8 | "./jsx-runtime": "./src/jsx.ts" 9 | }, 10 | "compilerOptions": { 11 | "lib": ["deno.window", "dom"], 12 | "jsx": "react-jsx", 13 | "jsxImportSource": "@char/aftercare" 14 | }, 15 | "publish": { 16 | "exclude": ["example/", ".editorconfig", ".prettierrc"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "dev": "deno run -A ../src/esbuild/mod.ts --watch --serve --in src/main.tsx", 4 | "build": "deno run -A ../src/esbuild/mod.ts --in src/main.tsx" 5 | }, 6 | "imports": { 7 | // usually you should be able to just have one import entry for @char/aftercare from jsr 8 | "@char/aftercare": "../src/mod.ts", 9 | "@char/aftercare/elem": "../src/elem.ts", 10 | "@char/aftercare/jsx-runtime": "../src/jsx.ts" 11 | }, 12 | "compilerOptions": { 13 | "lib": ["deno.window", "dom"], 14 | "jsx": "react-jsx", 15 | "jsxImportSource": "@char/aftercare" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "jsr:@std/assert"; 2 | assertEquals(2, 1 + 1); 3 | 4 | import { Signal } from "@char/aftercare"; 5 | 6 | const counter = new Signal(0); 7 | const increment = () => counter.value++; 8 | const decrement = () => counter.value--; 9 | const showCounter = (span: HTMLElement) => 10 | counter.subscribeImmediate(v => (span.textContent = v + "")); 11 | 12 | document.body.append( 13 |
14 | 17 | 18 | 21 |
, 22 | ); 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aftercare 2 | 3 | makes the dom nicer 4 | 5 | a microframework for client-side frontend development 6 | 7 | ## features 8 | 9 | - deno/esbuild build system 10 | - reactive signals 11 | - strongly-typed element creation helpers 12 | - jsx runtime 13 | - monadic result wrapper for functions that throw 14 | 15 | ## example 16 | 17 | ```tsx 18 | // src/main.tsx 19 | 20 | // esbuild resolves + bundles jsr/npm/https imports: 21 | import { assertEquals } from "jsr:@std/assert"; 22 | assertEquals(2, 1 + 1); 23 | 24 | // Signals have reactive behavior (set(..) calls all subscribe(..) listeners) 25 | import { Signal } from "jsr:@char/aftercare@0.3.0"; 26 | 27 | const counter = new Signal(0); 28 | const increment = () => counter.value++; 29 | const decrement = () => counter.value--; 30 | const showCounter = (span: HTMLElement) => 31 | counter.subscribeImmediate(value => (span.innerText = value + "")); 32 | 33 | document.body.append( 34 |
35 | 36 | 37 | 38 |
, 39 | ); 40 | ``` 41 | 42 | ```html 43 | 44 | 45 | 46 | 47 | 48 | 49 | ``` 50 | 51 | build like so: 52 | 53 | ```shell 54 | $ deno run -A jsr:@char/aftercare/esbuild --in src/main.tsx 55 | ``` 56 | -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | import { LazySignal } from "./signal.ts"; 2 | 3 | export abstract class Router { 4 | readonly route: LazySignal = new LazySignal(); 5 | 6 | abstract parseRoute(path: string): Route; 7 | abstract getPathForRoute(route: Route): string; 8 | 9 | navigateTo(pathOrRoute: string | Route) { 10 | const isPath = typeof pathOrRoute === "string"; 11 | const path = isPath ? pathOrRoute : this.getPathForRoute(pathOrRoute); 12 | const route = isPath ? this.parseRoute(pathOrRoute) : pathOrRoute; 13 | 14 | history.pushState(null, "", path); 15 | globalThis.scrollTo(0, 0); 16 | 17 | this.route.set(route); 18 | } 19 | 20 | install() { 21 | document.addEventListener( 22 | "click", 23 | e => { 24 | if (!(e.target instanceof Element)) return; 25 | const anchor = e.target.closest("a"); 26 | if (anchor === null) return; 27 | if (e.ctrlKey || e.button !== 0) return; 28 | 29 | const url = new URL(anchor.href); 30 | if (location.origin !== url.origin) return; // open external links normally 31 | 32 | e.preventDefault(); 33 | this.navigateTo(url.pathname); 34 | }, 35 | { signal: this.#dispose.signal }, 36 | ); 37 | 38 | globalThis.addEventListener( 39 | "popstate", 40 | () => { 41 | const parsedRoute = this.parseRoute(location.pathname); 42 | this.route.set(parsedRoute); 43 | }, 44 | { signal: this.#dispose.signal }, 45 | ); 46 | } 47 | 48 | #dispose = new AbortController(); 49 | dispose() { 50 | this.#dispose.abort(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/esbuild/plugins.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "jsr:@std/dotenv@0.225"; 2 | import * as path from "jsr:@std/path@1"; 3 | import type * as esbuild from "npm:esbuild@0.24"; 4 | 5 | export const envPlugin = (files: string[]): esbuild.Plugin => ({ 6 | name: "env", 7 | setup: build => { 8 | build.onResolve({ filter: /^build-system-env$/ }, args => { 9 | return { 10 | path: args.path, 11 | namespace: "env-ns", 12 | watchFiles: files, 13 | }; 14 | }); 15 | 16 | build.onLoad({ filter: /.*/, namespace: "env-ns" }, async () => { 17 | const env = {}; 18 | for (const file of files) { 19 | try { 20 | const loaded = await dotenv.load({ envPath: file, export: false }); 21 | Object.assign(env, loaded); 22 | } catch { 23 | // ignore 24 | } 25 | } 26 | 27 | return { 28 | contents: JSON.stringify(env), 29 | loader: "json", 30 | }; 31 | }); 32 | }, 33 | }); 34 | 35 | export const cssPlugin = (): esbuild.Plugin => ({ 36 | name: "css", 37 | setup: build => { 38 | const options = build.initialOptions; 39 | options.loader = { ...options.loader, ".woff": "file", ".woff2": "file" }; 40 | 41 | build.onResolve({ filter: /\.css$/ }, args => { 42 | if (args.path.startsWith("https://")) { 43 | return { path: args.path, external: true }; 44 | } 45 | 46 | return { path: path.join(args.resolveDir, args.path) }; 47 | }); 48 | 49 | build.onLoad({ filter: /\.css$/ }, args => { 50 | const loader = args.path.endsWith(".css") ? "css" : "file"; 51 | return { loader }; 52 | }); 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /src/signal.ts: -------------------------------------------------------------------------------- 1 | export type Subscription = (value: T) => unknown | Promise; 2 | 3 | class SignalBase { 4 | #listeners: Set> = new Set(); 5 | 6 | #value: A; 7 | 8 | constructor(value: A) { 9 | this.#value = value; 10 | } 11 | 12 | get(): A { 13 | return this.#value; 14 | } 15 | 16 | set(newValue: B) { 17 | this.#value = newValue; 18 | for (const listener of this.#listeners) { 19 | try { 20 | void listener(newValue); 21 | } catch { 22 | // ignore 23 | } 24 | } 25 | } 26 | 27 | subscribe( 28 | listener: Subscription, 29 | opts: { signal?: AbortSignal } = {}, 30 | ): { unsubscribe: () => void } { 31 | const listeners = this.#listeners; 32 | listeners.add(listener); 33 | 34 | const subscription = { 35 | unsubscribe() { 36 | listeners.delete(listener); 37 | }, 38 | }; 39 | 40 | if (opts.signal) { 41 | opts.signal.addEventListener("abort", () => subscription.unsubscribe()); 42 | } 43 | 44 | return subscription; 45 | } 46 | 47 | get value(): A { 48 | return this.get(); 49 | } 50 | 51 | set value(newValue: B) { 52 | this.set(newValue); 53 | } 54 | } 55 | 56 | export class Signal extends SignalBase { 57 | subscribeImmediate(listener: Subscription): ReturnType { 58 | listener(this.get()); 59 | return this.subscribe(listener); 60 | } 61 | } 62 | export class LazySignal extends SignalBase { 63 | constructor() { 64 | super(undefined); 65 | } 66 | 67 | subscribeImmediate(listener: Subscription): ReturnType { 68 | const value = this.get(); 69 | if (value) listener(value); 70 | return this.subscribe(listener); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /jsx-codegen/main.ts: -------------------------------------------------------------------------------- 1 | import ts from "npm:typescript"; 2 | 3 | const program = ts.createProgram(["./main.ts"], { 4 | target: ts.ScriptTarget.Latest, 5 | lib: ["lib.dom.d.ts", "lib.esnext.d.ts"], 6 | }); 7 | const checker = program.getTypeChecker(); 8 | 9 | const sourceFile = program.getSourceFiles().find(sf => !sf.isDeclarationFile); 10 | if (!sourceFile) throw new Error("bwah"); 11 | 12 | const htmlMap = checker.resolveName( 13 | "HTMLElementTagNameMap", 14 | sourceFile, 15 | ts.SymbolFlags.Interface, 16 | false, 17 | )!; 18 | 19 | const getTypeName = (ty: ts.Type) => { 20 | if (ty.getCallSignatures().length > 0) return checker.typeToString(ty); 21 | if (ty.aliasSymbol) return ty.aliasSymbol.getName(); 22 | if (ty.symbol) return ty.symbol.getName(); 23 | return checker.typeToString(ty); 24 | }; 25 | 26 | const isReadonly = (symbol: ts.Symbol) => { 27 | for (const decl of symbol.declarations ?? []) { 28 | if (ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl)) { 29 | const flags = ts.getCombinedModifierFlags(decl); 30 | if ((flags & ts.ModifierFlags.Readonly) !== 0) return true; 31 | } 32 | } 33 | 34 | return false; 35 | }; 36 | 37 | console.log("export interface ElementPropertyMap {"); 38 | 39 | for (const [key, htmlElem] of htmlMap.members!.entries()) { 40 | const htmlElemType = checker.getTypeOfSymbol(htmlElem); 41 | 42 | console.log(" ", `[${JSON.stringify(key)}]:`, "{"); 43 | 44 | for (const propSymbol of htmlElemType.getProperties()) { 45 | if (isReadonly(propSymbol)) continue; 46 | 47 | const propType = checker.getTypeOfSymbol(propSymbol); 48 | if (propType.getCallSignatures().length > 0) continue; 49 | 50 | const propName = propSymbol.name; 51 | console.log(" ", `[${JSON.stringify(propName)}]:`, getTypeName(propType) + ";"); 52 | } 53 | 54 | console.log(" };"); 55 | } 56 | 57 | console.log("}"); 58 | -------------------------------------------------------------------------------- /src/jsx.ts: -------------------------------------------------------------------------------- 1 | import { 2 | elem, 3 | type ElementEventListeners, 4 | type ElementExtras, 5 | type ElementProps, 6 | type ElementType, 7 | type TagName, 8 | } from "./elem.ts"; 9 | 10 | type ElementAttributes = Omit< 11 | ElementProps>, 12 | keyof ElementExtras> 13 | > & 14 | ElementExtras> & 15 | ElementEventListeners>; 16 | 17 | type DOMElement = Element; // Element gets shadowed in the JSX namespace later: 18 | 19 | // deno-lint-ignore no-namespace 20 | namespace JSX { 21 | export type Element = HTMLElement | SVGElement; 22 | export type Child = JSX.Element | DOMElement | DOMElement[] | string | Text; 23 | export type IntrinsicElements = { 24 | [K in TagName]: Omit, "children"> & { 25 | children?: JSX.Child | JSX.Child[] | undefined; 26 | }; 27 | }; 28 | } 29 | 30 | function Fragment(_props: Record, _key?: string): never { 31 | throw new Error("fragments are not supported!"); 32 | } 33 | 34 | function jsx( 35 | tag: T | ((props: Record) => JSX.Element), 36 | props: Record, 37 | _key?: string, 38 | ): ElementType { 39 | // function components 40 | if (typeof tag === "function") return tag(props) as ElementType; 41 | 42 | const { children = [], classList, dataset, style, _tap, ...attrs } = props; 43 | const childrenArray = Array.isArray(children) ? children : [children]; 44 | const extras = { classList, dataset, style, _tap } as ElementExtras> & 45 | ElementEventListeners>; 46 | for (const key of Object.keys(attrs)) { 47 | if (!key.startsWith("_on")) continue; 48 | // @ts-expect-error blind assignment 49 | extras[key] = attrs[key]; 50 | delete attrs[key]; 51 | } 52 | return elem(tag, attrs as ElementProps>, childrenArray, extras); 53 | } 54 | 55 | export { Fragment, jsx, jsx as jsxDEV, jsx as jsxs }; 56 | export type { JSX }; 57 | -------------------------------------------------------------------------------- /src/esbuild/mod.ts: -------------------------------------------------------------------------------- 1 | import { denoPlugins } from "jsr:@luca/esbuild-deno-loader@0.11"; 2 | import * as cli from "jsr:@std/cli@1"; 3 | import * as esbuild from "npm:esbuild@0.24"; 4 | import { cssPlugin, envPlugin } from "./plugins.ts"; 5 | 6 | export * from "./plugins.ts"; 7 | 8 | export interface BuildOptions { 9 | watch?: boolean; 10 | serve?: esbuild.ServeOptions; 11 | plugins?: esbuild.Plugin[]; 12 | in: esbuild.BuildOptions["entryPoints"]; 13 | outDir: string; // directory 14 | extraOptions?: Partial; 15 | } 16 | 17 | export async function build(opts: BuildOptions) { 18 | const buildOpts = { 19 | bundle: true, 20 | minify: true, 21 | splitting: true, 22 | target: "esnext", 23 | platform: "browser", 24 | format: "esm", 25 | keepNames: true, 26 | sourcemap: "linked", 27 | plugins: [...(opts.plugins ?? []), ...denoPlugins()], 28 | entryPoints: opts.in, 29 | outdir: opts.outDir, 30 | legalComments: "none", 31 | jsx: "automatic", 32 | jsxImportSource: "@char/aftercare", 33 | ...(opts.extraOptions ?? {}), 34 | } satisfies esbuild.BuildOptions; 35 | 36 | if (opts.watch) { 37 | const ctx = await esbuild.context(buildOpts); 38 | await ctx.watch(); 39 | if (opts.serve) { 40 | console.log(`Serving on http://${opts.serve.host}:${opts.serve.port}/ ...`); 41 | await ctx.serve(opts.serve); 42 | } 43 | } else { 44 | await esbuild.build(buildOpts); 45 | } 46 | } 47 | 48 | if (import.meta.main) { 49 | const args = cli.parseArgs(Deno.args, { 50 | boolean: ["watch", "serve"], 51 | string: ["bind"], 52 | collect: ["in"], 53 | default: { bind: "127.0.0.1:3000" }, 54 | }); 55 | 56 | const createServeInfo = (bind: string) => { 57 | let hostAndPort: { host: string; port: number }; 58 | if (bind.indexOf(":") === -1) { 59 | hostAndPort = { host: "127.0.0.1", port: parseInt(bind) }; 60 | } else { 61 | const [host, port] = bind.split(":"); 62 | hostAndPort = { host, port: parseInt(port) }; 63 | } 64 | return { ...hostAndPort, servedir: "./web", fallback: "./web/index.html" }; 65 | }; 66 | const serve = args.serve ? createServeInfo(args.bind) : undefined; 67 | 68 | await build({ 69 | watch: args.watch, 70 | serve, 71 | in: args.in ? (args.in as string[]) : ["./src/main.ts"], 72 | outDir: "./web/dist", 73 | plugins: [envPlugin([".env", ".env.local"]), cssPlugin()], 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /src/elem.ts: -------------------------------------------------------------------------------- 1 | type GenericElement = HTMLElement | SVGElement; 2 | 3 | // prettier-ignore 4 | type IsEqual = 5 | (() => G extends A ? 1 : 2) extends 6 | (() => G extends B ? 1 : 2) ? 7 | true : false; 8 | 9 | // prettier-ignore 10 | type WritableKeysOf = { 11 | [P in keyof T]: IsEqual< 12 | { [Q in P]: T[P] }, 13 | { readonly [Q in P]: T[P] } 14 | > extends false ? P : never; 15 | }[keyof T]; 16 | 17 | export type ElementProps = { 18 | // deno-lint-ignore ban-types 19 | [K in WritableKeysOf as NonNullable extends Function ? never : K]?: E[K]; 20 | }; 21 | 22 | export interface ElementExtras { 23 | classList?: (string | undefined)[]; 24 | style?: { 25 | // prettier-ignore 26 | [K in WritableKeysOf 27 | as K extends string 28 | ? CSSStyleDeclaration[K] extends string ? K : never 29 | : never 30 | ]?: CSSStyleDeclaration[K]; 31 | } & { 32 | [prop: `--${string}`]: string | undefined; 33 | }; 34 | dataset?: Partial>; 35 | /** extra function to run on the element */ 36 | _tap?: (elem: E) => void; 37 | } 38 | 39 | export type ElementEventListeners = { 40 | [K in keyof HTMLElementEventMap as K extends string ? `_on${K}` : never]?: ( 41 | this: E, 42 | ev: HTMLElementEventMap[K], 43 | ) => void; 44 | }; 45 | 46 | export type TagName = keyof HTMLElementTagNameMap; 47 | export type CustomTagType = new () => T; 48 | export type ElementType = T extends TagName 49 | ? HTMLElementTagNameMap[T] 50 | : T extends CustomTagType 51 | ? E 52 | : never; 53 | 54 | export function elem( 55 | tag: T, 56 | attrs: ElementProps> = {}, 57 | children: (Element | string | Text)[] = [], 58 | extras: ElementExtras> & ElementEventListeners> = {}, 59 | ): ElementType { 60 | const element = typeof tag === "string" ? document.createElement(tag) : new tag(); 61 | 62 | Object.assign( 63 | element, 64 | Object.fromEntries(Object.entries(attrs).filter(([_k, v]) => v !== undefined)), 65 | ); 66 | 67 | if (extras.classList) extras.classList.forEach(c => c && element.classList.add(c)); 68 | if (extras.dataset && (element instanceof HTMLElement || element instanceof SVGElement)) 69 | Object.entries(extras.dataset) 70 | .filter(([_k, v]) => v !== undefined) 71 | .forEach(([k, v]) => (element.dataset[k] = v)); 72 | 73 | const childNodes = children.map(e => 74 | typeof e === "string" ? document.createTextNode(e) : e, 75 | ); 76 | element.append(...childNodes); 77 | 78 | if (extras._tap) extras._tap(element as ElementType); 79 | 80 | if (extras.style) 81 | Object.entries(extras.style).forEach(([k, v]) => 82 | k.startsWith("--") 83 | ? v 84 | ? element.style.setProperty(k, v) 85 | : element.style.removeProperty(k) 86 | : // @ts-expect-error blind assignment 87 | (element.style[k] = v), 88 | ); 89 | 90 | for (const [key, value] of Object.entries(extras)) { 91 | if (!key.startsWith("_on")) continue; 92 | element.addEventListener(key.substring(3), value.bind(element)); 93 | } 94 | 95 | return element as ElementType; 96 | } 97 | 98 | export function rewrite(element: Element, children: (Element | string | Text)[] = []) { 99 | element.innerHTML = ""; 100 | const nodes = children.map(e => (typeof e === "string" ? document.createTextNode(e) : e)); 101 | element.append(...nodes); 102 | } 103 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "specifiers": { 4 | "jsr:@luca/esbuild-deno-loader@0.11": "0.11.0", 5 | "jsr:@std/bytes@^1.0.2": "1.0.3", 6 | "jsr:@std/cli@1": "1.0.6", 7 | "jsr:@std/dotenv@0.225": "0.225.2", 8 | "jsr:@std/encoding@^1.0.5": "1.0.5", 9 | "jsr:@std/path@1": "1.0.8", 10 | "jsr:@std/path@^1.0.6": "1.0.8", 11 | "npm:esbuild@0.24": "0.24.0" 12 | }, 13 | "jsr": { 14 | "@luca/esbuild-deno-loader@0.11.0": { 15 | "integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c", 16 | "dependencies": [ 17 | "jsr:@std/bytes", 18 | "jsr:@std/encoding", 19 | "jsr:@std/path@^1.0.6" 20 | ] 21 | }, 22 | "@std/bytes@1.0.3": { 23 | "integrity": "e5d5b9e685966314e4edb4be60dfc4bd7624a075bfd4ec8109252b4320f76452" 24 | }, 25 | "@std/cli@1.0.6": { 26 | "integrity": "d22d8b38c66c666d7ad1f2a66c5b122da1704f985d3c47f01129f05abb6c5d3d" 27 | }, 28 | "@std/dotenv@0.225.2": { 29 | "integrity": "e2025dce4de6c7bca21dece8baddd4262b09d5187217e231b033e088e0c4dd23" 30 | }, 31 | "@std/encoding@1.0.5": { 32 | "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" 33 | }, 34 | "@std/path@1.0.8": { 35 | "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" 36 | } 37 | }, 38 | "npm": { 39 | "@esbuild/aix-ppc64@0.24.0": { 40 | "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==" 41 | }, 42 | "@esbuild/android-arm64@0.24.0": { 43 | "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==" 44 | }, 45 | "@esbuild/android-arm@0.24.0": { 46 | "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==" 47 | }, 48 | "@esbuild/android-x64@0.24.0": { 49 | "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==" 50 | }, 51 | "@esbuild/darwin-arm64@0.24.0": { 52 | "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==" 53 | }, 54 | "@esbuild/darwin-x64@0.24.0": { 55 | "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==" 56 | }, 57 | "@esbuild/freebsd-arm64@0.24.0": { 58 | "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==" 59 | }, 60 | "@esbuild/freebsd-x64@0.24.0": { 61 | "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==" 62 | }, 63 | "@esbuild/linux-arm64@0.24.0": { 64 | "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==" 65 | }, 66 | "@esbuild/linux-arm@0.24.0": { 67 | "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==" 68 | }, 69 | "@esbuild/linux-ia32@0.24.0": { 70 | "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==" 71 | }, 72 | "@esbuild/linux-loong64@0.24.0": { 73 | "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==" 74 | }, 75 | "@esbuild/linux-mips64el@0.24.0": { 76 | "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==" 77 | }, 78 | "@esbuild/linux-ppc64@0.24.0": { 79 | "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==" 80 | }, 81 | "@esbuild/linux-riscv64@0.24.0": { 82 | "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==" 83 | }, 84 | "@esbuild/linux-s390x@0.24.0": { 85 | "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==" 86 | }, 87 | "@esbuild/linux-x64@0.24.0": { 88 | "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==" 89 | }, 90 | "@esbuild/netbsd-x64@0.24.0": { 91 | "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==" 92 | }, 93 | "@esbuild/openbsd-arm64@0.24.0": { 94 | "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==" 95 | }, 96 | "@esbuild/openbsd-x64@0.24.0": { 97 | "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==" 98 | }, 99 | "@esbuild/sunos-x64@0.24.0": { 100 | "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==" 101 | }, 102 | "@esbuild/win32-arm64@0.24.0": { 103 | "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==" 104 | }, 105 | "@esbuild/win32-ia32@0.24.0": { 106 | "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==" 107 | }, 108 | "@esbuild/win32-x64@0.24.0": { 109 | "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==" 110 | }, 111 | "esbuild@0.24.0": { 112 | "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", 113 | "dependencies": [ 114 | "@esbuild/aix-ppc64", 115 | "@esbuild/android-arm", 116 | "@esbuild/android-arm64", 117 | "@esbuild/android-x64", 118 | "@esbuild/darwin-arm64", 119 | "@esbuild/darwin-x64", 120 | "@esbuild/freebsd-arm64", 121 | "@esbuild/freebsd-x64", 122 | "@esbuild/linux-arm", 123 | "@esbuild/linux-arm64", 124 | "@esbuild/linux-ia32", 125 | "@esbuild/linux-loong64", 126 | "@esbuild/linux-mips64el", 127 | "@esbuild/linux-ppc64", 128 | "@esbuild/linux-riscv64", 129 | "@esbuild/linux-s390x", 130 | "@esbuild/linux-x64", 131 | "@esbuild/netbsd-x64", 132 | "@esbuild/openbsd-arm64", 133 | "@esbuild/openbsd-x64", 134 | "@esbuild/sunos-x64", 135 | "@esbuild/win32-arm64", 136 | "@esbuild/win32-ia32", 137 | "@esbuild/win32-x64" 138 | ] 139 | } 140 | } 141 | } 142 | --------------------------------------------------------------------------------