From cc6741a2b69f3e7015b65584ae0331d6ca08a0fe Mon Sep 17 00:00:00 2001 From: andylovescode Date: Sat, 9 Aug 2025 15:40:56 -0700 Subject: [PATCH 01/13] Core: Add support for intrinsic components --- .changeset/lovely-teeth-kiss.md | 5 + packages/vortex-core/src/context.ts | 4 +- packages/vortex-core/src/index.ts | 3 +- packages/vortex-core/src/render/index.ts | 96 ++-- packages/vortex-core/src/render/reconciler.ts | 441 +++++++++--------- .../vortex-core/src/setImmediate.polyfill.ts | 16 +- 6 files changed, 295 insertions(+), 270 deletions(-) create mode 100644 .changeset/lovely-teeth-kiss.md diff --git a/.changeset/lovely-teeth-kiss.md b/.changeset/lovely-teeth-kiss.md new file mode 100644 index 0000000..30f5996 --- /dev/null +++ b/.changeset/lovely-teeth-kiss.md @@ -0,0 +1,5 @@ +--- +"@vortexjs/core": minor +--- + +Add support for intrinsic components diff --git a/packages/vortex-core/src/context.ts b/packages/vortex-core/src/context.ts index ee56f56..b2b390e 100644 --- a/packages/vortex-core/src/context.ts +++ b/packages/vortex-core/src/context.ts @@ -1,7 +1,7 @@ import { unwrap } from "@vortexjs/common"; import type { JSXNode } from "./jsx/jsx-common"; -import { type Signal, type SignalOrValue, toSignal } from "./signal"; import { clearImmediate, setImmediate } from "./setImmediate.polyfill"; +import { type Signal, type SignalOrValue, toSignal } from "./signal"; export interface Context { (props: { value: SignalOrValue; children: JSXNode }): JSXNode; @@ -37,7 +37,7 @@ export class StreamingContext { private updateCallbackImmediate = 0; private updateCallbacks = new Set<() => void>(); private loadingCounter = 0; - private onDoneLoadingCallback = () => { }; + private onDoneLoadingCallback = () => {}; onDoneLoading: Promise; constructor() { diff --git a/packages/vortex-core/src/index.ts b/packages/vortex-core/src/index.ts index ffe4546..dbbcc98 100644 --- a/packages/vortex-core/src/index.ts +++ b/packages/vortex-core/src/index.ts @@ -1,7 +1,8 @@ export * from "./context"; +export * from "./intrinsic"; export * from "./jsx/jsx-common"; export * from "./lifetime"; export * from "./render"; +export * from "./setImmediate.polyfill"; export * from "./signal"; export * from "./std"; -export * from "./setImmediate.polyfill"; diff --git a/packages/vortex-core/src/render/index.ts b/packages/vortex-core/src/render/index.ts index 02d0207..89a0705 100644 --- a/packages/vortex-core/src/render/index.ts +++ b/packages/vortex-core/src/render/index.ts @@ -4,75 +4,77 @@ import type { JSXNode } from "../jsx/jsx-common"; import { Lifetime } from "../lifetime"; import { effect, type Store, store } from "../signal"; import { - FLElement, - FLFragment, - type FLNode, - FLPortal, - FLText, + FLElement, + FLFragment, + type FLNode, + FLPortal, + FLText, } from "./fragments"; import { Reconciler } from "./reconciler"; +import type { IntrinsicImplementation } from "../intrinsic"; export * as FL from "./fragments"; export interface Renderer { - createNode(type: string, hydration?: HydrationContext): RendererNode; - setAttribute(node: RendererNode, name: string, value: any): void; - createTextNode(hydration?: HydrationContext): RendererNode; - setTextContent(node: RendererNode, text: string): void; - setChildren(node: RendererNode, children: RendererNode[]): void; - getHydrationContext(node: RendererNode): HydrationContext; - addEventListener( - node: RendererNode, - name: string, - event: (event: any) => void, - ): Lifetime; - bindValue(node: RendererNode, name: string, value: Store): Lifetime; - setStyle(node: RendererNode, name: string, value: string | undefined): void; + createNode(type: string, hydration?: HydrationContext): RendererNode; + setAttribute(node: RendererNode, name: string, value: any): void; + createTextNode(hydration?: HydrationContext): RendererNode; + setTextContent(node: RendererNode, text: string): void; + setChildren(node: RendererNode, children: RendererNode[]): void; + getHydrationContext(node: RendererNode): HydrationContext; + addEventListener( + node: RendererNode, + name: string, + event: (event: any) => void, + ): Lifetime; + bindValue(node: RendererNode, name: string, value: Store): Lifetime; + setStyle(node: RendererNode, name: string, value: string | undefined): void; + implementations?: IntrinsicImplementation[]; } export interface RenderProps { - renderer: Renderer, - root: RendererNode, - component: JSXNode, - context?: ContextScope, + renderer: Renderer, + root: RendererNode, + component: JSXNode, + context?: ContextScope, }; function internalRender({ renderer, root, component, context }: RenderProps): Lifetime { - using _trace = trace("Initial page render"); + using _trace = trace("Initial page render"); - const reconciler = new Reconciler(renderer, root); - const lt = new Lifetime(); + const reconciler = new Reconciler(renderer, root); + const lt = new Lifetime(); - const flNode = reconciler.render({ - node: component, - hydration: renderer.getHydrationContext(root), - lt, - context: context ?? ContextScope.current ?? new ContextScope(), - }); + const flNode = reconciler.render({ + node: component, + hydration: renderer.getHydrationContext(root), + lt, + context: context ?? ContextScope.current ?? new ContextScope(), + }); - const portal = new FLPortal(root, renderer); + const portal = new FLPortal(root, renderer); - portal.children = [flNode]; + portal.children = [flNode]; - return lt; + return lt; } export function render( - renderer: Renderer, - root: RendererNode, - component: JSXNode, + renderer: Renderer, + root: RendererNode, + component: JSXNode, ): Lifetime; export function render(props: RenderProps): Lifetime; export function render( - propsOrRenderer: Renderer | RenderProps, root?: RendererNode, component?: JSXNode + propsOrRenderer: Renderer | RenderProps, root?: RendererNode, component?: JSXNode ) { - if ("renderer" in propsOrRenderer) { - return internalRender(propsOrRenderer); - } else { - return internalRender({ - renderer: propsOrRenderer, - root, - component, - }); - } + if ("renderer" in propsOrRenderer) { + return internalRender(propsOrRenderer); + } else { + return internalRender({ + renderer: propsOrRenderer, + root, + component, + }); + } } diff --git a/packages/vortex-core/src/render/reconciler.ts b/packages/vortex-core/src/render/reconciler.ts index aaeb765..651f21f 100644 --- a/packages/vortex-core/src/render/reconciler.ts +++ b/packages/vortex-core/src/render/reconciler.ts @@ -5,220 +5,233 @@ import type { JSXNode } from "../jsx/jsx-common"; import { Lifetime } from "../lifetime"; import { FLElement, FLFragment, FLText, type FLNode } from "./fragments"; import { effect, store, type Store } from "../signal"; +import { IntrinsicKey } from "../intrinsic"; export class Reconciler { - constructor( - private renderer: Renderer, - private root: RendererNode, - ) { } - - render({ node, hydration, lt, context }: { - node: JSXNode, - hydration: HydrationContext | undefined, - lt: Lifetime, - context: ContextScope, - }): FLNode { - if (node === undefined || node === null) { - return new FLFragment(); - } - - if (Array.isArray(node)) { - return this.render({ - node: { - type: "fragment", - children: node - }, hydration, lt, context - }); - } - - switch (node.type) { - case "fragment": { - const frag = new FLFragment(); - frag.children = node.children.map((child) => - this.render({ node: child, hydration, lt, context }), - ); - return frag; - } - case "text": { - return new FLText( - node.value.toString(), - this.renderer, - hydration, - ); - } - case "element": { - const element = new FLElement( - node.name, - this.renderer, - hydration, - ); - - const elmHydration = this.renderer.getHydrationContext( - unwrap(element.rendererNode), - ); - - element.children = node.children.map((child) => - this.render({ node: child, hydration: elmHydration, lt, context }), - ); - - for (const [name, value] of Object.entries(node.attributes)) { - value - .subscribe((next) => { - element.setAttribute(name, next); - }) - .cascadesFrom(lt); - } - - for (const [name, value] of Object.entries(node.bindings)) { - this.renderer - .bindValue(unwrap(element.rendererNode), name, value) - .cascadesFrom(lt); - } - - for (const [name, handler] of Object.entries( - node.eventHandlers, - )) { - this.renderer - .addEventListener( - unwrap(element.rendererNode), - name, - handler, - ) - .cascadesFrom(lt); - } - - for (const [name, value] of Object.entries(node.styles)) { - value - .subscribe((next) => { - this.renderer.setStyle( - unwrap(element.rendererNode), - name, - next, - ); - }) - .cascadesFrom(lt); - } - - const users = [node.use].flat() as (( - ref: RendererNode, - ) => void)[]; - - for (const user of users) { - user(unwrap(element.rendererNode)); - } - - return element; - } - case "component": { - using _hook = Lifetime.changeHookLifetime(lt); - using _context = ContextScope.setCurrent(context); - using _trace = trace(`Rendering ${node.impl.name}`); - - const result = node.impl(node.props); - - return this.render({ node: result, hydration, lt, context }); - } - case "dynamic": { - const swapContainer = new FLFragment(); - - effect( - (get, { lifetime }) => { - const newRender = this.render({ - node: get(node.value), - hydration, - lt: lifetime, - context, - }); - - swapContainer.children = [newRender]; - }, - undefined, - lt, - ); - - return swapContainer; - } - case "list": { - type ListType = unknown; - - const swapContainer = new FLFragment(); - const renderMap: Map< - string, - { - node: FLNode; - item: Store; - lifetime: Lifetime; - } - > = new Map(); - - const container = new FLFragment(); - let lastKeyOrder = ""; - - effect((get) => { - const items = get(node.items); - const newKeys = items.map((item, idx) => - node.getKey(item, idx), - ); - - for (const key of renderMap.keys()) { - if (!newKeys.includes(key)) { - const entry = unwrap(renderMap.get(key)); - entry.lifetime.close(); - renderMap.delete(key); - } - } - - for (const key of newKeys) { - if (!renderMap.has(key)) { - const item = items[newKeys.indexOf(key)]; - const itemStore = store(item); - const itemLifetime = new Lifetime(); - using _hl = - Lifetime.changeHookLifetime(itemLifetime); - - const renderedItem = this.render({ - node: node.renderItem(item, newKeys.indexOf(key)), - hydration, - lt: itemLifetime, - context, - }); - - renderMap.set(key, { - node: renderedItem, - item: itemStore, - lifetime: itemLifetime, - }); - } - } - - const newKeyOrder = newKeys.join("|||"); - - if (newKeyOrder !== lastKeyOrder) { - lastKeyOrder = newKeyOrder; - container.children = newKeys.map( - (key) => unwrap(renderMap.get(key)).node, - ); - } - }); - - return container; - } - case "context": { - const forked = context.fork(); - using _newScope = ContextScope.setCurrent(forked); - - forked.addContext(node.id, node.value); - - return this.render({ - node: node.children, hydration, lt, context: forked - }); - } - default: { - unreachable( - node, - `No rendering implementation for ${JSON.stringify(node)}`, - ); - } - } - } + constructor( + private renderer: Renderer, + private root: RendererNode, + ) { } + + render({ node, hydration, lt, context }: { + node: JSXNode, + hydration: HydrationContext | undefined, + lt: Lifetime, + context: ContextScope, + }): FLNode { + if (node === undefined || node === null) { + return new FLFragment(); + } + + if (Array.isArray(node)) { + return this.render({ + node: { + type: "fragment", + children: node + }, hydration, lt, context + }); + } + + switch (node.type) { + case "fragment": { + const frag = new FLFragment(); + frag.children = node.children.map((child) => + this.render({ node: child, hydration, lt, context }), + ); + return frag; + } + case "text": { + return new FLText( + node.value.toString(), + this.renderer, + hydration, + ); + } + case "element": { + const element = new FLElement( + node.name, + this.renderer, + hydration, + ); + + const elmHydration = this.renderer.getHydrationContext( + unwrap(element.rendererNode), + ); + + element.children = node.children.map((child) => + this.render({ node: child, hydration: elmHydration, lt, context }), + ); + + for (const [name, value] of Object.entries(node.attributes)) { + value + .subscribe((next) => { + element.setAttribute(name, next); + }) + .cascadesFrom(lt); + } + + for (const [name, value] of Object.entries(node.bindings)) { + this.renderer + .bindValue(unwrap(element.rendererNode), name, value) + .cascadesFrom(lt); + } + + for (const [name, handler] of Object.entries( + node.eventHandlers, + )) { + this.renderer + .addEventListener( + unwrap(element.rendererNode), + name, + handler, + ) + .cascadesFrom(lt); + } + + for (const [name, value] of Object.entries(node.styles)) { + value + .subscribe((next) => { + this.renderer.setStyle( + unwrap(element.rendererNode), + name, + next, + ); + }) + .cascadesFrom(lt); + } + + const users = [node.use].flat() as (( + ref: RendererNode, + ) => void)[]; + + for (const user of users) { + user(unwrap(element.rendererNode)); + } + + return element; + } + case "component": { + using _hook = Lifetime.changeHookLifetime(lt); + using _context = ContextScope.setCurrent(context); + using _trace = trace(`Rendering ${node.impl.name}`); + + let comp = node.impl; + + if (IntrinsicKey in comp && typeof comp[IntrinsicKey] === "string") { + const intrinsicImpl = this.renderer.implementations?.find( + (impl) => impl.intrinsic === comp, + ); + + if (intrinsicImpl) { + comp = intrinsicImpl.implementation as any; + } + } + + const result = comp(node.props); + + return this.render({ node: result, hydration, lt, context }); + } + case "dynamic": { + const swapContainer = new FLFragment(); + + effect( + (get, { lifetime }) => { + const newRender = this.render({ + node: get(node.value), + hydration, + lt: lifetime, + context, + }); + + swapContainer.children = [newRender]; + }, + undefined, + lt, + ); + + return swapContainer; + } + case "list": { + type ListType = unknown; + + const swapContainer = new FLFragment(); + const renderMap: Map< + string, + { + node: FLNode; + item: Store; + lifetime: Lifetime; + } + > = new Map(); + + const container = new FLFragment(); + let lastKeyOrder = ""; + + effect((get) => { + const items = get(node.items); + const newKeys = items.map((item, idx) => + node.getKey(item, idx), + ); + + for (const key of renderMap.keys()) { + if (!newKeys.includes(key)) { + const entry = unwrap(renderMap.get(key)); + entry.lifetime.close(); + renderMap.delete(key); + } + } + + for (const key of newKeys) { + if (!renderMap.has(key)) { + const item = items[newKeys.indexOf(key)]; + const itemStore = store(item); + const itemLifetime = new Lifetime(); + using _hl = + Lifetime.changeHookLifetime(itemLifetime); + + const renderedItem = this.render({ + node: node.renderItem(item, newKeys.indexOf(key)), + hydration, + lt: itemLifetime, + context, + }); + + renderMap.set(key, { + node: renderedItem, + item: itemStore, + lifetime: itemLifetime, + }); + } + } + + const newKeyOrder = newKeys.join("|||"); + + if (newKeyOrder !== lastKeyOrder) { + lastKeyOrder = newKeyOrder; + container.children = newKeys.map( + (key) => unwrap(renderMap.get(key)).node, + ); + } + }); + + return container; + } + case "context": { + const forked = context.fork(); + using _newScope = ContextScope.setCurrent(forked); + + forked.addContext(node.id, node.value); + + return this.render({ + node: node.children, hydration, lt, context: forked + }); + } + default: { + unreachable( + node, + `No rendering implementation for ${JSON.stringify(node)}`, + ); + } + } + } } diff --git a/packages/vortex-core/src/setImmediate.polyfill.ts b/packages/vortex-core/src/setImmediate.polyfill.ts index 987cc81..4c22d33 100644 --- a/packages/vortex-core/src/setImmediate.polyfill.ts +++ b/packages/vortex-core/src/setImmediate.polyfill.ts @@ -1,7 +1,11 @@ -export const setImmediate: typeof globalThis.setImmediate = globalThis.setImmediate ?? ((callback: (...args: any[]) => void, ...args: any[]): number => { - return setTimeout(callback, 0, ...args) as unknown as number; -}); +export const setImmediate: typeof globalThis.setImmediate = + globalThis.setImmediate ?? + ((callback: (...args: any[]) => void, ...args: any[]): number => { + return setTimeout(callback, 0, ...args) as unknown as number; + }); -export const clearImmediate: typeof globalThis.clearImmediate = globalThis.clearImmediate ?? ((instance: number) => { - clearTimeout(instance); -}); +export const clearImmediate: typeof globalThis.clearImmediate = + globalThis.clearImmediate ?? + ((instance: number) => { + clearTimeout(instance); + }); From dc695c7e90a995e8421a4ada72afba5c44135b29 Mon Sep 17 00:00:00 2001 From: andylovescode Date: Sat, 9 Aug 2025 16:33:50 -0700 Subject: [PATCH 02/13] Core: Add optional context API --- .changeset/mean-beds-try.md | 5 + packages/vortex-core/src/context.ts | 235 ++++++++++++++-------------- 2 files changed, 125 insertions(+), 115 deletions(-) create mode 100644 .changeset/mean-beds-try.md diff --git a/.changeset/mean-beds-try.md b/.changeset/mean-beds-try.md new file mode 100644 index 0000000..5862f9a --- /dev/null +++ b/.changeset/mean-beds-try.md @@ -0,0 +1,5 @@ +--- +"@vortexjs/core": minor +--- + +Add optional context API diff --git a/packages/vortex-core/src/context.ts b/packages/vortex-core/src/context.ts index b2b390e..dd9f87d 100644 --- a/packages/vortex-core/src/context.ts +++ b/packages/vortex-core/src/context.ts @@ -4,146 +4,151 @@ import { clearImmediate, setImmediate } from "./setImmediate.polyfill"; import { type Signal, type SignalOrValue, toSignal } from "./signal"; export interface Context { - (props: { value: SignalOrValue; children: JSXNode }): JSXNode; - use(): Signal; + (props: { value: SignalOrValue; children: JSXNode }): JSXNode; + use(): Signal; + useOptional(): Signal | undefined; } export function createContext(name = "Unnamed"): Context { - const id = crypto.randomUUID(); - - const result = (props: { - value: SignalOrValue; - children: JSXNode; - }): JSXNode => { - return { - type: "context", - id, - value: toSignal(props.value), - children: props.children, - }; - }; - - result.use = () => { - return unwrap( - useContextScope().contexts[id], - `Context "${name}" not found, you may have forgotten to wrap your component in the context provider.`, - ); - }; - - return result; + const id = crypto.randomUUID(); + + const result = (props: { + value: SignalOrValue; + children: JSXNode; + }): JSXNode => { + return { + type: "context", + id, + value: toSignal(props.value), + children: props.children, + }; + }; + + result.use = () => { + return unwrap( + useContextScope().contexts[id], + `Context "${name}" not found, you may have forgotten to wrap your component in the context provider.`, + ); + }; + + result.useOptional = () => { + return useContextScope().contexts[id]; + } + + return result; } export class StreamingContext { - private updateCallbackImmediate = 0; - private updateCallbacks = new Set<() => void>(); - private loadingCounter = 0; - private onDoneLoadingCallback = () => {}; - onDoneLoading: Promise; - - constructor() { - this.onDoneLoading = new Promise((resolve) => { - this.onDoneLoadingCallback = resolve; - }); - } - - onUpdate(callback: () => void): () => void { - this.updateCallbacks.add(callback); - - return () => { - this.updateCallbacks.delete(callback); - }; - } - - markLoading() { - const self = this; - - this.loadingCounter++; - - return { - [Symbol.dispose]() { - self.loadingCounter--; - self.updated(); - }, - }; - } - - updated() { - if (this.updateCallbackImmediate) { - clearImmediate(this.updateCallbackImmediate); - } - - // biome-ignore lint/complexity/noUselessThisAlias: without it, shit breaks - const self = this; - - this.updateCallbackImmediate = setImmediate(() => { - self.updateCallbackImmediate = 0; - - for (const callback of self.updateCallbacks) { - callback(); - } - - if (self.loadingCounter === 0) { - self.onDoneLoadingCallback(); - } - }) as unknown as number; - } + private updateCallbackImmediate = 0; + private updateCallbacks = new Set<() => void>(); + private loadingCounter = 0; + private onDoneLoadingCallback = () => { }; + onDoneLoading: Promise; + + constructor() { + this.onDoneLoading = new Promise((resolve) => { + this.onDoneLoadingCallback = resolve; + }); + } + + onUpdate(callback: () => void): () => void { + this.updateCallbacks.add(callback); + + return () => { + this.updateCallbacks.delete(callback); + }; + } + + markLoading() { + const self = this; + + this.loadingCounter++; + + return { + [Symbol.dispose]() { + self.loadingCounter--; + self.updated(); + }, + }; + } + + updated() { + if (this.updateCallbackImmediate) { + clearImmediate(this.updateCallbackImmediate); + } + + // biome-ignore lint/complexity/noUselessThisAlias: without it, shit breaks + const self = this; + + this.updateCallbackImmediate = setImmediate(() => { + self.updateCallbackImmediate = 0; + + for (const callback of self.updateCallbacks) { + callback(); + } + + if (self.loadingCounter === 0) { + self.onDoneLoadingCallback(); + } + }) as unknown as number; + } } export class ContextScope { - contexts: Record> = {}; - streaming: StreamingContext = new StreamingContext(); + contexts: Record> = {}; + streaming: StreamingContext = new StreamingContext(); - fork() { - const newScope = new ContextScope(); - newScope.contexts = { ...this.contexts }; - return newScope; - } + fork() { + const newScope = new ContextScope(); + newScope.contexts = { ...this.contexts }; + return newScope; + } - addContext(id: string, value: Signal): void { - this.contexts[id] = value; - } + addContext(id: string, value: Signal): void { + this.contexts[id] = value; + } - static current: ContextScope | null = null; + static current: ContextScope | null = null; - static setCurrent(scope: ContextScope | null) { - const previous = ContextScope.current; + static setCurrent(scope: ContextScope | null) { + const previous = ContextScope.current; - ContextScope.current = scope; + ContextScope.current = scope; - return { - [Symbol.dispose]() { - ContextScope.current = previous; - }, - }; - } + return { + [Symbol.dispose]() { + ContextScope.current = previous; + }, + }; + } } export function useContextScope(): ContextScope { - const scope = ContextScope.current; - if (!scope) { - throw new Error( - "No context scope found, you should have one if you're rendering a component.", - ); - } - return scope; + const scope = ContextScope.current; + if (!scope) { + throw new Error( + "No context scope found, you should have one if you're rendering a component.", + ); + } + return scope; } export function useStreaming(): StreamingContext { - return useContextScope().streaming; + return useContextScope().streaming; } export function useOptionalContextScope(): ContextScope | null { - const scope = ContextScope.current; - if (!scope) { - return null; - } - return scope; + const scope = ContextScope.current; + if (!scope) { + return null; + } + return scope; } export function useOptionalStreaming(): StreamingContext | null { - const scope = useOptionalContextScope(); - if (!scope) { - return null; - } - return scope.streaming; + const scope = useOptionalContextScope(); + if (!scope) { + return null; + } + return scope.streaming; } From 53c9cf878999bf22bad7e1f54ed798735915b9ce Mon Sep 17 00:00:00 2001 From: andylovescode Date: Sat, 9 Aug 2025 16:34:25 -0700 Subject: [PATCH 03/13] Core: Add intrinsic.ts, silly me! --- packages/vortex-core/src/intrinsic.ts | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/vortex-core/src/intrinsic.ts diff --git a/packages/vortex-core/src/intrinsic.ts b/packages/vortex-core/src/intrinsic.ts new file mode 100644 index 0000000..84973a7 --- /dev/null +++ b/packages/vortex-core/src/intrinsic.ts @@ -0,0 +1,37 @@ +import type { JSXNode } from "./jsx/jsx-common"; + +export const IntrinsicKey = "~vortex:intrinsic"; + +export type IntrinsicComponent = { + "~vortex:intrinsic": Id; +} & ((args: Args) => JSXNode); + +export function intrinsic(id: Id) { + const impl = (() => { + throw new Error( + `Intrinsic component "${id}" is not implemented. Please provide an implementation.`, + ); + }) as unknown as IntrinsicComponent; + + impl[IntrinsicKey] = id; + + return impl; +} + +export type IntrinsicImplementation< + Args extends {} = {}, + Id extends string = string, +> = { + intrinsic: IntrinsicComponent; + implementation: (args: Args) => JSXNode; +}; + +export function implementIntrinsic( + intrinsic: IntrinsicComponent, + implementation: (args: Args) => JSXNode, +): IntrinsicImplementation { + return { + intrinsic, + implementation, + }; +} From fab91bb3ac5d8ac76b44f8b06e2ad5f6003b6361 Mon Sep 17 00:00:00 2001 From: andylovescode Date: Sat, 9 Aug 2025 16:37:01 -0700 Subject: [PATCH 04/13] Common: Denamespace SKL --- .changeset/giant-buckets-knock.md | 5 + packages/vortex-common/src/index.ts | 2 +- packages/vortex-common/src/skl.ts | 1016 +++++++++++++-------------- 3 files changed, 513 insertions(+), 510 deletions(-) create mode 100644 .changeset/giant-buckets-knock.md diff --git a/.changeset/giant-buckets-knock.md b/.changeset/giant-buckets-knock.md new file mode 100644 index 0000000..3d571db --- /dev/null +++ b/.changeset/giant-buckets-knock.md @@ -0,0 +1,5 @@ +--- +"@vortexjs/common": patch +--- + +Denamespace SKL diff --git a/packages/vortex-common/src/index.ts b/packages/vortex-common/src/index.ts index b6ecb5d..b275b3f 100644 --- a/packages/vortex-common/src/index.ts +++ b/packages/vortex-common/src/index.ts @@ -5,7 +5,7 @@ export * from "./deep-partial"; export * from "./dev"; export * from "./hash"; export * from "./npm"; -export * from "./skl"; +export * as SKL from "./skl"; export * from "./smolify"; export * from "./ultraglobal"; export * from "./unreachable"; diff --git a/packages/vortex-common/src/skl.ts b/packages/vortex-common/src/skl.ts index ca65edb..7b67636 100644 --- a/packages/vortex-common/src/skl.ts +++ b/packages/vortex-common/src/skl.ts @@ -2,513 +2,511 @@ // SKL - A one file library for efficient serialization // -export namespace SKL { - export type Identifier = ["identifier", string]; - - export type String = ["string", string]; - - export type Number = ["number", number]; - - export const keywords = { - null: "Null", - true: "True", - false: "False", - undefined: "Undefined", - } as const; - - export const symbols = { - "(": "LeftParenthesis", - ")": "RightParenthesis", - "[": "LeftSquareBracket", - "]": "RightSquareBracket", - "{": "LeftCurlyBracket", - "}": "RightCurlyBracket", - ",": "Comma", - ":": "Colon", - ";": "Semicolon", - ".": "Dot", - "=": "Equals", - "+": "Plus", - "-": "Minus", - "*": "Asterisk", - "/": "Slash", - "%": "Percent", - "!": "ExclamationMark", - "<": "LeftAngularBracket", - ">": "RightAngularBracket", - } as const; - - export type Keyword = ["keyword", (typeof keywords)[keyof typeof keywords]]; - - export type Symbol = ["symbol", (typeof symbols)[keyof typeof symbols]]; - - export type Token = Identifier | String | Number | Keyword | Symbol; - - export function isWhitespace(char: string) { - return char === " " || char === "\t" || char === "\n" || char === "\r"; - } - - export function isAlphabetic(char: string) { - return ( - (char >= "a" && char <= "z") || - (char >= "A" && char <= "Z") || - char === "_" - ); - } - export function isNumeric(char: string) { - return char >= "0" && char <= "9"; - } - - export function isAlphanumeric(char: string) { - return isAlphabetic(char) || isNumeric(char); - } - - export function* lex(str: string): Generator { - let i = 0; - - const peek = () => str[i] ?? ""; - const read = () => str[i++] ?? ""; - const seek = () => { - i++; - }; - - while (i < str.length) { - const indicator = peek(); - - if (isWhitespace(indicator)) { - seek(); - continue; - } - - if (isAlphabetic(indicator)) { - let str = ""; - - while (isAlphanumeric(peek())) { - str += read(); - } - - if (str in keywords) { - yield ["keyword", keywords[str as keyof typeof keywords]]; - } else { - yield ["identifier", str]; - } - - continue; - } - - if (isNumeric(indicator)) { - let numStr = ""; - - while (isNumeric(peek()) || peek() === ".") { - numStr += read(); - } - - yield ["number", Number(numStr)]; - - continue; - } - - if (indicator === '"' || indicator === "'" || indicator === "`") { - const quote = read(); - let acc = ""; - - while (peek() !== quote && i < str.length) { - const char = read(); - - if (char === "\\") { - // Handle escape sequences - const nextChar = peek(); - const escapers = { - n: "\n", - t: "\t", - r: "\r", - b: "\b", - f: "\f", - v: "\v", - "\\": "\\", - }; - if (nextChar in escapers) { - acc += escapers[nextChar as keyof typeof escapers]; - seek(); // consume the escape character - } else if (nextChar === quote) { - acc += quote; // handle escaped quote - seek(); // consume the closing quote - } else { - acc += char; // just add the backslash if no valid escape sequence - } - } else { - acc += char; - } - } - - if (peek() === quote) { - seek(); // consume the closing quote - } - - yield ["string", acc]; - - continue; - } - - if (indicator in symbols) { - yield ["symbol", symbols[indicator as keyof typeof symbols]]; - seek(); // consume the symbol - continue; - } - - // If we reach here, it means we encountered an unknown character - throw new Error( - `Unknown character: '${indicator}' at position ${i}`, - ); - } - } - - export interface ParseContext { - tokens: Token[]; - current: number; - } - - export function parseContext_create(tokens: Token[]): ParseContext { - return { - tokens, - current: 0, - }; - } - - export function parseContext_next(context: ParseContext): Token | null { - if (context.current >= context.tokens.length) { - return null; // No more tokens - } - return context.tokens[context.current++] ?? null; - } - - export function parseContext_peek(context: ParseContext): Token | null { - if (context.current >= context.tokens.length) { - return null; // No more tokens - } - return context.tokens[context.current] ?? null; - } - - export function parseContext_read(context: ParseContext): Token | null { - const token = parseContext_peek(context); - if (token !== null) { - context.current++; - } - return token; - } - - export function parseContext_readObj(context: ParseContext) { - let indicator = parseContext_read(context); - - if (indicator === null) { - throw new Error("Unexpected end of input"); - } - - if (indicator[0] === "number") { - return indicator[1]; - } - - if (indicator[0] === "string") { - return indicator[1]; - } - - if (indicator[0] === "keyword") { - if (indicator[1] === keywords.null) { - return null; - } - if (indicator[1] === keywords.undefined) { - return undefined; - } - if (indicator[1] === keywords.true) { - return true; - } - if (indicator[1] === keywords.false) { - return false; - } - throw new Error(`Unexpected keyword: ${indicator[1]}`); - } - - let clazz = null; - - if (indicator[0] === "identifier") { - clazz = indicator[1]; - indicator = parseContext_read(context); - } - - if (indicator === null) { - throw new Error("Unexpected end of input (after reading class)"); - } - - if (indicator[0] === "symbol" && indicator[1] === "LeftParenthesis") { - const kv: Record = {}; - - while (true) { - const key = parseContext_read(context); - - if (key === null) { - throw new Error( - "Unexpected end of input (when trying to read key)", - ); - } - - if (key[0] === "symbol" && key[1] === "RightParenthesis") { - break; // End of object - } - - if (key[0] !== "identifier" && key[0] !== "string") { - throw new Error( - `Expected identifier or string, got ${key[0]}`, - ); - } - - const equals = parseContext_read(context); - - if ( - equals === null || - equals[0] !== "symbol" || - equals[1] !== "Equals" - ) { - throw new Error( - `Expected '=', got ${equals ? equals[0] : "end of input"}`, - ); - } - - const keyName = key[1]; - - const value = parseContext_readObj(context); - - kv[keyName] = value; - } - - if (clazz !== null) { - if (clazz === "date") { - return new Date(kv.unix as number); - } - if (clazz === "set") { - return new Set(kv.items as unknown[]); - } - if (clazz === "map") { - const map = new Map(); - for (const [key, value] of kv.entries as [ - unknown, - unknown, - ][]) { - map.set(key, value); - } - return map; - } - throw new Error(`Unknown class: ${clazz}`); - } - - return kv; - } - - if (indicator[0] === "symbol" && indicator[1] === "LeftSquareBracket") { - const arr: unknown[] = []; - - while (true) { - if ( - parseContext_peek(context)?.[0] === "symbol" && - parseContext_peek(context)?.[1] === "RightSquareBracket" - ) { - parseContext_read(context); // consume the closing bracket - break; // End of array - } - - const value = parseContext_readObj(context); - - arr.push(value); - } - - return arr; - } - } - - export function parse(source: string): unknown { - const tokens = [...lex(source)]; - - const context = parseContext_create(tokens); - - const result = parseContext_readObj(context); - - if (context.current < tokens.length) { - throw new Error( - `Unexpected tokens at the end: ${tokens - .slice(context.current) - .map((t) => t[0]) - .join(", ")}`, - ); - } - - return result; - } - - export interface SerializeContext { - output: string; - indentLevel: number; - minified: boolean; - } - - export function serializeContext_create(): SerializeContext { - return { - output: "", - indentLevel: 0, - minified: false, - }; - } - - export function serializeContext_indent(context: SerializeContext): void { - context.indentLevel++; - } - - export function serializeContext_dedent(context: SerializeContext): void { - context.indentLevel = Math.max(0, context.indentLevel - 1); - } - - export function serializeContext_newline(context: SerializeContext): void { - if (context.minified) { - serializeContext_write(context, " "); - return; - } - serializeContext_write(context, "\n"); - serializeContext_write(context, " ".repeat(context.indentLevel)); - } - - export function serializeContext_write( - context: SerializeContext, - str: string, - ): void { - context.output += str; - } - - export function escapeStr(str: string): string { - let minimumLength = Number.POSITIVE_INFINITY; - let result = ""; - - for (const container of [`"`, `'`, "`"]) { - let current = ""; - - current += container; - - for (const char of str) { - if (char === container) { - current += `\\${char}`; - } else if (char === "\\") { - current += "\\\\"; - } else if (char === "\n") { - current += "\\n"; - } else if (char === "\t") { - current += "\\t"; - } else if (char === "\r") { - current += "\\r"; - } else { - current += char; - } - } - - current += container; - - if (current.length < minimumLength) { - minimumLength = current.length; - result = current; - } - } - - return result; - } - - export function serializeContext_writeObject( - context: SerializeContext, - obj: unknown, - ) { - if (typeof obj === "number") { - serializeContext_write(context, obj.toString()); - return; - } - if (typeof obj === "string") { - serializeContext_write(context, escapeStr(obj)); // Use JSON.stringify to handle escaping - return; - } - if (obj === null) { - serializeContext_write(context, "null"); - return; - } - if (obj === undefined) { - serializeContext_write(context, "undefined"); - return; - } - if (typeof obj === "boolean") { - serializeContext_write(context, obj ? "true" : "false"); - return; - } - if (Array.isArray(obj)) { - serializeContext_write(context, "["); - serializeContext_indent(context); - for (const item of obj) { - serializeContext_newline(context); - serializeContext_writeObject(context, item); - } - serializeContext_dedent(context); - serializeContext_newline(context); - serializeContext_write(context, "]"); - return; - } - if (obj instanceof Date) { - serializeContext_write(context, `date(unix=${obj.getTime()})`); - } - if (obj instanceof Set) { - serializeContext_write(context, "set("); - serializeContext_indent(context); - serializeContext_newline(context); - serializeContext_write(context, "items = "); - serializeContext_writeObject(context, obj.values()); - serializeContext_dedent(context); - serializeContext_newline(context); - serializeContext_write(context, ")"); - } - if (obj instanceof Map) { - serializeContext_write(context, "map("); - serializeContext_indent(context); - serializeContext_newline(context); - serializeContext_write(context, "entries = "); - serializeContext_writeObject(context, obj.entries()); - serializeContext_dedent(context); - serializeContext_newline(context); - serializeContext_write(context, ")"); - } - if (typeof obj === "object") { - serializeContext_write(context, "("); - serializeContext_indent(context); - for (const [key, value] of Object.entries(obj)) { - serializeContext_newline(context); - if ([...key].every(isAlphabetic)) { - serializeContext_write(context, `${key} = `); - } else { - serializeContext_write(context, `${escapeStr(key)} = `); - } - serializeContext_writeObject(context, value); - } - serializeContext_dedent(context); - serializeContext_newline(context); - serializeContext_write(context, ")"); - return; - } - throw new Error(`Unsupported type for serialization: ${typeof obj}`); - } - - export function serialize( - obj: unknown, - opts?: { minified?: boolean }, - ): string { - const context = serializeContext_create(); - - const minified = opts?.minified ?? false; - - context.minified = minified; - - serializeContext_writeObject(context, obj); - return context.output.trim(); - } - - export const stringify = serialize; +export type Identifier = ["identifier", string]; + +export type String = ["string", string]; + +export type Number = ["number", number]; + +export const keywords = { + null: "Null", + true: "True", + false: "False", + undefined: "Undefined", +} as const; + +export const symbols = { + "(": "LeftParenthesis", + ")": "RightParenthesis", + "[": "LeftSquareBracket", + "]": "RightSquareBracket", + "{": "LeftCurlyBracket", + "}": "RightCurlyBracket", + ",": "Comma", + ":": "Colon", + ";": "Semicolon", + ".": "Dot", + "=": "Equals", + "+": "Plus", + "-": "Minus", + "*": "Asterisk", + "/": "Slash", + "%": "Percent", + "!": "ExclamationMark", + "<": "LeftAngularBracket", + ">": "RightAngularBracket", +} as const; + +export type Keyword = ["keyword", (typeof keywords)[keyof typeof keywords]]; + +export type Symbol = ["symbol", (typeof symbols)[keyof typeof symbols]]; + +export type Token = Identifier | String | Number | Keyword | Symbol; + +export function isWhitespace(char: string) { + return char === " " || char === "\t" || char === "\n" || char === "\r"; } + +export function isAlphabetic(char: string) { + return ( + (char >= "a" && char <= "z") || + (char >= "A" && char <= "Z") || + char === "_" + ); +} +export function isNumeric(char: string) { + return char >= "0" && char <= "9"; +} + +export function isAlphanumeric(char: string) { + return isAlphabetic(char) || isNumeric(char); +} + +export function* lex(str: string): Generator { + let i = 0; + + const peek = () => str[i] ?? ""; + const read = () => str[i++] ?? ""; + const seek = () => { + i++; + }; + + while (i < str.length) { + const indicator = peek(); + + if (isWhitespace(indicator)) { + seek(); + continue; + } + + if (isAlphabetic(indicator)) { + let str = ""; + + while (isAlphanumeric(peek())) { + str += read(); + } + + if (str in keywords) { + yield ["keyword", keywords[str as keyof typeof keywords]]; + } else { + yield ["identifier", str]; + } + + continue; + } + + if (isNumeric(indicator)) { + let numStr = ""; + + while (isNumeric(peek()) || peek() === ".") { + numStr += read(); + } + + yield ["number", Number(numStr)]; + + continue; + } + + if (indicator === '"' || indicator === "'" || indicator === "`") { + const quote = read(); + let acc = ""; + + while (peek() !== quote && i < str.length) { + const char = read(); + + if (char === "\\") { + // Handle escape sequences + const nextChar = peek(); + const escapers = { + n: "\n", + t: "\t", + r: "\r", + b: "\b", + f: "\f", + v: "\v", + "\\": "\\", + }; + if (nextChar in escapers) { + acc += escapers[nextChar as keyof typeof escapers]; + seek(); // consume the escape character + } else if (nextChar === quote) { + acc += quote; // handle escaped quote + seek(); // consume the closing quote + } else { + acc += char; // just add the backslash if no valid escape sequence + } + } else { + acc += char; + } + } + + if (peek() === quote) { + seek(); // consume the closing quote + } + + yield ["string", acc]; + + continue; + } + + if (indicator in symbols) { + yield ["symbol", symbols[indicator as keyof typeof symbols]]; + seek(); // consume the symbol + continue; + } + + // If we reach here, it means we encountered an unknown character + throw new Error( + `Unknown character: '${indicator}' at position ${i}`, + ); + } +} + +export interface ParseContext { + tokens: Token[]; + current: number; +} + +export function parseContext_create(tokens: Token[]): ParseContext { + return { + tokens, + current: 0, + }; +} + +export function parseContext_next(context: ParseContext): Token | null { + if (context.current >= context.tokens.length) { + return null; // No more tokens + } + return context.tokens[context.current++] ?? null; +} + +export function parseContext_peek(context: ParseContext): Token | null { + if (context.current >= context.tokens.length) { + return null; // No more tokens + } + return context.tokens[context.current] ?? null; +} + +export function parseContext_read(context: ParseContext): Token | null { + const token = parseContext_peek(context); + if (token !== null) { + context.current++; + } + return token; +} + +export function parseContext_readObj(context: ParseContext) { + let indicator = parseContext_read(context); + + if (indicator === null) { + throw new Error("Unexpected end of input"); + } + + if (indicator[0] === "number") { + return indicator[1]; + } + + if (indicator[0] === "string") { + return indicator[1]; + } + + if (indicator[0] === "keyword") { + if (indicator[1] === keywords.null) { + return null; + } + if (indicator[1] === keywords.undefined) { + return undefined; + } + if (indicator[1] === keywords.true) { + return true; + } + if (indicator[1] === keywords.false) { + return false; + } + throw new Error(`Unexpected keyword: ${indicator[1]}`); + } + + let clazz = null; + + if (indicator[0] === "identifier") { + clazz = indicator[1]; + indicator = parseContext_read(context); + } + + if (indicator === null) { + throw new Error("Unexpected end of input (after reading class)"); + } + + if (indicator[0] === "symbol" && indicator[1] === "LeftParenthesis") { + const kv: Record = {}; + + while (true) { + const key = parseContext_read(context); + + if (key === null) { + throw new Error( + "Unexpected end of input (when trying to read key)", + ); + } + + if (key[0] === "symbol" && key[1] === "RightParenthesis") { + break; // End of object + } + + if (key[0] !== "identifier" && key[0] !== "string") { + throw new Error( + `Expected identifier or string, got ${key[0]}`, + ); + } + + const equals = parseContext_read(context); + + if ( + equals === null || + equals[0] !== "symbol" || + equals[1] !== "Equals" + ) { + throw new Error( + `Expected '=', got ${equals ? equals[0] : "end of input"}`, + ); + } + + const keyName = key[1]; + + const value = parseContext_readObj(context); + + kv[keyName] = value; + } + + if (clazz !== null) { + if (clazz === "date") { + return new Date(kv.unix as number); + } + if (clazz === "set") { + return new Set(kv.items as unknown[]); + } + if (clazz === "map") { + const map = new Map(); + for (const [key, value] of kv.entries as [ + unknown, + unknown, + ][]) { + map.set(key, value); + } + return map; + } + throw new Error(`Unknown class: ${clazz}`); + } + + return kv; + } + + if (indicator[0] === "symbol" && indicator[1] === "LeftSquareBracket") { + const arr: unknown[] = []; + + while (true) { + if ( + parseContext_peek(context)?.[0] === "symbol" && + parseContext_peek(context)?.[1] === "RightSquareBracket" + ) { + parseContext_read(context); // consume the closing bracket + break; // End of array + } + + const value = parseContext_readObj(context); + + arr.push(value); + } + + return arr; + } +} + +export function parse(source: string): unknown { + const tokens = [...lex(source)]; + + const context = parseContext_create(tokens); + + const result = parseContext_readObj(context); + + if (context.current < tokens.length) { + throw new Error( + `Unexpected tokens at the end: ${tokens + .slice(context.current) + .map((t) => t[0]) + .join(", ")}`, + ); + } + + return result; +} + +export interface SerializeContext { + output: string; + indentLevel: number; + minified: boolean; +} + +export function serializeContext_create(): SerializeContext { + return { + output: "", + indentLevel: 0, + minified: false, + }; +} + +export function serializeContext_indent(context: SerializeContext): void { + context.indentLevel++; +} + +export function serializeContext_dedent(context: SerializeContext): void { + context.indentLevel = Math.max(0, context.indentLevel - 1); +} + +export function serializeContext_newline(context: SerializeContext): void { + if (context.minified) { + serializeContext_write(context, " "); + return; + } + serializeContext_write(context, "\n"); + serializeContext_write(context, " ".repeat(context.indentLevel)); +} + +export function serializeContext_write( + context: SerializeContext, + str: string, +): void { + context.output += str; +} + +export function escapeStr(str: string): string { + let minimumLength = Number.POSITIVE_INFINITY; + let result = ""; + + for (const container of [`"`, `'`, "`"]) { + let current = ""; + + current += container; + + for (const char of str) { + if (char === container) { + current += `\\${char}`; + } else if (char === "\\") { + current += "\\\\"; + } else if (char === "\n") { + current += "\\n"; + } else if (char === "\t") { + current += "\\t"; + } else if (char === "\r") { + current += "\\r"; + } else { + current += char; + } + } + + current += container; + + if (current.length < minimumLength) { + minimumLength = current.length; + result = current; + } + } + + return result; +} + +export function serializeContext_writeObject( + context: SerializeContext, + obj: unknown, +) { + if (typeof obj === "number") { + serializeContext_write(context, obj.toString()); + return; + } + if (typeof obj === "string") { + serializeContext_write(context, escapeStr(obj)); // Use JSON.stringify to handle escaping + return; + } + if (obj === null) { + serializeContext_write(context, "null"); + return; + } + if (obj === undefined) { + serializeContext_write(context, "undefined"); + return; + } + if (typeof obj === "boolean") { + serializeContext_write(context, obj ? "true" : "false"); + return; + } + if (Array.isArray(obj)) { + serializeContext_write(context, "["); + serializeContext_indent(context); + for (const item of obj) { + serializeContext_newline(context); + serializeContext_writeObject(context, item); + } + serializeContext_dedent(context); + serializeContext_newline(context); + serializeContext_write(context, "]"); + return; + } + if (obj instanceof Date) { + serializeContext_write(context, `date(unix=${obj.getTime()})`); + } + if (obj instanceof Set) { + serializeContext_write(context, "set("); + serializeContext_indent(context); + serializeContext_newline(context); + serializeContext_write(context, "items = "); + serializeContext_writeObject(context, obj.values()); + serializeContext_dedent(context); + serializeContext_newline(context); + serializeContext_write(context, ")"); + } + if (obj instanceof Map) { + serializeContext_write(context, "map("); + serializeContext_indent(context); + serializeContext_newline(context); + serializeContext_write(context, "entries = "); + serializeContext_writeObject(context, obj.entries()); + serializeContext_dedent(context); + serializeContext_newline(context); + serializeContext_write(context, ")"); + } + if (typeof obj === "object") { + serializeContext_write(context, "("); + serializeContext_indent(context); + for (const [key, value] of Object.entries(obj)) { + serializeContext_newline(context); + if ([...key].every(isAlphabetic)) { + serializeContext_write(context, `${key} = `); + } else { + serializeContext_write(context, `${escapeStr(key)} = `); + } + serializeContext_writeObject(context, value); + } + serializeContext_dedent(context); + serializeContext_newline(context); + serializeContext_write(context, ")"); + return; + } + throw new Error(`Unsupported type for serialization: ${typeof obj}`); +} + +export function serialize( + obj: unknown, + opts?: { minified?: boolean }, +): string { + const context = serializeContext_create(); + + const minified = opts?.minified ?? false; + + context.minified = minified; + + serializeContext_writeObject(context, obj); + return context.output.trim(); +} + +export const stringify = serialize; From 45bf0268404b6785bafc60578f320264285a76b3 Mon Sep 17 00:00:00 2001 From: andylovescode Date: Thu, 14 Aug 2025 11:22:53 -0700 Subject: [PATCH 05/13] CLIv2: First draft --- bun.lock | 42 +- package.json | 2 +- packages/vortex-cli/.gitignore | 34 + packages/vortex-cli/.npmignore | 33 + packages/vortex-cli/CHANGELOG.md | 13 + packages/vortex-cli/README.md | 6 + packages/vortex-cli/package.json | 32 + packages/vortex-cli/src/corebind/index.ts | 74 ++ packages/vortex-cli/src/index.ts | 76 ++ packages/vortex-cli/src/render/ansi.ts | 47 + packages/vortex-cli/src/render/index.ts | 226 +++++ packages/vortex-cli/src/tokens/symbols.ts | 98 +++ .../vortex-cli/src/tokens/tailwind-colors.ts | 290 ++++++ packages/vortex-cli/src/tree/index.ts | 352 ++++++++ packages/vortex-cli/tsconfig.json | 27 + packages/vortex-common/src/skl.ts | 828 +++++++++--------- packages/vortex-core/src/context.ts | 240 ++--- packages/vortex-core/src/jsx/jsx-common.ts | 297 ++++--- packages/vortex-core/src/render/fragments.ts | 204 ++--- packages/vortex-core/src/render/index.ts | 4 +- packages/vortex-core/src/render/reconciler.ts | 1 + packages/vortex-intrinsics/.gitignore | 34 + packages/vortex-intrinsics/.npmignore | 33 + packages/vortex-intrinsics/CHANGELOG.md | 13 + packages/vortex-intrinsics/README.md | 7 + packages/vortex-intrinsics/package.json | 30 + packages/vortex-intrinsics/src/index.ts | 117 +++ packages/vortex-intrinsics/tsconfig.json | 27 + packages/vortex-ssr/src/index.test.tsx | 14 +- packages/vortex-ssr/src/index.ts | 13 +- packages/wormhole/package.json | 92 +- packages/wormhole/src/cli.ts | 18 - packages/wormhole/src/cli.tsx | 42 + 33 files changed, 2497 insertions(+), 869 deletions(-) create mode 100644 packages/vortex-cli/.gitignore create mode 100644 packages/vortex-cli/.npmignore create mode 100644 packages/vortex-cli/CHANGELOG.md create mode 100644 packages/vortex-cli/README.md create mode 100644 packages/vortex-cli/package.json create mode 100644 packages/vortex-cli/src/corebind/index.ts create mode 100644 packages/vortex-cli/src/index.ts create mode 100644 packages/vortex-cli/src/render/ansi.ts create mode 100644 packages/vortex-cli/src/render/index.ts create mode 100644 packages/vortex-cli/src/tokens/symbols.ts create mode 100644 packages/vortex-cli/src/tokens/tailwind-colors.ts create mode 100644 packages/vortex-cli/src/tree/index.ts create mode 100644 packages/vortex-cli/tsconfig.json create mode 100644 packages/vortex-intrinsics/.gitignore create mode 100644 packages/vortex-intrinsics/.npmignore create mode 100644 packages/vortex-intrinsics/CHANGELOG.md create mode 100644 packages/vortex-intrinsics/README.md create mode 100644 packages/vortex-intrinsics/package.json create mode 100644 packages/vortex-intrinsics/src/index.ts create mode 100644 packages/vortex-intrinsics/tsconfig.json delete mode 100644 packages/wormhole/src/cli.ts create mode 100644 packages/wormhole/src/cli.tsx diff --git a/bun.lock b/bun.lock index 1d5fd67..101aabe 100644 --- a/bun.lock +++ b/bun.lock @@ -135,6 +135,23 @@ "typescript": "catalog:", }, }, + "packages/vortex-cli": { + "name": "@vortexjs/cli", + "version": "0.0.1", + "dependencies": { + "@vortexjs/common": "workspace:*", + "@vortexjs/core": "workspace:*", + "@vortexjs/intrinsics": "workspace:*", + "yoga-layout": "^3.2.1", + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsup": "catalog:", + }, + "peerDependencies": { + "typescript": "catalog:", + }, + }, "packages/vortex-common": { "name": "@vortexjs/common", "version": "0.1.0", @@ -175,6 +192,21 @@ "typescript": "catalog:", }, }, + "packages/vortex-intrinsics": { + "name": "@vortexjs/intrinsics", + "version": "0.0.0", + "dependencies": { + "@vortexjs/common": "workspace:*", + "@vortexjs/core": "workspace:*", + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsup": "catalog:", + }, + "peerDependencies": { + "typescript": "catalog:", + }, + }, "packages/vortex-prime": { "name": "@vortexjs/prime", "version": "1.3.4", @@ -197,13 +229,13 @@ "dependencies": { "@vortexjs/common": "workspace:*", "@vortexjs/core": "workspace:*", + "@vortexjs/dom": "workspace:*", }, "devDependencies": { "@types/bun": "catalog:", "tsup": "catalog:", }, "peerDependencies": { - "@vortexjs/dom": "workspace:*", "typescript": "catalog:", }, }, @@ -216,10 +248,12 @@ }, "dependencies": { "@speed-highlight/core": "catalog:", + "@vortexjs/cli": "workspace:*", "@vortexjs/common": "workspace:*", "@vortexjs/core": "workspace:*", "@vortexjs/discovery": "workspace:*", "@vortexjs/dom": "workspace:*", + "@vortexjs/intrinsics": "workspace:*", "@vortexjs/pippin": "workspace:*", "@vortexjs/pippin-plugin-tailwind": "workspace:", "@vortexjs/ssr": "workspace:*", @@ -550,6 +584,8 @@ "@vortexjs/cataloger": ["@vortexjs/cataloger@workspace:packages/cataloger"], + "@vortexjs/cli": ["@vortexjs/cli@workspace:packages/vortex-cli"], + "@vortexjs/common": ["@vortexjs/common@workspace:packages/vortex-common"], "@vortexjs/core": ["@vortexjs/core@workspace:packages/vortex-core"], @@ -560,6 +596,8 @@ "@vortexjs/example-wormhole": ["@vortexjs/example-wormhole@workspace:packages/example-wormhole"], + "@vortexjs/intrinsics": ["@vortexjs/intrinsics@workspace:packages/vortex-intrinsics"], + "@vortexjs/locounter": ["@vortexjs/locounter@workspace:packages/locounter"], "@vortexjs/pippin": ["@vortexjs/pippin@workspace:packages/pippin"], @@ -936,6 +974,8 @@ "yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="], + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], diff --git a/package.json b/package.json index 27752bd..39dda32 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "change": "changeset", "release": "changeset publish", "build": "turbo build", - "wdev": "turbo @vortexjs/example-wormhole#dev", + "wdev": "cd packages/example-wormhole && turbo build && bun dev", "version": "changeset version", "dev": "turbo dev", "test": "turbo test", diff --git a/packages/vortex-cli/.gitignore b/packages/vortex-cli/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/vortex-cli/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/vortex-cli/.npmignore b/packages/vortex-cli/.npmignore new file mode 100644 index 0000000..dfe1eec --- /dev/null +++ b/packages/vortex-cli/.npmignore @@ -0,0 +1,33 @@ +# dependencies (bun install) +node_modules + +# output +out +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/vortex-cli/CHANGELOG.md b/packages/vortex-cli/CHANGELOG.md new file mode 100644 index 0000000..d6fb7a8 --- /dev/null +++ b/packages/vortex-cli/CHANGELOG.md @@ -0,0 +1,13 @@ +# @vortexjs/cache + +## 0.0.1 + +### Patch Changes + +- Updated dependencies [f870d4f] +- Updated dependencies [0885d49] +- Updated dependencies [1f8e9da] +- Updated dependencies [936f5d6] +- Updated dependencies [50075fb] +- Updated dependencies [1f8e9da] + - @vortexjs/common@0.1.0 diff --git a/packages/vortex-cli/README.md b/packages/vortex-cli/README.md new file mode 100644 index 0000000..a048b7d --- /dev/null +++ b/packages/vortex-cli/README.md @@ -0,0 +1,6 @@ +# Vortex CLI + +- Layers + - Render + - Layout + - Framework Integration diff --git a/packages/vortex-cli/package.json b/packages/vortex-cli/package.json new file mode 100644 index 0000000..3e976bb --- /dev/null +++ b/packages/vortex-cli/package.json @@ -0,0 +1,32 @@ +{ + "name": "@vortexjs/cli", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsup": "catalog:" + }, + "dependencies": { + "@vortexjs/common": "workspace:*", + "@vortexjs/core": "workspace:*", + "@vortexjs/intrinsics": "workspace:*", + "yoga-layout": "^3.2.1" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsup ./src/index.ts --format esm --dts --out-dir dist" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "version": "0.0.1" +} diff --git a/packages/vortex-cli/src/corebind/index.ts b/packages/vortex-cli/src/corebind/index.ts new file mode 100644 index 0000000..88f9b64 --- /dev/null +++ b/packages/vortex-cli/src/corebind/index.ts @@ -0,0 +1,74 @@ +import { createContext, getImmediateValue, implementIntrinsic, store, type IntrinsicComponent, type Lifetime, type Renderer, type Store } from "@vortexjs/core"; +import { Box, type TreeNode, Text } from "../tree"; +import { fontWeightToPrimitiveBoldness, Frame, Text as TextIntrinsic, type FontWeight } from "@vortexjs/intrinsics"; +import { jsx } from "@vortexjs/core/jsx-runtime"; + +export function cli(): Renderer { + return { + createNode(type: string, hydration?: undefined): TreeNode { + if (type === "box") { + return new Box(); + } + if (type === "text") { + return new Text([]); + } + throw new Error(`Unknown node type: ${type}`); + }, + setAttribute(node: TreeNode, name: string, value: any): void { + if (node instanceof Box) { + (node.attributes as any)[name] = value; + node.update(); + } else if (node instanceof Text) { + if (name === "weight") { + const weight = value as FontWeight; + const isBold = fontWeightToPrimitiveBoldness(weight); + node.style.bold = isBold === "bold"; + return; + } + (node.style as any)[name] = value; + } else { + throw new Error("setAttribute can only be called on Box and Text nodes."); + } + }, + createTextNode(hydration?: undefined): TreeNode { + return new Text([]); + }, + setTextContent(node: TreeNode, text: string): void { + if (node instanceof Text) { + node.children = [text]; + } else { + throw new Error("setTextContent can only be called on Text nodes."); + } + }, + setChildren(node: TreeNode, children: TreeNode[]): void { + if ('children' in node) { + node.children = children; + } + }, + getHydrationContext(node: TreeNode): undefined { + return undefined; + }, + addEventListener(node: TreeNode, name: string, event: (event: any) => void): Lifetime { + throw new Error("Function not implemented."); + }, + bindValue(node: TreeNode, name: string, value: Store): Lifetime { + if (node instanceof Box) { + return value.subscribe((newValue) => { + this.setAttribute(node, name, newValue); + }); + } + throw new Error("bindValue can only be called on Box nodes."); + }, + setStyle(node: TreeNode, name: string, value: string | undefined): void { + throw new Error("Function not implemented."); + }, + implementations: [ + implementIntrinsic(Frame, (props) => { + return jsx("box", props); + }), + implementIntrinsic(TextIntrinsic, (props) => { + return jsx("text", props); + }) + ] + } +} diff --git a/packages/vortex-cli/src/index.ts b/packages/vortex-cli/src/index.ts new file mode 100644 index 0000000..86a2830 --- /dev/null +++ b/packages/vortex-cli/src/index.ts @@ -0,0 +1,76 @@ +import { ContextScope, type JSXNode, render } from "@vortexjs/core"; +import { Direction, Edge, type Node } from "yoga-layout"; +import { cli } from "./corebind"; +import { Canvas, Renderer } from "./render"; +import symbols from "./tokens/symbols"; +import colors from "./tokens/tailwind-colors"; +import { Box } from "./tree"; + +function printYogaNode(node: Node) { + console.group("Yoga Node"); + + console.log("X:", node.getComputedLeft()); + console.log("Y:", node.getComputedTop()); + console.log("Width:", node.getComputedWidth()); + console.log("Height:", node.getComputedHeight()); + console.log("PaddingTop", node.getPadding(Edge.Top)); + console.log("PaddingRight", node.getPadding(Edge.Right)); + console.log("PaddingBottom", node.getPadding(Edge.Bottom)); + console.log("PaddingLeft", node.getPadding(Edge.Left)); + + for (let i = 0; i < node.getChildCount(); i++) { + const child = node.getChild(i); + printYogaNode(child); + } + + console.groupEnd(); +} + +export function cliApp(root: JSXNode) { + const renderer = new Renderer(); + const internalRoot = new Box(); + const context = new ContextScope(); + + function paint() { + const width = process.stdout.columns; + const height = process.stdout.rows; + + internalRoot.yoga.setWidth(width); + internalRoot.yoga.setHeight(height); + + internalRoot.yoga.calculateLayout(undefined, undefined, Direction.LTR); + + const canvas = new Canvas(width, height); + + internalRoot.render(canvas); + + renderer.render(canvas); + } + + let paintImmediate = 0; + + function queuePaint() { + if (paintImmediate) return; + paintImmediate = setTimeout(() => { + paint(); + paintImmediate = 0; + }, 10) as unknown as number; + } + + context.streaming.onUpdate(queuePaint); + + render({ + root: internalRoot, + renderer: cli(), + component: root, + context, + }); + + process.stdout.on("resize", () => { + queuePaint(); + }); + + queuePaint(); +} + +export { symbols, colors }; diff --git a/packages/vortex-cli/src/render/ansi.ts b/packages/vortex-cli/src/render/ansi.ts new file mode 100644 index 0000000..f498344 --- /dev/null +++ b/packages/vortex-cli/src/render/ansi.ts @@ -0,0 +1,47 @@ +import type { FileSink } from "bun"; + +export class ANSIWriter { + constructor(public terminal: FileSink) { } + + csi() { + this.terminal.write("\x1b["); + } + + write(text: string) { + this.terminal.write(text); + } + + moveTo(x: number, y: number) { + this.csi(); + this.terminal.write(`${y + 1};${x + 1}H`); + } + + setBackground(color: string) { + const { r, g, b } = Bun.color(color, "{rgb}")!; + + this.csi(); + this.terminal.write(`48;2;${r};${g};${b}m`); + } + + setForeground(color: string) { + const { r, g, b } = Bun.color(color, "{rgb}")!; + + this.csi(); + this.terminal.write(`38;2;${r};${g};${b}m`); + } + + setItalic(italic: boolean) { + this.csi(); + this.terminal.write(italic ? "3m" : "23m"); + } + + setBold(bold: boolean) { + this.csi(); + this.terminal.write(bold ? "1m" : "22m"); + } + + setUnderline(underline: boolean) { + this.csi(); + this.terminal.write(underline ? "4m" : "24m"); + } +} diff --git a/packages/vortex-cli/src/render/index.ts b/packages/vortex-cli/src/render/index.ts new file mode 100644 index 0000000..3ea95a3 --- /dev/null +++ b/packages/vortex-cli/src/render/index.ts @@ -0,0 +1,226 @@ +import symbols from "../tokens/symbols"; +import { ANSIWriter } from "./ansi"; + +export interface Cell { + text: string; + background: string; + foreground: string; + bold: boolean; + italic: boolean; + underline: boolean; +} + +export type BoxStyle = "outline-round" | "outline-square" | "background-square" | "none"; + +export class Canvas { + constructor(public width: number, public height: number) { + this.clipEndX = width; + this.clipEndY = height; + this.buffer = new Array(width * height).fill({ + text: " ", + background: "black", + foreground: "white", + bold: false, + italic: false, + underline: false + }).map(x => structuredClone(x)); + } + + put(x: number, y: number, cell: Cell) { + if (x < this.clipStartX || x >= this.clipEndX || y < + this.clipStartY || y >= this.clipEndY) { + return; + } + + const index = y * this.width + x; + this.buffer[index]!.text = cell.text; + if (cell.background !== "transparent") { + this.buffer[index]!.background = cell.background; + } + this.buffer[index]!.bold = cell.bold; + this.buffer[index]!.italic = cell.italic; + this.buffer[index]!.underline = cell.underline; + this.buffer[index]!.foreground = cell.foreground; + } + + box(type: BoxStyle, x1: number, y1: number, x2: number, y2: number, color: string) { + if (type === "none") { + return; + } + + for (let y = y1; y <= y2; y++) { + for (let x = x1; x <= x2; x++) { + if (type === "background-square") { + this.put(x, y, { + text: " ", + background: color, + foreground: "white", + bold: false, + italic: false, + underline: false + }); + continue; + } + + const vertical = x === x1 || x === x2; + const horizontal = y === y1 || y === y2; + + const up = (y > y1) && vertical; + const down = (y < y2) && vertical; + const left = (x > x1) && horizontal; + const right = (x < x2) && horizontal; + const symbol = ({ + "outline-round": symbols.outline.rounded, + "outline-square": symbols.outline.square, + })[type]({ + up, + down, + left, + right, + }); + + if (!(up || down || left || right)) { + if (type === "outline-round" || type === "outline-square") { + continue; // Skip the inner area for outlines + } + + this.put(x, y, { + text: " ", + background: color, + foreground: "transparent", + bold: false, + italic: false, + underline: false, + }); + } else { + this.put(x, y, { + text: symbol, + background: "transparent", + foreground: color, + bold: false, + italic: false, + underline: false + }); + } + } + } + } + + clip(x1: number, y1: number, x2: number, y2: number) { + const prevClipStartX = this.clipStartX; + const prevClipStartY = this.clipStartY; + const prevClipEndX = this.clipEndX; + const prevClipEndY = this.clipEndY; + + this.clipStartX = Math.max(0, x1, this.clipStartX); + this.clipStartY = Math.max(0, y1, this.clipStartY); + this.clipEndX = Math.min(this.width, x2, this.clipEndX); + this.clipEndY = Math.min(this.height, y2, this.clipEndY); + + const self = this; + + return { + [Symbol.dispose]() { + self.clipStartX = prevClipStartX; + self.clipStartY = prevClipStartY; + self.clipEndX = prevClipEndX; + self.clipEndY = prevClipEndY; + } + } + } + + clipStartX = 0; + clipStartY = 0; + clipEndX = 0; + clipEndY = 0; + buffer: Cell[]; +} + +function getCellKey(cell: Cell) { + return `${cell.text}-${cell.background}-${cell.foreground}`; +} + +export class Renderer { + currentCells: string[] = []; + currentWidth = 0; + currentHeight = 0; + ansi = new ANSIWriter(Bun.stdout.writer()); + currentBGColor = ""; + currentFGColor = ""; + currentX = 0; + currentY = 0; + currentBold: boolean | undefined = undefined; + currentUnderline: boolean | undefined = undefined; + currentItalic: boolean | undefined = undefined; + + setBGColor(color: string) { + if (this.currentBGColor === color) return; + this.currentBGColor = color; + this.ansi.setBackground(color); + } + + setFGColor(color: string) { + if (this.currentFGColor === color) return; + this.currentFGColor = color; + this.ansi.setForeground(color); + } + + setPosition(x: number, y: number) { + if (this.currentX === x && this.currentY === y) return; + this.currentX = x; + this.currentY = y; + this.ansi.moveTo(x, y); + } + + setBold(bold: boolean) { + if (this.currentBold === bold) return; + this.currentBold = bold; + this.ansi.setBold(bold); + } + + setItalic(italic: boolean) { + if (this.currentItalic === italic) return; + this.currentItalic = italic; + this.ansi.setItalic(italic); + } + + setUnderline(underline: boolean) { + if (this.currentUnderline === underline) return; + this.currentUnderline = underline; + this.ansi.setUnderline(underline); + } + + render(canvas: Canvas) { + if (this.currentWidth !== canvas.width || this.currentHeight !== canvas.height) { + this.currentCells = []; + } + + this.currentX = -1; + this.currentY = -1; + this.currentBold = undefined; + this.currentUnderline = undefined; + this.currentItalic = undefined; + + this.currentWidth = canvas.width; + this.currentHeight = canvas.height; + + for (let y = 0; y < canvas.height; y++) { + for (let x = 0; x < canvas.width; x++) { + const cell = canvas.buffer[y * canvas.width + x]!; + const key = getCellKey(cell); + + if (this.currentCells[y * canvas.width + x] !== key) { + this.setPosition(x, y); + this.setBGColor(cell.background); + this.setFGColor(cell.foreground); + this.setItalic(cell.italic); + this.setBold(cell.bold); + this.setUnderline(cell.underline); + this.ansi.write(cell.text); + this.currentX++; + this.currentCells[y * canvas.width + x] = key; + } + } + } + } +} diff --git a/packages/vortex-cli/src/tokens/symbols.ts b/packages/vortex-cli/src/tokens/symbols.ts new file mode 100644 index 0000000..9fff851 --- /dev/null +++ b/packages/vortex-cli/src/tokens/symbols.ts @@ -0,0 +1,98 @@ +export interface UDLR { + up: boolean; + down: boolean; + left: boolean; + right: boolean; +} + +type Fallback = T extends "" ? F : T; + +export type UDLRSymbolSet = Record, string>; + +function udlrSymbol16(symbols: UDLRSymbolSet) { + return function ({ up, down, left, right }: UDLR) { + const key = `${up ? "u" : ""}${down ? "d" : ""}${left ? "l" : ""}${right ? "r" : ""}` as keyof UDLRSymbolSet; + return symbols[key] || symbols.none; + } +} + +const symbols = { + outline: { + rounded: udlrSymbol16({ + u: "╵", + d: "╷", + l: "╴", + r: "╶", + lr: "─", + dr: "╭", + dl: "╮", + dlr: "┬", + ur: "╰", + ul: "╯", + ulr: "┴", + ud: "│", + udr: "├", + udl: "┤", + udlr: "┼", + none: " " + }), + double: udlrSymbol16({ + u: "╨", + d: "╥", + l: "╡", + r: "╞", + lr: "═", + dr: "╔", + dl: "╗", + dlr: "╦", + ur: "╚", + ul: "╝", + ulr: "╩", + ud: "║", + udr: "╠", + udl: "╣", + udlr: "╬", + none: " " + }), + square: udlrSymbol16({ + u: "╵", + d: "╷", + l: "╴", + r: "╶", + lr: "─", + dr: "┌", + dl: "┐", + dlr: "┬", + ur: "└", + ul: "┘", + ulr: "┴", + ud: "│", + udr: "├", + udl: "┤", + udlr: "┼", + none: " " + }), + }, + background: { + square: udlrSymbol16({ + u: "▀", + d: "▄", + l: "▌", + r: "▐", + lr: "█", + dr: "▘", + dl: "▝", + dlr: "▄", + ur: "▖", + ul: "▗", + ulr: "▀", + ud: "█", + udr: "█", + udl: "█", + udlr: "█", + none: " " + }), + } +} as const; + +export default symbols; diff --git a/packages/vortex-cli/src/tokens/tailwind-colors.ts b/packages/vortex-cli/src/tokens/tailwind-colors.ts new file mode 100644 index 0000000..705a128 --- /dev/null +++ b/packages/vortex-cli/src/tokens/tailwind-colors.ts @@ -0,0 +1,290 @@ +export default { + black: '#000', + white: '#fff', + slate: { + '50': 'oklch(98.4% 0.003 247.858)', + '100': 'oklch(96.8% 0.007 247.896)', + '200': 'oklch(92.9% 0.013 255.508)', + '300': 'oklch(86.9% 0.022 252.894)', + '400': 'oklch(70.4% 0.04 256.788)', + '500': 'oklch(55.4% 0.046 257.417)', + '600': 'oklch(44.6% 0.043 257.281)', + '700': 'oklch(37.2% 0.044 257.287)', + '800': 'oklch(27.9% 0.041 260.031)', + '900': 'oklch(20.8% 0.042 265.755)', + '950': 'oklch(12.9% 0.042 264.695)', + }, + gray: { + '50': 'oklch(98.5% 0.002 247.839)', + '100': 'oklch(96.7% 0.003 264.542)', + '200': 'oklch(92.8% 0.006 264.531)', + '300': 'oklch(87.2% 0.01 258.338)', + '400': 'oklch(70.7% 0.022 261.325)', + '500': 'oklch(55.1% 0.027 264.364)', + '600': 'oklch(44.6% 0.03 256.802)', + '700': 'oklch(37.3% 0.034 259.733)', + '800': 'oklch(27.8% 0.033 256.848)', + '900': 'oklch(21% 0.034 264.665)', + '950': 'oklch(13% 0.028 261.692)', + }, + zinc: { + '50': 'oklch(98.5% 0 0)', + '100': 'oklch(96.7% 0.001 286.375)', + '200': 'oklch(92% 0.004 286.32)', + '300': 'oklch(87.1% 0.006 286.286)', + '400': 'oklch(70.5% 0.015 286.067)', + '500': 'oklch(55.2% 0.016 285.938)', + '600': 'oklch(44.2% 0.017 285.786)', + '700': 'oklch(37% 0.013 285.805)', + '800': 'oklch(27.4% 0.006 286.033)', + '900': 'oklch(21% 0.006 285.885)', + '950': 'oklch(14.1% 0.005 285.823)', + }, + neutral: { + '50': 'oklch(98.5% 0 0)', + '100': 'oklch(97% 0 0)', + '200': 'oklch(92.2% 0 0)', + '300': 'oklch(87% 0 0)', + '400': 'oklch(70.8% 0 0)', + '500': 'oklch(55.6% 0 0)', + '600': 'oklch(43.9% 0 0)', + '700': 'oklch(37.1% 0 0)', + '800': 'oklch(26.9% 0 0)', + '900': 'oklch(20.5% 0 0)', + '950': 'oklch(14.5% 0 0)', + }, + stone: { + '50': 'oklch(98.5% 0.001 106.423)', + '100': 'oklch(97% 0.001 106.424)', + '200': 'oklch(92.3% 0.003 48.717)', + '300': 'oklch(86.9% 0.005 56.366)', + '400': 'oklch(70.9% 0.01 56.259)', + '500': 'oklch(55.3% 0.013 58.071)', + '600': 'oklch(44.4% 0.011 73.639)', + '700': 'oklch(37.4% 0.01 67.558)', + '800': 'oklch(26.8% 0.007 34.298)', + '900': 'oklch(21.6% 0.006 56.043)', + '950': 'oklch(14.7% 0.004 49.25)', + }, + red: { + '50': 'oklch(97.1% 0.013 17.38)', + '100': 'oklch(93.6% 0.032 17.717)', + '200': 'oklch(88.5% 0.062 18.334)', + '300': 'oklch(80.8% 0.114 19.571)', + '400': 'oklch(70.4% 0.191 22.216)', + '500': 'oklch(63.7% 0.237 25.331)', + '600': 'oklch(57.7% 0.245 27.325)', + '700': 'oklch(50.5% 0.213 27.518)', + '800': 'oklch(44.4% 0.177 26.899)', + '900': 'oklch(39.6% 0.141 25.723)', + '950': 'oklch(25.8% 0.092 26.042)', + }, + orange: { + '50': 'oklch(98% 0.016 73.684)', + '100': 'oklch(95.4% 0.038 75.164)', + '200': 'oklch(90.1% 0.076 70.697)', + '300': 'oklch(83.7% 0.128 66.29)', + '400': 'oklch(75% 0.183 55.934)', + '500': 'oklch(70.5% 0.213 47.604)', + '600': 'oklch(64.6% 0.222 41.116)', + '700': 'oklch(55.3% 0.195 38.402)', + '800': 'oklch(47% 0.157 37.304)', + '900': 'oklch(40.8% 0.123 38.172)', + '950': 'oklch(26.6% 0.079 36.259)', + }, + amber: { + '50': 'oklch(98.7% 0.022 95.277)', + '100': 'oklch(96.2% 0.059 95.617)', + '200': 'oklch(92.4% 0.12 95.746)', + '300': 'oklch(87.9% 0.169 91.605)', + '400': 'oklch(82.8% 0.189 84.429)', + '500': 'oklch(76.9% 0.188 70.08)', + '600': 'oklch(66.6% 0.179 58.318)', + '700': 'oklch(55.5% 0.163 48.998)', + '800': 'oklch(47.3% 0.137 46.201)', + '900': 'oklch(41.4% 0.112 45.904)', + '950': 'oklch(27.9% 0.077 45.635)', + }, + yellow: { + '50': 'oklch(98.7% 0.026 102.212)', + '100': 'oklch(97.3% 0.071 103.193)', + '200': 'oklch(94.5% 0.129 101.54)', + '300': 'oklch(90.5% 0.182 98.111)', + '400': 'oklch(85.2% 0.199 91.936)', + '500': 'oklch(79.5% 0.184 86.047)', + '600': 'oklch(68.1% 0.162 75.834)', + '700': 'oklch(55.4% 0.135 66.442)', + '800': 'oklch(47.6% 0.114 61.907)', + '900': 'oklch(42.1% 0.095 57.708)', + '950': 'oklch(28.6% 0.066 53.813)', + }, + lime: { + '50': 'oklch(98.6% 0.031 120.757)', + '100': 'oklch(96.7% 0.067 122.328)', + '200': 'oklch(93.8% 0.127 124.321)', + '300': 'oklch(89.7% 0.196 126.665)', + '400': 'oklch(84.1% 0.238 128.85)', + '500': 'oklch(76.8% 0.233 130.85)', + '600': 'oklch(64.8% 0.2 131.684)', + '700': 'oklch(53.2% 0.157 131.589)', + '800': 'oklch(45.3% 0.124 130.933)', + '900': 'oklch(40.5% 0.101 131.063)', + '950': 'oklch(27.4% 0.072 132.109)', + }, + green: { + '50': 'oklch(98.2% 0.018 155.826)', + '100': 'oklch(96.2% 0.044 156.743)', + '200': 'oklch(92.5% 0.084 155.995)', + '300': 'oklch(87.1% 0.15 154.449)', + '400': 'oklch(79.2% 0.209 151.711)', + '500': 'oklch(72.3% 0.219 149.579)', + '600': 'oklch(62.7% 0.194 149.214)', + '700': 'oklch(52.7% 0.154 150.069)', + '800': 'oklch(44.8% 0.119 151.328)', + '900': 'oklch(39.3% 0.095 152.535)', + '950': 'oklch(26.6% 0.065 152.934)', + }, + emerald: { + '50': 'oklch(97.9% 0.021 166.113)', + '100': 'oklch(95% 0.052 163.051)', + '200': 'oklch(90.5% 0.093 164.15)', + '300': 'oklch(84.5% 0.143 164.978)', + '400': 'oklch(76.5% 0.177 163.223)', + '500': 'oklch(69.6% 0.17 162.48)', + '600': 'oklch(59.6% 0.145 163.225)', + '700': 'oklch(50.8% 0.118 165.612)', + '800': 'oklch(43.2% 0.095 166.913)', + '900': 'oklch(37.8% 0.077 168.94)', + '950': 'oklch(26.2% 0.051 172.552)', + }, + teal: { + '50': 'oklch(98.4% 0.014 180.72)', + '100': 'oklch(95.3% 0.051 180.801)', + '200': 'oklch(91% 0.096 180.426)', + '300': 'oklch(85.5% 0.138 181.071)', + '400': 'oklch(77.7% 0.152 181.912)', + '500': 'oklch(70.4% 0.14 182.503)', + '600': 'oklch(60% 0.118 184.704)', + '700': 'oklch(51.1% 0.096 186.391)', + '800': 'oklch(43.7% 0.078 188.216)', + '900': 'oklch(38.6% 0.063 188.416)', + '950': 'oklch(27.7% 0.046 192.524)', + }, + cyan: { + '50': 'oklch(98.4% 0.019 200.873)', + '100': 'oklch(95.6% 0.045 203.388)', + '200': 'oklch(91.7% 0.08 205.041)', + '300': 'oklch(86.5% 0.127 207.078)', + '400': 'oklch(78.9% 0.154 211.53)', + '500': 'oklch(71.5% 0.143 215.221)', + '600': 'oklch(60.9% 0.126 221.723)', + '700': 'oklch(52% 0.105 223.128)', + '800': 'oklch(45% 0.085 224.283)', + '900': 'oklch(39.8% 0.07 227.392)', + '950': 'oklch(30.2% 0.056 229.695)', + }, + sky: { + '50': 'oklch(97.7% 0.013 236.62)', + '100': 'oklch(95.1% 0.026 236.824)', + '200': 'oklch(90.1% 0.058 230.902)', + '300': 'oklch(82.8% 0.111 230.318)', + '400': 'oklch(74.6% 0.16 232.661)', + '500': 'oklch(68.5% 0.169 237.323)', + '600': 'oklch(58.8% 0.158 241.966)', + '700': 'oklch(50% 0.134 242.749)', + '800': 'oklch(44.3% 0.11 240.79)', + '900': 'oklch(39.1% 0.09 240.876)', + '950': 'oklch(29.3% 0.066 243.157)', + }, + blue: { + '50': 'oklch(97% 0.014 254.604)', + '100': 'oklch(93.2% 0.032 255.585)', + '200': 'oklch(88.2% 0.059 254.128)', + '300': 'oklch(80.9% 0.105 251.813)', + '400': 'oklch(70.7% 0.165 254.624)', + '500': 'oklch(62.3% 0.214 259.815)', + '600': 'oklch(54.6% 0.245 262.881)', + '700': 'oklch(48.8% 0.243 264.376)', + '800': 'oklch(42.4% 0.199 265.638)', + '900': 'oklch(37.9% 0.146 265.522)', + '950': 'oklch(28.2% 0.091 267.935)', + }, + indigo: { + '50': 'oklch(96.2% 0.018 272.314)', + '100': 'oklch(93% 0.034 272.788)', + '200': 'oklch(87% 0.065 274.039)', + '300': 'oklch(78.5% 0.115 274.713)', + '400': 'oklch(67.3% 0.182 276.935)', + '500': 'oklch(58.5% 0.233 277.117)', + '600': 'oklch(51.1% 0.262 276.966)', + '700': 'oklch(45.7% 0.24 277.023)', + '800': 'oklch(39.8% 0.195 277.366)', + '900': 'oklch(35.9% 0.144 278.697)', + '950': 'oklch(25.7% 0.09 281.288)', + }, + violet: { + '50': 'oklch(96.9% 0.016 293.756)', + '100': 'oklch(94.3% 0.029 294.588)', + '200': 'oklch(89.4% 0.057 293.283)', + '300': 'oklch(81.1% 0.111 293.571)', + '400': 'oklch(70.2% 0.183 293.541)', + '500': 'oklch(60.6% 0.25 292.717)', + '600': 'oklch(54.1% 0.281 293.009)', + '700': 'oklch(49.1% 0.27 292.581)', + '800': 'oklch(43.2% 0.232 292.759)', + '900': 'oklch(38% 0.189 293.745)', + '950': 'oklch(28.3% 0.141 291.089)', + }, + purple: { + '50': 'oklch(97.7% 0.014 308.299)', + '100': 'oklch(94.6% 0.033 307.174)', + '200': 'oklch(90.2% 0.063 306.703)', + '300': 'oklch(82.7% 0.119 306.383)', + '400': 'oklch(71.4% 0.203 305.504)', + '500': 'oklch(62.7% 0.265 303.9)', + '600': 'oklch(55.8% 0.288 302.321)', + '700': 'oklch(49.6% 0.265 301.924)', + '800': 'oklch(43.8% 0.218 303.724)', + '900': 'oklch(38.1% 0.176 304.987)', + '950': 'oklch(29.1% 0.149 302.717)', + }, + fuchsia: { + '50': 'oklch(97.7% 0.017 320.058)', + '100': 'oklch(95.2% 0.037 318.852)', + '200': 'oklch(90.3% 0.076 319.62)', + '300': 'oklch(83.3% 0.145 321.434)', + '400': 'oklch(74% 0.238 322.16)', + '500': 'oklch(66.7% 0.295 322.15)', + '600': 'oklch(59.1% 0.293 322.896)', + '700': 'oklch(51.8% 0.253 323.949)', + '800': 'oklch(45.2% 0.211 324.591)', + '900': 'oklch(40.1% 0.17 325.612)', + '950': 'oklch(29.3% 0.136 325.661)', + }, + pink: { + '50': 'oklch(97.1% 0.014 343.198)', + '100': 'oklch(94.8% 0.028 342.258)', + '200': 'oklch(89.9% 0.061 343.231)', + '300': 'oklch(82.3% 0.12 346.018)', + '400': 'oklch(71.8% 0.202 349.761)', + '500': 'oklch(65.6% 0.241 354.308)', + '600': 'oklch(59.2% 0.249 0.584)', + '700': 'oklch(52.5% 0.223 3.958)', + '800': 'oklch(45.9% 0.187 3.815)', + '900': 'oklch(40.8% 0.153 2.432)', + '950': 'oklch(28.4% 0.109 3.907)', + }, + rose: { + '50': 'oklch(96.9% 0.015 12.422)', + '100': 'oklch(94.1% 0.03 12.58)', + '200': 'oklch(89.2% 0.058 10.001)', + '300': 'oklch(81% 0.117 11.638)', + '400': 'oklch(71.2% 0.194 13.428)', + '500': 'oklch(64.5% 0.246 16.439)', + '600': 'oklch(58.6% 0.253 17.585)', + '700': 'oklch(51.4% 0.222 16.935)', + '800': 'oklch(45.5% 0.188 13.697)', + '900': 'oklch(41% 0.159 10.272)', + '950': 'oklch(27.1% 0.105 12.094)', + }, +} diff --git a/packages/vortex-cli/src/tree/index.ts b/packages/vortex-cli/src/tree/index.ts new file mode 100644 index 0000000..31d5d1c --- /dev/null +++ b/packages/vortex-cli/src/tree/index.ts @@ -0,0 +1,352 @@ +import Yoga, { Direction, Display, Edge, FlexDirection, Gutter, Justify, MeasureMode, type Node } from "yoga-layout"; +import type { BoxStyle, Canvas } from "../render"; +import { resolveUDLRDescription, type Frame } from "@vortexjs/intrinsics"; +import type { IntrinsicComponent } from "@vortexjs/core"; + +export interface TreeNode { + yoga: Node; + render(canvas: Canvas): void; +} + +function getYogaConfig() { + const config = Yoga.Config.create(); + + config.setPointScaleFactor(1); + config.setUseWebDefaults(false); + + return config; +} + +const config = getYogaConfig(); + +export type FrameProps = Omit + ? Props + : never, "children">; + +function pixelToTileX(px: number | T): number | T { + if (Number.isNaN(Number(px))) { + return px; + } + + return Math.round(Number(px) / (10 / 4)); +} + +function pixelToTileY(px: number | T): number | T { + if (Number.isNaN(Number(px))) { + return px; + } + + return Math.round(Number(px) / (25 / 4)); +} + +export class Box implements TreeNode { + yoga: Node; + attributes: FrameProps = {}; + private _children: TreeNode[] = []; + + get children(): TreeNode[] { + return this._children; + } + + set children(children: TreeNode[]) { + this._children = children; + + while (this.yoga.getChildCount() > 0) { + this.yoga.removeChild(this.yoga.getChild(0)); + } + + for (const child of children) { + this.yoga.insertChild(child.yoga, this.yoga.getChildCount()); + } + } + + constructor() { + this.yoga = Yoga.Node.create(config); + } + + render(canvas: Canvas): void { + const layout = this.yoga.getComputedLayout(); + + const left = layout.left; + const top = layout.top; + const right = left + layout.width; + const bottom = top + layout.height; + + if (this.attributes.background) { + const backgroundColor = typeof this.attributes.background === "string" + ? this.attributes.background + : this.attributes.background.color ?? "black"; + + if (this.attributes.background === "transparent") { + canvas.box("none", left, top, right, bottom, backgroundColor); + } else { + canvas.box("background-square", left, top, right - 1, bottom - 1, backgroundColor); + } + } + + if (this.attributes.border) { + const borderWidth = typeof this.attributes.border == "string" ? 1 : this.attributes.border.width ?? 1; + const borderColor = typeof this.attributes.border === "string" + ? this.attributes.border + : this.attributes.border.color ?? "white"; + const radius = typeof this.attributes.border === "string" + ? 0 + : this.attributes.border.radius ?? 0; + + if (borderWidth > 0) { + canvas.box(radius > 0 ? "outline-round" : "outline-square", left, top, right - 1, bottom - 1, borderColor); + } + } + + const border = this.attributes.border ? 1 : 0; + + using _clip = canvas.clip(left + border, top + border, right - border, bottom - border); + + for (const child of this.children) { + child.render(canvas); + } + } + + update() { + this.yoga.setFlexGrow(this.attributes.grow); + this.yoga.setFlexDirection({ + row: FlexDirection.Row, + "row-reverse": FlexDirection.RowReverse, + column: FlexDirection.Column, + "column-reverse": FlexDirection.ColumnReverse + }[this.attributes.direction ?? "row"]); + + this.yoga.setGap(Gutter.Row, pixelToTileX(this.attributes.gap ?? 0) as number); + this.yoga.setGap(Gutter.Column, pixelToTileY(this.attributes.gap ?? 0) as number); + + const padding = resolveUDLRDescription(this.attributes.padding ?? 0); + + this.yoga.setPadding(Edge.Top, pixelToTileY(padding.top) as number); + this.yoga.setPadding(Edge.Right, pixelToTileX(padding.right) as number); + this.yoga.setPadding(Edge.Bottom, pixelToTileY(padding.bottom) as number); + this.yoga.setPadding(Edge.Left, pixelToTileX(padding.left) as number); + + const margin = resolveUDLRDescription(this.attributes.margin ?? 0); + + this.yoga.setMargin(Edge.Top, pixelToTileY(margin.top) as number); + this.yoga.setMargin(Edge.Right, pixelToTileX(margin.right) as number); + this.yoga.setMargin(Edge.Bottom, pixelToTileY(margin.bottom) as number); + this.yoga.setMargin(Edge.Left, pixelToTileX(margin.left) as number); + + switch (this.attributes.justifyContent) { + case "flex-start": + this.yoga.setJustifyContent(Justify.FlexStart); + break; + case "flex-end": + this.yoga.setJustifyContent(Justify.FlexEnd); + break; + case "center": + this.yoga.setJustifyContent(Justify.Center); + break; + case "space-between": + this.yoga.setJustifyContent(Justify.SpaceBetween); + break; + case "space-around": + this.yoga.setJustifyContent(Justify.SpaceAround); + break; + case "space-evenly": + this.yoga.setJustifyContent(Justify.SpaceEvenly); + break; + } + + switch (this.attributes.flexDirection) { + case "row": + this.yoga.setFlexDirection(FlexDirection.Row); + break; + case "column": + this.yoga.setFlexDirection(FlexDirection.Column); + break; + case "row-reverse": + this.yoga.setFlexDirection(FlexDirection.RowReverse); + break; + case "column-reverse": + this.yoga.setFlexDirection(FlexDirection.ColumnReverse); + break; + } + + if (this.attributes.border) { + this.yoga.setBorder(Edge.All, 1) + } + this.yoga.setWidth( + typeof this.attributes.width === "number" + ? pixelToTileX(this.attributes.width) + : (this.attributes.width as any) + ); + this.yoga.setHeight( + typeof this.attributes.height === "number" + ? pixelToTileY(this.attributes.height) + : (this.attributes.height as any) + ); + this.yoga.setMinWidth( + typeof this.attributes.minWidth === "number" + ? pixelToTileX(this.attributes.minWidth) + : (this.attributes.minWidth as any) + ); + this.yoga.setMinHeight( + typeof this.attributes.minHeight === "number" + ? pixelToTileY(this.attributes.minHeight) + : (this.attributes.minHeight as any) + ); + this.yoga.setMaxWidth( + typeof this.attributes.maxWidth === "number" + ? pixelToTileX(this.attributes.maxWidth) + : (this.attributes.maxWidth as any) + ); + this.yoga.setMaxHeight( + typeof this.attributes.maxHeight === "number" + ? pixelToTileY(this.attributes.maxHeight) + : (this.attributes.maxHeight as any) + ); + } +} + +function getTextSegments(text: string): string[] { + return text.split(/(\s+)/).filter(segment => segment.length > 0); +} + +export function layoutText(text: TextSlice[], maxWidth: number): TextSlice[][] { + let lines: TextSlice[][] = []; + + let currentLine: TextSlice[] = []; + let width = 0; + + for (const segment of text) { + if (width + segment.text.length > maxWidth || segment.text === "\n") { + lines.push(currentLine); + currentLine = []; + width = 0; + } + + currentLine.push(segment); + width += segment.text.length; + } + + if (currentLine.length > 0) { + lines.push(currentLine); + } + + return lines; +} + +type TextChild = Text | string; + +type TextSlice = { + text: string; +} & TextStyle; + +type TextStyle = { + color: string; + underline: boolean; + italic: boolean; + bold: boolean; + background: string; +} + +export class Text implements TreeNode { + yoga: Node; + children: TextChild[]; + style: Partial = {}; + realize(style: TextStyle = { + color: "white", + underline: false, + italic: false, + bold: false, + background: "transparent" + }): TextSlice[] { + const color = this.style.color ?? style.color; + const italic = this.style.italic ?? style.italic; + const bold = this.style.bold ?? style.bold; + const underline = this.style.underline ?? style.underline; + const background = this.style.background ?? style.background; + + let slices: TextSlice[] = []; + + for (const child of this.children) { + if (typeof child === "string") { + const segments = getTextSegments(child); + for (const segment of segments) { + slices.push({ + text: segment, + color, + italic, + bold, + underline, + background: "transparent" + }) + } + continue; + } + + const realized = child.realize({ + color, + italic, + bold, + underline, + background + }); + + slices.push(...realized); + } + + return slices; + } + + constructor(text: TextChild[]) { + this.children = text; + this.yoga = Yoga.Node.create(config); + this.yoga.setMeasureFunc((width, widthMode, height, heightMode) => { + const maxW = widthMode === MeasureMode.Undefined ? Number.MAX_VALUE : width; + const realized = this.realize(); + const lines = layoutText(realized, maxW); + + let measuredWidth = Math.min(...lines.map(x => x.length)); + if (widthMode === MeasureMode.AtMost) { + measuredWidth = Math.min(...realized.map(x => x.text.length)); + } else if (widthMode === MeasureMode.Exactly) { + measuredWidth = width; + } + + let measuredHeight = lines.length; + if (heightMode === MeasureMode.AtMost) { + measuredHeight = Math.min(measuredHeight, height); + } else if (heightMode === MeasureMode.Exactly) { + measuredHeight = height; + } + + return { width: measuredWidth, height: measuredHeight }; + }); + } + + render(canvas: Canvas): void { + const { left, top, width } = this.yoga.getComputedLayout(); + + const lines = layoutText(this.realize(), width); + + let y = top; + + for (const line of lines) { + let x = left; + + for (const segment of line) { + for (const character of segment.text) { + canvas.put(x, y, { + background: segment.background, + foreground: segment.color, + text: character, + bold: segment.bold, + underline: segment.underline, + italic: segment.italic + }) + x++; + } + } + + y++; + } + } +} diff --git a/packages/vortex-cli/tsconfig.json b/packages/vortex-cli/tsconfig.json new file mode 100644 index 0000000..d715a95 --- /dev/null +++ b/packages/vortex-cli/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": ["src/**/*", "test/**/*"] +} diff --git a/packages/vortex-common/src/skl.ts b/packages/vortex-common/src/skl.ts index 7b67636..fea1bcd 100644 --- a/packages/vortex-common/src/skl.ts +++ b/packages/vortex-common/src/skl.ts @@ -9,32 +9,32 @@ export type String = ["string", string]; export type Number = ["number", number]; export const keywords = { - null: "Null", - true: "True", - false: "False", - undefined: "Undefined", + null: "Null", + true: "True", + false: "False", + undefined: "Undefined", } as const; export const symbols = { - "(": "LeftParenthesis", - ")": "RightParenthesis", - "[": "LeftSquareBracket", - "]": "RightSquareBracket", - "{": "LeftCurlyBracket", - "}": "RightCurlyBracket", - ",": "Comma", - ":": "Colon", - ";": "Semicolon", - ".": "Dot", - "=": "Equals", - "+": "Plus", - "-": "Minus", - "*": "Asterisk", - "/": "Slash", - "%": "Percent", - "!": "ExclamationMark", - "<": "LeftAngularBracket", - ">": "RightAngularBracket", + "(": "LeftParenthesis", + ")": "RightParenthesis", + "[": "LeftSquareBracket", + "]": "RightSquareBracket", + "{": "LeftCurlyBracket", + "}": "RightCurlyBracket", + ",": "Comma", + ":": "Colon", + ";": "Semicolon", + ".": "Dot", + "=": "Equals", + "+": "Plus", + "-": "Minus", + "*": "Asterisk", + "/": "Slash", + "%": "Percent", + "!": "ExclamationMark", + "<": "LeftAngularBracket", + ">": "RightAngularBracket", } as const; export type Keyword = ["keyword", (typeof keywords)[keyof typeof keywords]]; @@ -44,469 +44,459 @@ export type Symbol = ["symbol", (typeof symbols)[keyof typeof symbols]]; export type Token = Identifier | String | Number | Keyword | Symbol; export function isWhitespace(char: string) { - return char === " " || char === "\t" || char === "\n" || char === "\r"; + return char === " " || char === "\t" || char === "\n" || char === "\r"; } export function isAlphabetic(char: string) { - return ( - (char >= "a" && char <= "z") || - (char >= "A" && char <= "Z") || - char === "_" - ); + return ( + (char >= "a" && char <= "z") || + (char >= "A" && char <= "Z") || + char === "_" + ); } export function isNumeric(char: string) { - return char >= "0" && char <= "9"; + return char >= "0" && char <= "9"; } export function isAlphanumeric(char: string) { - return isAlphabetic(char) || isNumeric(char); + return isAlphabetic(char) || isNumeric(char); } export function* lex(str: string): Generator { - let i = 0; - - const peek = () => str[i] ?? ""; - const read = () => str[i++] ?? ""; - const seek = () => { - i++; - }; - - while (i < str.length) { - const indicator = peek(); - - if (isWhitespace(indicator)) { - seek(); - continue; - } - - if (isAlphabetic(indicator)) { - let str = ""; - - while (isAlphanumeric(peek())) { - str += read(); - } - - if (str in keywords) { - yield ["keyword", keywords[str as keyof typeof keywords]]; - } else { - yield ["identifier", str]; - } - - continue; - } - - if (isNumeric(indicator)) { - let numStr = ""; - - while (isNumeric(peek()) || peek() === ".") { - numStr += read(); - } - - yield ["number", Number(numStr)]; - - continue; - } - - if (indicator === '"' || indicator === "'" || indicator === "`") { - const quote = read(); - let acc = ""; - - while (peek() !== quote && i < str.length) { - const char = read(); - - if (char === "\\") { - // Handle escape sequences - const nextChar = peek(); - const escapers = { - n: "\n", - t: "\t", - r: "\r", - b: "\b", - f: "\f", - v: "\v", - "\\": "\\", - }; - if (nextChar in escapers) { - acc += escapers[nextChar as keyof typeof escapers]; - seek(); // consume the escape character - } else if (nextChar === quote) { - acc += quote; // handle escaped quote - seek(); // consume the closing quote - } else { - acc += char; // just add the backslash if no valid escape sequence - } - } else { - acc += char; - } - } - - if (peek() === quote) { - seek(); // consume the closing quote - } - - yield ["string", acc]; - - continue; - } - - if (indicator in symbols) { - yield ["symbol", symbols[indicator as keyof typeof symbols]]; - seek(); // consume the symbol - continue; - } - - // If we reach here, it means we encountered an unknown character - throw new Error( - `Unknown character: '${indicator}' at position ${i}`, - ); - } + let i = 0; + + const peek = () => str[i] ?? ""; + const read = () => str[i++] ?? ""; + const seek = () => { + i++; + }; + + while (i < str.length) { + const indicator = peek(); + + if (isWhitespace(indicator)) { + seek(); + continue; + } + + if (isAlphabetic(indicator)) { + let str = ""; + + while (isAlphanumeric(peek())) { + str += read(); + } + + if (str in keywords) { + yield ["keyword", keywords[str as keyof typeof keywords]]; + } else { + yield ["identifier", str]; + } + + continue; + } + + if (isNumeric(indicator)) { + let numStr = ""; + + while (isNumeric(peek()) || peek() === ".") { + numStr += read(); + } + + yield ["number", Number(numStr)]; + + continue; + } + + if (indicator === '"' || indicator === "'" || indicator === "`") { + const quote = read(); + let acc = ""; + + while (peek() !== quote && i < str.length) { + const char = read(); + + if (char === "\\") { + // Handle escape sequences + const nextChar = peek(); + const escapers = { + n: "\n", + t: "\t", + r: "\r", + b: "\b", + f: "\f", + v: "\v", + "\\": "\\", + }; + if (nextChar in escapers) { + acc += escapers[nextChar as keyof typeof escapers]; + seek(); // consume the escape character + } else if (nextChar === quote) { + acc += quote; // handle escaped quote + seek(); // consume the closing quote + } else { + acc += char; // just add the backslash if no valid escape sequence + } + } else { + acc += char; + } + } + + if (peek() === quote) { + seek(); // consume the closing quote + } + + yield ["string", acc]; + + continue; + } + + if (indicator in symbols) { + yield ["symbol", symbols[indicator as keyof typeof symbols]]; + seek(); // consume the symbol + continue; + } + + // If we reach here, it means we encountered an unknown character + throw new Error(`Unknown character: '${indicator}' at position ${i}`); + } } export interface ParseContext { - tokens: Token[]; - current: number; + tokens: Token[]; + current: number; } export function parseContext_create(tokens: Token[]): ParseContext { - return { - tokens, - current: 0, - }; + return { + tokens, + current: 0, + }; } export function parseContext_next(context: ParseContext): Token | null { - if (context.current >= context.tokens.length) { - return null; // No more tokens - } - return context.tokens[context.current++] ?? null; + if (context.current >= context.tokens.length) { + return null; // No more tokens + } + return context.tokens[context.current++] ?? null; } export function parseContext_peek(context: ParseContext): Token | null { - if (context.current >= context.tokens.length) { - return null; // No more tokens - } - return context.tokens[context.current] ?? null; + if (context.current >= context.tokens.length) { + return null; // No more tokens + } + return context.tokens[context.current] ?? null; } export function parseContext_read(context: ParseContext): Token | null { - const token = parseContext_peek(context); - if (token !== null) { - context.current++; - } - return token; + const token = parseContext_peek(context); + if (token !== null) { + context.current++; + } + return token; } export function parseContext_readObj(context: ParseContext) { - let indicator = parseContext_read(context); - - if (indicator === null) { - throw new Error("Unexpected end of input"); - } - - if (indicator[0] === "number") { - return indicator[1]; - } - - if (indicator[0] === "string") { - return indicator[1]; - } - - if (indicator[0] === "keyword") { - if (indicator[1] === keywords.null) { - return null; - } - if (indicator[1] === keywords.undefined) { - return undefined; - } - if (indicator[1] === keywords.true) { - return true; - } - if (indicator[1] === keywords.false) { - return false; - } - throw new Error(`Unexpected keyword: ${indicator[1]}`); - } - - let clazz = null; - - if (indicator[0] === "identifier") { - clazz = indicator[1]; - indicator = parseContext_read(context); - } - - if (indicator === null) { - throw new Error("Unexpected end of input (after reading class)"); - } - - if (indicator[0] === "symbol" && indicator[1] === "LeftParenthesis") { - const kv: Record = {}; - - while (true) { - const key = parseContext_read(context); - - if (key === null) { - throw new Error( - "Unexpected end of input (when trying to read key)", - ); - } - - if (key[0] === "symbol" && key[1] === "RightParenthesis") { - break; // End of object - } - - if (key[0] !== "identifier" && key[0] !== "string") { - throw new Error( - `Expected identifier or string, got ${key[0]}`, - ); - } - - const equals = parseContext_read(context); - - if ( - equals === null || - equals[0] !== "symbol" || - equals[1] !== "Equals" - ) { - throw new Error( - `Expected '=', got ${equals ? equals[0] : "end of input"}`, - ); - } - - const keyName = key[1]; - - const value = parseContext_readObj(context); - - kv[keyName] = value; - } - - if (clazz !== null) { - if (clazz === "date") { - return new Date(kv.unix as number); - } - if (clazz === "set") { - return new Set(kv.items as unknown[]); - } - if (clazz === "map") { - const map = new Map(); - for (const [key, value] of kv.entries as [ - unknown, - unknown, - ][]) { - map.set(key, value); - } - return map; - } - throw new Error(`Unknown class: ${clazz}`); - } - - return kv; - } - - if (indicator[0] === "symbol" && indicator[1] === "LeftSquareBracket") { - const arr: unknown[] = []; - - while (true) { - if ( - parseContext_peek(context)?.[0] === "symbol" && - parseContext_peek(context)?.[1] === "RightSquareBracket" - ) { - parseContext_read(context); // consume the closing bracket - break; // End of array - } - - const value = parseContext_readObj(context); - - arr.push(value); - } - - return arr; - } + let indicator = parseContext_read(context); + + if (indicator === null) { + throw new Error("Unexpected end of input"); + } + + if (indicator[0] === "number") { + return indicator[1]; + } + + if (indicator[0] === "string") { + return indicator[1]; + } + + if (indicator[0] === "keyword") { + if (indicator[1] === keywords.null) { + return null; + } + if (indicator[1] === keywords.undefined) { + return undefined; + } + if (indicator[1] === keywords.true) { + return true; + } + if (indicator[1] === keywords.false) { + return false; + } + throw new Error(`Unexpected keyword: ${indicator[1]}`); + } + + let clazz = null; + + if (indicator[0] === "identifier") { + clazz = indicator[1]; + indicator = parseContext_read(context); + } + + if (indicator === null) { + throw new Error("Unexpected end of input (after reading class)"); + } + + if (indicator[0] === "symbol" && indicator[1] === "LeftParenthesis") { + const kv: Record = {}; + + while (true) { + const key = parseContext_read(context); + + if (key === null) { + throw new Error( + "Unexpected end of input (when trying to read key)", + ); + } + + if (key[0] === "symbol" && key[1] === "RightParenthesis") { + break; // End of object + } + + if (key[0] !== "identifier" && key[0] !== "string") { + throw new Error(`Expected identifier or string, got ${key[0]}`); + } + + const equals = parseContext_read(context); + + if ( + equals === null || + equals[0] !== "symbol" || + equals[1] !== "Equals" + ) { + throw new Error( + `Expected '=', got ${equals ? equals[0] : "end of input"}`, + ); + } + + const keyName = key[1]; + + const value = parseContext_readObj(context); + + kv[keyName] = value; + } + + if (clazz !== null) { + if (clazz === "date") { + return new Date(kv.unix as number); + } + if (clazz === "set") { + return new Set(kv.items as unknown[]); + } + if (clazz === "map") { + const map = new Map(); + for (const [key, value] of kv.entries as [unknown, unknown][]) { + map.set(key, value); + } + return map; + } + throw new Error(`Unknown class: ${clazz}`); + } + + return kv; + } + + if (indicator[0] === "symbol" && indicator[1] === "LeftSquareBracket") { + const arr: unknown[] = []; + + while (true) { + if ( + parseContext_peek(context)?.[0] === "symbol" && + parseContext_peek(context)?.[1] === "RightSquareBracket" + ) { + parseContext_read(context); // consume the closing bracket + break; // End of array + } + + const value = parseContext_readObj(context); + + arr.push(value); + } + + return arr; + } } export function parse(source: string): unknown { - const tokens = [...lex(source)]; + const tokens = [...lex(source)]; - const context = parseContext_create(tokens); + const context = parseContext_create(tokens); - const result = parseContext_readObj(context); + const result = parseContext_readObj(context); - if (context.current < tokens.length) { - throw new Error( - `Unexpected tokens at the end: ${tokens - .slice(context.current) - .map((t) => t[0]) - .join(", ")}`, - ); - } + if (context.current < tokens.length) { + throw new Error( + `Unexpected tokens at the end: ${tokens + .slice(context.current) + .map((t) => t[0]) + .join(", ")}`, + ); + } - return result; + return result; } export interface SerializeContext { - output: string; - indentLevel: number; - minified: boolean; + output: string; + indentLevel: number; + minified: boolean; } export function serializeContext_create(): SerializeContext { - return { - output: "", - indentLevel: 0, - minified: false, - }; + return { + output: "", + indentLevel: 0, + minified: false, + }; } export function serializeContext_indent(context: SerializeContext): void { - context.indentLevel++; + context.indentLevel++; } export function serializeContext_dedent(context: SerializeContext): void { - context.indentLevel = Math.max(0, context.indentLevel - 1); + context.indentLevel = Math.max(0, context.indentLevel - 1); } export function serializeContext_newline(context: SerializeContext): void { - if (context.minified) { - serializeContext_write(context, " "); - return; - } - serializeContext_write(context, "\n"); - serializeContext_write(context, " ".repeat(context.indentLevel)); + if (context.minified) { + serializeContext_write(context, " "); + return; + } + serializeContext_write(context, "\n"); + serializeContext_write(context, " ".repeat(context.indentLevel)); } export function serializeContext_write( - context: SerializeContext, - str: string, + context: SerializeContext, + str: string, ): void { - context.output += str; + context.output += str; } export function escapeStr(str: string): string { - let minimumLength = Number.POSITIVE_INFINITY; - let result = ""; - - for (const container of [`"`, `'`, "`"]) { - let current = ""; - - current += container; - - for (const char of str) { - if (char === container) { - current += `\\${char}`; - } else if (char === "\\") { - current += "\\\\"; - } else if (char === "\n") { - current += "\\n"; - } else if (char === "\t") { - current += "\\t"; - } else if (char === "\r") { - current += "\\r"; - } else { - current += char; - } - } - - current += container; - - if (current.length < minimumLength) { - minimumLength = current.length; - result = current; - } - } - - return result; + let minimumLength = Number.POSITIVE_INFINITY; + let result = ""; + + for (const container of [`"`, `'`, "`"]) { + let current = ""; + + current += container; + + for (const char of str) { + if (char === container) { + current += `\\${char}`; + } else if (char === "\\") { + current += "\\\\"; + } else if (char === "\n") { + current += "\\n"; + } else if (char === "\t") { + current += "\\t"; + } else if (char === "\r") { + current += "\\r"; + } else { + current += char; + } + } + + current += container; + + if (current.length < minimumLength) { + minimumLength = current.length; + result = current; + } + } + + return result; } export function serializeContext_writeObject( - context: SerializeContext, - obj: unknown, + context: SerializeContext, + obj: unknown, ) { - if (typeof obj === "number") { - serializeContext_write(context, obj.toString()); - return; - } - if (typeof obj === "string") { - serializeContext_write(context, escapeStr(obj)); // Use JSON.stringify to handle escaping - return; - } - if (obj === null) { - serializeContext_write(context, "null"); - return; - } - if (obj === undefined) { - serializeContext_write(context, "undefined"); - return; - } - if (typeof obj === "boolean") { - serializeContext_write(context, obj ? "true" : "false"); - return; - } - if (Array.isArray(obj)) { - serializeContext_write(context, "["); - serializeContext_indent(context); - for (const item of obj) { - serializeContext_newline(context); - serializeContext_writeObject(context, item); - } - serializeContext_dedent(context); - serializeContext_newline(context); - serializeContext_write(context, "]"); - return; - } - if (obj instanceof Date) { - serializeContext_write(context, `date(unix=${obj.getTime()})`); - } - if (obj instanceof Set) { - serializeContext_write(context, "set("); - serializeContext_indent(context); - serializeContext_newline(context); - serializeContext_write(context, "items = "); - serializeContext_writeObject(context, obj.values()); - serializeContext_dedent(context); - serializeContext_newline(context); - serializeContext_write(context, ")"); - } - if (obj instanceof Map) { - serializeContext_write(context, "map("); - serializeContext_indent(context); - serializeContext_newline(context); - serializeContext_write(context, "entries = "); - serializeContext_writeObject(context, obj.entries()); - serializeContext_dedent(context); - serializeContext_newline(context); - serializeContext_write(context, ")"); - } - if (typeof obj === "object") { - serializeContext_write(context, "("); - serializeContext_indent(context); - for (const [key, value] of Object.entries(obj)) { - serializeContext_newline(context); - if ([...key].every(isAlphabetic)) { - serializeContext_write(context, `${key} = `); - } else { - serializeContext_write(context, `${escapeStr(key)} = `); - } - serializeContext_writeObject(context, value); - } - serializeContext_dedent(context); - serializeContext_newline(context); - serializeContext_write(context, ")"); - return; - } - throw new Error(`Unsupported type for serialization: ${typeof obj}`); + if (typeof obj === "number") { + serializeContext_write(context, obj.toString()); + return; + } + if (typeof obj === "string") { + serializeContext_write(context, escapeStr(obj)); // Use JSON.stringify to handle escaping + return; + } + if (obj === null) { + serializeContext_write(context, "null"); + return; + } + if (obj === undefined) { + serializeContext_write(context, "undefined"); + return; + } + if (typeof obj === "boolean") { + serializeContext_write(context, obj ? "true" : "false"); + return; + } + if (Array.isArray(obj)) { + serializeContext_write(context, "["); + serializeContext_indent(context); + for (const item of obj) { + serializeContext_newline(context); + serializeContext_writeObject(context, item); + } + serializeContext_dedent(context); + serializeContext_newline(context); + serializeContext_write(context, "]"); + return; + } + if (obj instanceof Date) { + serializeContext_write(context, `date(unix=${obj.getTime()})`); + } + if (obj instanceof Set) { + serializeContext_write(context, "set("); + serializeContext_indent(context); + serializeContext_newline(context); + serializeContext_write(context, "items = "); + serializeContext_writeObject(context, obj.values()); + serializeContext_dedent(context); + serializeContext_newline(context); + serializeContext_write(context, ")"); + } + if (obj instanceof Map) { + serializeContext_write(context, "map("); + serializeContext_indent(context); + serializeContext_newline(context); + serializeContext_write(context, "entries = "); + serializeContext_writeObject(context, obj.entries()); + serializeContext_dedent(context); + serializeContext_newline(context); + serializeContext_write(context, ")"); + } + if (typeof obj === "object") { + serializeContext_write(context, "("); + serializeContext_indent(context); + for (const [key, value] of Object.entries(obj)) { + serializeContext_newline(context); + if ([...key].every(isAlphabetic)) { + serializeContext_write(context, `${key} = `); + } else { + serializeContext_write(context, `${escapeStr(key)} = `); + } + serializeContext_writeObject(context, value); + } + serializeContext_dedent(context); + serializeContext_newline(context); + serializeContext_write(context, ")"); + return; + } + throw new Error(`Unsupported type for serialization: ${typeof obj}`); } -export function serialize( - obj: unknown, - opts?: { minified?: boolean }, -): string { - const context = serializeContext_create(); +export function serialize(obj: unknown, opts?: { minified?: boolean }): string { + const context = serializeContext_create(); - const minified = opts?.minified ?? false; + const minified = opts?.minified ?? false; - context.minified = minified; + context.minified = minified; - serializeContext_writeObject(context, obj); - return context.output.trim(); + serializeContext_writeObject(context, obj); + return context.output.trim(); } export const stringify = serialize; diff --git a/packages/vortex-core/src/context.ts b/packages/vortex-core/src/context.ts index dd9f87d..233e9a9 100644 --- a/packages/vortex-core/src/context.ts +++ b/packages/vortex-core/src/context.ts @@ -4,151 +4,151 @@ import { clearImmediate, setImmediate } from "./setImmediate.polyfill"; import { type Signal, type SignalOrValue, toSignal } from "./signal"; export interface Context { - (props: { value: SignalOrValue; children: JSXNode }): JSXNode; - use(): Signal; - useOptional(): Signal | undefined; + (props: { value: SignalOrValue; children: JSXNode }): JSXNode; + use(): Signal; + useOptional(): Signal | undefined; } export function createContext(name = "Unnamed"): Context { - const id = crypto.randomUUID(); - - const result = (props: { - value: SignalOrValue; - children: JSXNode; - }): JSXNode => { - return { - type: "context", - id, - value: toSignal(props.value), - children: props.children, - }; - }; - - result.use = () => { - return unwrap( - useContextScope().contexts[id], - `Context "${name}" not found, you may have forgotten to wrap your component in the context provider.`, - ); - }; - - result.useOptional = () => { - return useContextScope().contexts[id]; - } - - return result; + const id = crypto.randomUUID(); + + const result = (props: { + value: SignalOrValue; + children: JSXNode; + }): JSXNode => { + return { + type: "context", + id, + value: toSignal(props.value), + children: props.children, + }; + }; + + result.use = () => { + return unwrap( + useContextScope().contexts[id], + `Context "${name}" not found, you may have forgotten to wrap your component in the context provider.`, + ); + }; + + result.useOptional = () => { + return useContextScope().contexts[id]; + }; + + return result; } export class StreamingContext { - private updateCallbackImmediate = 0; - private updateCallbacks = new Set<() => void>(); - private loadingCounter = 0; - private onDoneLoadingCallback = () => { }; - onDoneLoading: Promise; - - constructor() { - this.onDoneLoading = new Promise((resolve) => { - this.onDoneLoadingCallback = resolve; - }); - } - - onUpdate(callback: () => void): () => void { - this.updateCallbacks.add(callback); - - return () => { - this.updateCallbacks.delete(callback); - }; - } - - markLoading() { - const self = this; - - this.loadingCounter++; - - return { - [Symbol.dispose]() { - self.loadingCounter--; - self.updated(); - }, - }; - } - - updated() { - if (this.updateCallbackImmediate) { - clearImmediate(this.updateCallbackImmediate); - } - - // biome-ignore lint/complexity/noUselessThisAlias: without it, shit breaks - const self = this; - - this.updateCallbackImmediate = setImmediate(() => { - self.updateCallbackImmediate = 0; - - for (const callback of self.updateCallbacks) { - callback(); - } - - if (self.loadingCounter === 0) { - self.onDoneLoadingCallback(); - } - }) as unknown as number; - } + private updateCallbackImmediate = 0; + private updateCallbacks = new Set<() => void>(); + private loadingCounter = 0; + private onDoneLoadingCallback = () => {}; + onDoneLoading: Promise; + + constructor() { + this.onDoneLoading = new Promise((resolve) => { + this.onDoneLoadingCallback = resolve; + }); + } + + onUpdate(callback: () => void): () => void { + this.updateCallbacks.add(callback); + + return () => { + this.updateCallbacks.delete(callback); + }; + } + + markLoading() { + const self = this; + + this.loadingCounter++; + + return { + [Symbol.dispose]() { + self.loadingCounter--; + self.updated(); + }, + }; + } + + updated() { + if (this.updateCallbackImmediate) { + clearImmediate(this.updateCallbackImmediate); + } + + // biome-ignore lint/complexity/noUselessThisAlias: without it, shit breaks + const self = this; + + this.updateCallbackImmediate = setImmediate(() => { + self.updateCallbackImmediate = 0; + + for (const callback of self.updateCallbacks) { + callback(); + } + + if (self.loadingCounter === 0) { + self.onDoneLoadingCallback(); + } + }) as unknown as number; + } } export class ContextScope { - contexts: Record> = {}; - streaming: StreamingContext = new StreamingContext(); + contexts: Record> = {}; + streaming: StreamingContext = new StreamingContext(); - fork() { - const newScope = new ContextScope(); - newScope.contexts = { ...this.contexts }; - return newScope; - } + fork() { + const newScope = new ContextScope(); + newScope.contexts = { ...this.contexts }; + return newScope; + } - addContext(id: string, value: Signal): void { - this.contexts[id] = value; - } + addContext(id: string, value: Signal): void { + this.contexts[id] = value; + } - static current: ContextScope | null = null; + static current: ContextScope | null = null; - static setCurrent(scope: ContextScope | null) { - const previous = ContextScope.current; + static setCurrent(scope: ContextScope | null) { + const previous = ContextScope.current; - ContextScope.current = scope; + ContextScope.current = scope; - return { - [Symbol.dispose]() { - ContextScope.current = previous; - }, - }; - } + return { + [Symbol.dispose]() { + ContextScope.current = previous; + }, + }; + } } export function useContextScope(): ContextScope { - const scope = ContextScope.current; - if (!scope) { - throw new Error( - "No context scope found, you should have one if you're rendering a component.", - ); - } - return scope; + const scope = ContextScope.current; + if (!scope) { + throw new Error( + "No context scope found, you should have one if you're rendering a component.", + ); + } + return scope; } export function useStreaming(): StreamingContext { - return useContextScope().streaming; + return useContextScope().streaming; } export function useOptionalContextScope(): ContextScope | null { - const scope = ContextScope.current; - if (!scope) { - return null; - } - return scope; + const scope = ContextScope.current; + if (!scope) { + return null; + } + return scope; } export function useOptionalStreaming(): StreamingContext | null { - const scope = useOptionalContextScope(); - if (!scope) { - return null; - } - return scope.streaming; + const scope = useOptionalContextScope(); + if (!scope) { + return null; + } + return scope.streaming; } diff --git a/packages/vortex-core/src/jsx/jsx-common.ts b/packages/vortex-core/src/jsx/jsx-common.ts index 51ed0cb..5755b04 100644 --- a/packages/vortex-core/src/jsx/jsx-common.ts +++ b/packages/vortex-core/src/jsx/jsx-common.ts @@ -1,86 +1,86 @@ import { getUltraglobalReference } from "@vortexjs/common"; import { - isSignal, - type Signal, - type Store, - toSignal, - useDerived, + isSignal, + type Signal, + type Store, + toSignal, + useDerived, } from "../signal"; export type JSXNode = - | JSXElement - | JSXComponent - | JSXFragment - | JSXText - | JSXDynamic - | JSXList - | JSXContext - | undefined; + | JSXElement + | JSXComponent + | JSXFragment + | JSXText + | JSXDynamic + | JSXList + | JSXContext + | undefined; export interface JSXContext { - type: "context"; - id: string; - value: Signal; - children: JSXNode; + type: "context"; + id: string; + value: Signal; + children: JSXNode; } export interface JSXList { - type: "list"; - getKey(item: T, index: number): string; - renderItem(item: T, idx: number): JSXNode; - items: Signal; - key(cb: (item: T, idx: number) => string | number): JSXList; - show(cb: (item: T, idx: number) => JSXNode): JSXList; + type: "list"; + getKey(item: T, index: number): string; + renderItem(item: T, idx: number): JSXNode; + items: Signal; + key(cb: (item: T, idx: number) => string | number): JSXList; + show(cb: (item: T, idx: number) => JSXNode): JSXList; } export interface JSXSource { - fileName?: string; - lineNumber?: number; - columnNumber?: number; + fileName?: string; + lineNumber?: number; + columnNumber?: number; } export interface JSXElement extends JSXSource { - type: "element"; - name: string; - attributes: Record>; - bindings: Record>; - eventHandlers: Record void>; - use: Use; - children: JSXNode[]; - styles: Record>; + type: "element"; + name: string; + attributes: Record>; + bindings: Record>; + eventHandlers: Record void>; + use: Use; + children: JSXNode[]; + styles: Record>; } export type Use = ((ref: T) => void) | Use[]; export interface JSXComponent extends JSXSource { - type: "component"; - impl: (props: Props) => JSXNode; - props: Props; + type: "component"; + impl: (props: Props) => JSXNode; + props: Props; } export interface JSXFragment extends JSXSource { - type: "fragment"; - children: JSXNode[]; + type: "fragment"; + children: JSXNode[]; } export interface JSXText extends JSXSource { - type: "text"; - value: string; + type: "text"; + value: string; } export interface JSXDynamic extends JSXSource { - type: "dynamic"; - value: Signal; + type: "dynamic"; + value: Signal; } export interface JSXRuntimeProps { - children?: JSXChildren; - [key: string]: any; + children?: JSXChildren; + [key: string]: any; } export const Fragment = getUltraglobalReference({ - name: "Fragment", - package: "@vortexjs/core" + name: "Fragment", + package: "@vortexjs/core" }, Symbol("Fragment")); export type JSXNonSignalChild = JSXNode | string | number | boolean | undefined; @@ -90,114 +90,111 @@ export type JSXChild = JSXNonSignalChild | Signal; export type JSXChildren = JSXChild | JSXChild[]; export function normalizeChildren(children: JSXChildren): JSXNode[] { - if (children === undefined) { - return []; - } - return [children] - .flat() - .filter((child) => child !== null && child !== undefined) - .map((x) => - typeof x === "string" || - typeof x === "number" || - typeof x === "boolean" - ? createTextNode(x) - : isSignal(x) - ? { - type: "dynamic", - value: useDerived((get) => { - const val = get(x); - return typeof val === "number" || - typeof val === "string" || - typeof val === "boolean" - ? createTextNode(val) - : val; - }), - } - : x, - ); + if (children === undefined) { + return []; + } + return [children] + .flat() + .filter((child) => child !== null && child !== undefined) + .map((x) => + typeof x === "string" || + typeof x === "number" || + typeof x === "boolean" + ? createTextNode(x) + : isSignal(x) + ? { + type: "dynamic", + value: useDerived((get) => { + const val = get(x); + return typeof val === "number" || + typeof val === "string" || + typeof val === "boolean" + ? createTextNode(val) + : val; + }), + } + : x, + ); } export function createTextNode(value: any, source?: JSXSource): JSXNode { - return { - type: "text", - value, - ...source, - }; + return { + type: "text", + value, + ...source, + }; } export function createElementInternal( - type: string, - props: Record, - children: JSXChildren, - source?: JSXSource, + type: string, + props: Record, + children: JSXChildren, + source?: JSXSource, ): JSXNode { - const normalizedChildren = normalizeChildren(children).map((child) => { - if ( - typeof child === "string" || - typeof child === "number" || - typeof child === "boolean" - ) { - return createTextNode(child); - } - return child; - }); - - const properAttributes: Record> = {}; - const bindings: Record> = {}; - const eventHandlers: Record void> = {}; - const use: Use = []; - const styles: Record> = {}; - - for (const [key, value] of Object.entries(props)) { - if (value !== undefined) { - if (key.startsWith("bind:")) { - const bindingKey = key.slice(5); - - if (!isSignal(value) || !("set" in value)) { - throw new Error( - `Binding value for "${bindingKey}" must be a writable store.`, - ); - } - - bindings[bindingKey] = value as Store; - } else if (key.startsWith("on:")) { - const eventKey = key.slice(3); - if (typeof value !== "function") { - throw new Error( - `Event handler for "${eventKey}" must be a function.`, - ); - } - eventHandlers[eventKey] = value; - } else if (key === "use") { - if (typeof value !== "function" && !Array.isArray(value)) { - throw new Error( - "Use hook must be a function or an array of functions.", - ); - } - use.push(value); - } else if (key === "style") { - for (const [styleKey, styleValue] of Object.entries(value)) { - if (styleValue !== undefined) { - styles[styleKey] = toSignal(styleValue); - } - } - } else { - const valsig = toSignal(value); - properAttributes[key] = useDerived((get) => - String(get(valsig)), - ); - } - } - } - - return { - type: "element", - name: type, - attributes: properAttributes, - children: normalizedChildren, - bindings, - eventHandlers, - use, - styles, - }; + const normalizedChildren = normalizeChildren(children).map((child) => { + if ( + typeof child === "string" || + typeof child === "number" || + typeof child === "boolean" + ) { + return createTextNode(child); + } + return child; + }); + + const properAttributes: Record> = {}; + const bindings: Record> = {}; + const eventHandlers: Record void> = {}; + const use: Use = []; + const styles: Record> = {}; + + for (const [key, value] of Object.entries(props)) { + if (value !== undefined) { + if (key.startsWith("bind:")) { + const bindingKey = key.slice(5); + + if (!isSignal(value) || !("set" in value)) { + throw new Error( + `Binding value for "${bindingKey}" must be a writable store.`, + ); + } + + bindings[bindingKey] = value as Store; + } else if (key.startsWith("on:")) { + const eventKey = key.slice(3); + if (typeof value !== "function") { + throw new Error( + `Event handler for "${eventKey}" must be a function.`, + ); + } + eventHandlers[eventKey] = value; + } else if (key === "use") { + if (typeof value !== "function" && !Array.isArray(value)) { + throw new Error( + "Use hook must be a function or an array of functions.", + ); + } + use.push(value); + } else if (key === "style") { + for (const [styleKey, styleValue] of Object.entries(value)) { + if (styleValue !== undefined) { + styles[styleKey] = toSignal(styleValue); + } + } + } else { + properAttributes[key] = toSignal(value); + } + } + } + + return { + type: "element", + name: type, + attributes: properAttributes, + children: normalizedChildren, + bindings, + eventHandlers, + use, + styles, + }; } diff --git a/packages/vortex-core/src/render/fragments.ts b/packages/vortex-core/src/render/fragments.ts index 55e607c..5ae2df6 100644 --- a/packages/vortex-core/src/render/fragments.ts +++ b/packages/vortex-core/src/render/fragments.ts @@ -3,121 +3,123 @@ import { unwrap } from "@vortexjs/common"; import type { Renderer } from "."; +import type { ContextScope } from "../context"; export abstract class FLNode { - _children: FLNode[] = []; - parent: FLNode | null = null; - rendererNode: RendererNode | null = null; - - abstract onChildrenChanged(): void; - - get children(): FLNode[] { - return this._children; - } - - set children(next: FLNode[]) { - this._children = next; - for (const child of next) { - child.parent = this; - } - this.onChildrenChanged(); - } - - get flatChildren(): FLNode[] { - const flat: FLNode[] = []; - - function traverse(node: FLNode) { - if (node instanceof FLFragment) { - for (const child of node.children) { - traverse(child); - } - } else { - flat.push(node); - } - } - - for (const child of this.children) { - traverse(child); - } - - return flat; - } + _children: FLNode[] = []; + parent: FLNode | null = null; + rendererNode: RendererNode | null = null; + + abstract onChildrenChanged(): void; + + get children(): FLNode[] { + return this._children; + } + + set children(next: FLNode[]) { + this._children = next; + for (const child of next) { + child.parent = this; + } + this.onChildrenChanged(); + } + + get flatChildren(): FLNode[] { + const flat: FLNode[] = []; + + function traverse(node: FLNode) { + if (node instanceof FLFragment) { + for (const child of node.children) { + traverse(child); + } + } else { + flat.push(node); + } + } + + for (const child of this.children) { + traverse(child); + } + + return flat; + } } export class FLFragment extends FLNode { - onChildrenChanged(): void { - this.parent?.onChildrenChanged(); - } + onChildrenChanged(): void { + this.parent?.onChildrenChanged(); + } } export class FLText< - RendererNode, - HydrationContext, + RendererNode, + HydrationContext, > extends FLNode { - _text: string; - - get text(): string { - return this._text; - } - - set text(value: string) { - this._text = value; - this.renderer.setTextContent(unwrap(this.rendererNode), value); - } - - constructor( - text: string, - private renderer: Renderer, - hydration?: HydrationContext, - ) { - super(); - this._text = text; - this.rendererNode = renderer.createTextNode(hydration); - renderer.setTextContent(this.rendererNode, text); - } - - onChildrenChanged(): void { - this.parent?.onChildrenChanged(); - } + _text: string; + + get text(): string { + return this._text; + } + + set text(value: string) { + this._text = value; + this.renderer.setTextContent(unwrap(this.rendererNode), value); + } + + constructor( + text: string, + private renderer: Renderer, + hydration: HydrationContext | undefined, + scope: ContextScope + ) { + super(); + this._text = text; + this.rendererNode = renderer.createTextNode(hydration, scope); + renderer.setTextContent(this.rendererNode, text); + } + + onChildrenChanged(): void { + this.parent?.onChildrenChanged(); + } } export class FLElement< - RendererNode, - HydrationContext, + RendererNode, + HydrationContext, > extends FLNode { - setAttribute(name: string, value: any): void { - this.renderer.setAttribute(unwrap(this.rendererNode), name, value); - } - - constructor( - private tag: string, - private renderer: Renderer, - hydration?: HydrationContext, - ) { - super(); - this.rendererNode = renderer.createNode(tag, hydration); - } - - onChildrenChanged(): void { - const children = this.flatChildren.map((child) => - unwrap(child.rendererNode), - ); - this.renderer.setChildren(unwrap(this.rendererNode), children); - } + setAttribute(name: string, value: any): void { + this.renderer.setAttribute(unwrap(this.rendererNode), name, value); + } + + constructor( + private tag: string, + private renderer: Renderer, + hydration?: HydrationContext, + ) { + super(); + this.rendererNode = renderer.createNode(tag, hydration); + } + + onChildrenChanged(): void { + const children = this.flatChildren.map((child) => + unwrap(child.rendererNode), + ); + this.renderer.setChildren(unwrap(this.rendererNode), children); + } } export class FLPortal extends FLNode { - constructor( - private target: RendererNode, - private renderer: Renderer, - ) { - super(); - } - - onChildrenChanged(): void { - this.renderer.setChildren( - this.target, - this.flatChildren.map((child) => unwrap(child.rendererNode)), - ); - } + constructor( + private target: RendererNode, + private renderer: Renderer, + ) { + super(); + } + + onChildrenChanged(): void { + this.renderer.setChildren( + this.target, + this.flatChildren.map((child) => unwrap(child.rendererNode)), + ); + } } diff --git a/packages/vortex-core/src/render/index.ts b/packages/vortex-core/src/render/index.ts index 89a0705..dfc68ec 100644 --- a/packages/vortex-core/src/render/index.ts +++ b/packages/vortex-core/src/render/index.ts @@ -18,7 +18,7 @@ export * as FL from "./fragments"; export interface Renderer { createNode(type: string, hydration?: HydrationContext): RendererNode; setAttribute(node: RendererNode, name: string, value: any): void; - createTextNode(hydration?: HydrationContext): RendererNode; + createTextNode(hydration: HydrationContext | undefined, context: ContextScope): RendererNode; setTextContent(node: RendererNode, text: string): void; setChildren(node: RendererNode, children: RendererNode[]): void; getHydrationContext(node: RendererNode): HydrationContext; @@ -29,7 +29,7 @@ export interface Renderer { ): Lifetime; bindValue(node: RendererNode, name: string, value: Store): Lifetime; setStyle(node: RendererNode, name: string, value: string | undefined): void; - implementations?: IntrinsicImplementation[]; + implementations?: IntrinsicImplementation[]; } export interface RenderProps { diff --git a/packages/vortex-core/src/render/reconciler.ts b/packages/vortex-core/src/render/reconciler.ts index 651f21f..61d7624 100644 --- a/packages/vortex-core/src/render/reconciler.ts +++ b/packages/vortex-core/src/render/reconciler.ts @@ -45,6 +45,7 @@ export class Reconciler { node.value.toString(), this.renderer, hydration, + context ); } case "element": { diff --git a/packages/vortex-intrinsics/.gitignore b/packages/vortex-intrinsics/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/vortex-intrinsics/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/vortex-intrinsics/.npmignore b/packages/vortex-intrinsics/.npmignore new file mode 100644 index 0000000..dfe1eec --- /dev/null +++ b/packages/vortex-intrinsics/.npmignore @@ -0,0 +1,33 @@ +# dependencies (bun install) +node_modules + +# output +out +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/vortex-intrinsics/CHANGELOG.md b/packages/vortex-intrinsics/CHANGELOG.md new file mode 100644 index 0000000..d6fb7a8 --- /dev/null +++ b/packages/vortex-intrinsics/CHANGELOG.md @@ -0,0 +1,13 @@ +# @vortexjs/cache + +## 0.0.1 + +### Patch Changes + +- Updated dependencies [f870d4f] +- Updated dependencies [0885d49] +- Updated dependencies [1f8e9da] +- Updated dependencies [936f5d6] +- Updated dependencies [50075fb] +- Updated dependencies [1f8e9da] + - @vortexjs/common@0.1.0 diff --git a/packages/vortex-intrinsics/README.md b/packages/vortex-intrinsics/README.md new file mode 100644 index 0000000..5c490a4 --- /dev/null +++ b/packages/vortex-intrinsics/README.md @@ -0,0 +1,7 @@ +# Vortex Cache + +A very fast caching system for making rebuilds fast(er). + +## Why another library? + +Because we want to be able to pass around one cache object throughout many libraries (although right now it's just `pippin`, `discovery`, and `wormhole`) diff --git a/packages/vortex-intrinsics/package.json b/packages/vortex-intrinsics/package.json new file mode 100644 index 0000000..9846167 --- /dev/null +++ b/packages/vortex-intrinsics/package.json @@ -0,0 +1,30 @@ +{ + "name": "@vortexjs/intrinsics", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsup": "catalog:" + }, + "dependencies": { + "@vortexjs/common": "workspace:*", + "@vortexjs/core": "workspace:*" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsup ./src/index.ts --format esm --dts --out-dir dist" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "version": "0.0.0" +} diff --git a/packages/vortex-intrinsics/src/index.ts b/packages/vortex-intrinsics/src/index.ts new file mode 100644 index 0000000..dd9d903 --- /dev/null +++ b/packages/vortex-intrinsics/src/index.ts @@ -0,0 +1,117 @@ +import { intrinsic, type JSXChildren } from "@vortexjs/core"; + +type TextSize = "title" | "heading" | "subheading" | "body" | "caption"; + +export type FontWeight = "normal" | "bold" | "bolder" | "lighter" | number; + +export function fontWeightToNumber(weight: FontWeight): number { + switch (weight) { + case "normal": + return 400; + case "bold": + return 700; + case "bolder": + return 800; + case "lighter": + return 300; + default: + return weight; + } +} + +export function fontWeightToPrimitiveBoldness( + weight: FontWeight, +): "normal" | "bold" { + return fontWeightToNumber(weight) >= 700 ? "bold" : "normal"; +} + +export const Text = intrinsic< + { + children: JSXChildren; + color?: string; + weight?: FontWeight; + italic?: boolean; + underline?: boolean; + size?: number | TextSize; + }, + "vortex:text" +>("vortex:text"); + +export type UDLRDescription = + | { + base?: number | T; + top?: number | T; + right?: number | T; + bottom?: number | T; + left?: number | T; + x?: number | T; + y?: number | T; + } + | number + | T; + +export function resolveUDLRDescription( + desc: UDLRDescription, +): { + top: T | number; + right: T | number; + bottom: T | number; + left: T | number; +} { + if (typeof desc === "number" || typeof desc === "string") { + return { top: desc, right: desc, bottom: desc, left: desc }; + } + return { + top: desc.top ?? desc.y ?? desc.base ?? 0, + right: desc.right ?? desc.x ?? desc.base ?? 0, + bottom: desc.bottom ?? desc.y ?? desc.base ?? 0, + left: desc.left ?? desc.x ?? desc.base ?? 0, + }; +} + +export type Background = + | { + color?: string; + } + | string; +export type Border = + | { + color?: string; + width?: number; + radius?: number; + } + | string; + +export const Frame = intrinsic< + { + children: JSXChildren; + border?: Border; + background?: Background; + + padding?: UDLRDescription; + margin?: UDLRDescription; + width?: number | string; + height?: number | string; + minWidth?: number | string; + minHeight?: number | string; + maxWidth?: number | string; + maxHeight?: number | string; + direction?: "row" | "column" | "row-reverse" | "column-reverse"; + grow?: number; + gap?: number | string; + alignItems?: + | "flex-start" + | "flex-end" + | "center" + | "stretch" + | "baseline"; + justifyContent?: + | "flex-start" + | "flex-end" + | "center" + | "space-between" + | "space-around" + | "space-evenly"; + }, + "vortex:frame" +>("vortex:frame"); diff --git a/packages/vortex-intrinsics/tsconfig.json b/packages/vortex-intrinsics/tsconfig.json new file mode 100644 index 0000000..d715a95 --- /dev/null +++ b/packages/vortex-intrinsics/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": ["src/**/*", "test/**/*"] +} diff --git a/packages/vortex-ssr/src/index.test.tsx b/packages/vortex-ssr/src/index.test.tsx index 1013b5e..1c3b509 100644 --- a/packages/vortex-ssr/src/index.test.tsx +++ b/packages/vortex-ssr/src/index.test.tsx @@ -1,12 +1,6 @@ import { expect, test } from "bun:test"; import { render } from "@vortexjs/core"; -import { - createCodegenStream, - createHTMLRoot, - diffInto, - printHTML, - ssr, -} from "."; +import { createHTMLRoot, diffInto, printHTML, ssr } from "."; function App() { return ( @@ -43,7 +37,8 @@ test("html printing", () => { render(ssr(), root, ); expect(printHTML(root)).toMatchInlineSnapshot( - `"Test Page

Hello, World!

"`); + `"Test Page

Hello, World!

"`, + ); }); test("html diffing", () => { @@ -58,5 +53,6 @@ test("html diffing", () => { const codegen = diffInto(root, root2); expect(codegen.getCode()).toMatchInlineSnapshot( - `"var $0=document.documentElement;var $1="insertBefore";var $2=document.body;var $3=document.head;$0[$1]($2, $3);var $4="appendChild";var $5="createElement";$2[$4](document[$5]("p"));var $6="querySelectorAll";var $7="h1";var $8=document[$6]($7)[0];var $9="removeAttribute";$8[$9]("style");var $a="childNodes";var $b=$8[$a][0];var $c="textContent";$b[$c] = " Hello, multiverse! ";var $d="p";var $e=document[$6]($d)[0];var $f="createTextNode";$e[$4](document[$f](""));var $g=$e[$a][0];$g[$c] = "I hope you like my website, I worked really hard on it!!!";"`); + `"var $0=document.documentElement;var $1="insertBefore";var $2=document.body;var $3=document.head;$0[$1]($2, $3);var $4="appendChild";var $5="createElement";$2[$4](document[$5]("p"));var $6="querySelectorAll";var $7="h1";var $8=document[$6]($7)[0];var $9="removeAttribute";$8[$9]("style");var $a="childNodes";var $b=$8[$a][0];var $c="textContent";$b[$c] = " Hello, multiverse! ";var $d="p";var $e=document[$6]($d)[0];var $f="createTextNode";$e[$4](document[$f](""));var $g=$e[$a][0];$g[$c] = "I hope you like my website, I worked really hard on it!!!";"`, + ); }); diff --git a/packages/vortex-ssr/src/index.ts b/packages/vortex-ssr/src/index.ts index 520739a..d63d005 100644 --- a/packages/vortex-ssr/src/index.ts +++ b/packages/vortex-ssr/src/index.ts @@ -21,7 +21,7 @@ export type VNode = VElement | VText; function documentBadQuerySelector( document: VNode, - tagName: string + tagName: string, ): VElement[] { const result: VElement[] = []; @@ -63,7 +63,10 @@ function getIdent(node: VNode, codegen: CodegenStream): string { if (!ident && "tagName" in node) { // Find the index that this node would be in a fake document.querySelector - const index = documentBadQuerySelector(codegen.document, node.tagName).indexOf(node); + const index = documentBadQuerySelector( + codegen.document, + node.tagName, + ).indexOf(node); ident ??= `document[${codegen.getIndexerShorthand("querySelectorAll")}](${codegen.getIndexerShorthand(node.tagName)})[${index}]`; } @@ -197,7 +200,11 @@ export function printHTML(node: VNode, printer = createHTMLPrinter()): string { return printer.html; } -export function diffInto(from: VNode, to: VNode, codegen: CodegenStream = createCodegenStream(from as VElement)) { +export function diffInto( + from: VNode, + to: VNode, + codegen: CodegenStream = createCodegenStream(from as VElement), +) { if ("tagName" in from && "tagName" in to) { // Safety check to ensure both nodes are of the same type if (getType(from) !== getType(to)) { diff --git a/packages/wormhole/package.json b/packages/wormhole/package.json index 44b6479..d42d84e 100644 --- a/packages/wormhole/package.json +++ b/packages/wormhole/package.json @@ -1,47 +1,49 @@ { - "name": "@vortexjs/wormhole", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "bin": { - "wormhole": "./dist/cli.js", - "wh": "./dist/cli.js" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsup": "catalog:" - }, - "dependencies": { - "@speed-highlight/core": "catalog:", - "@vortexjs/common": "workspace:*", - "@vortexjs/core": "workspace:*", - "@vortexjs/discovery": "workspace:*", - "@vortexjs/dom": "workspace:*", - "@vortexjs/ssr": "workspace:*", - "@vortexjs/pippin-plugin-tailwind": "workspace:", - "@vortexjs/pippin": "workspace:*", - "chalk": "catalog:" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsup ./src/index.ts ./src/cli.ts ./src/virt/route.ts --format esm --dts --out-dir dist --external bun", - "test": "bun test" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, - "./route": { - "types": "./dist/virt/route.d.ts", - "import": "./dist/virt/route.js", - "require": "./dist/virt/route.cjs" - } - }, - "version": "0.2.0" + "name": "@vortexjs/wormhole", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "bin": { + "wormhole": "./dist/cli.js", + "wh": "./dist/cli.js" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsup": "catalog:" + }, + "dependencies": { + "@speed-highlight/core": "catalog:", + "@vortexjs/common": "workspace:*", + "@vortexjs/core": "workspace:*", + "@vortexjs/discovery": "workspace:*", + "@vortexjs/dom": "workspace:*", + "@vortexjs/ssr": "workspace:*", + "@vortexjs/pippin-plugin-tailwind": "workspace:", + "@vortexjs/pippin": "workspace:*", + "@vortexjs/cli": "workspace:*", + "@vortexjs/intrinsics": "workspace:*", + "chalk": "catalog:" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsup ./src/index.ts ./src/cli.tsx ./src/virt/route.ts --format esm --dts --out-dir dist --external bun", + "test": "bun test" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./route": { + "types": "./dist/virt/route.d.ts", + "import": "./dist/virt/route.js", + "require": "./dist/virt/route.cjs" + } + }, + "version": "0.2.0" } diff --git a/packages/wormhole/src/cli.ts b/packages/wormhole/src/cli.ts deleted file mode 100644 index 8c65bd6..0000000 --- a/packages/wormhole/src/cli.ts +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bun - -import { Lifetime } from "@vortexjs/core"; -import { StatusBoard } from "~/cli/statusboard"; -import { Project } from "~/state"; -import { DevServer } from "./dev/dev-server"; - -const projectDir = process.cwd(); - -const lt = new Lifetime(); - -const state = new Project(projectDir, lt); - -await state.init(); - -DevServer(state); - -StatusBoard(state); diff --git a/packages/wormhole/src/cli.tsx b/packages/wormhole/src/cli.tsx new file mode 100644 index 0000000..24f3bf3 --- /dev/null +++ b/packages/wormhole/src/cli.tsx @@ -0,0 +1,42 @@ +#!/usr/bin/env bun + +import { cliApp, colors } from "@vortexjs/cli"; +import { Frame, Text } from "@vortexjs/intrinsics"; + +cliApp( + + + This is a{" "} + + *test* + + + + + This is not a{" "} + + *test* + + + , +); + +// const projectDir = process.cwd(); + +// const lt = new Lifetime(); + +// const state = new Project(projectDir, lt); + +// await state.init(); + +// DevServer(state); + +// StatusBoard(state); From 5a4c4460f661b32999c997cf64eae40994a23baa Mon Sep 17 00:00:00 2001 From: andylovescode Date: Sat, 16 Aug 2025 13:42:49 -0700 Subject: [PATCH 06/13] CLIv2: Slight improvements --- packages/locounter/package.json | 40 +++++++++++------------ packages/locounter/src/index.ts | 5 ++- packages/vortex-cli/src/tokens/symbols.ts | 1 + packages/vortex-cli/src/tree/index.ts | 11 ++----- packages/vortex-intrinsics/src/index.ts | 1 + packages/wormhole/src/cli.tsx | 19 +++++++++-- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/packages/locounter/package.json b/packages/locounter/package.json index 9051691..1de831c 100644 --- a/packages/locounter/package.json +++ b/packages/locounter/package.json @@ -1,22 +1,22 @@ { - "name": "@vortexjs/locounter", - "module": "src/index.ts", - "type": "module", - "license": "MIT-0", - "private": true, - "bin": { - "locounter": "./src/index.ts" - }, - "devDependencies": { - "@types/bun": "catalog:" - }, - "dependencies": { - "@vortexjs/common": "workspace:*" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "count": "bun run src/index.ts" - } + "name": "@vortexjs/locounter", + "module": "src/index.ts", + "type": "module", + "license": "MIT-0", + "version": "0.0.0", + "bin": { + "locounter": "./src/index.ts" + }, + "devDependencies": { + "@types/bun": "catalog:" + }, + "dependencies": { + "@vortexjs/common": "workspace:*" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "count": "bun run src/index.ts" + } } diff --git a/packages/locounter/src/index.ts b/packages/locounter/src/index.ts index 17c75ba..eb1a103 100644 --- a/packages/locounter/src/index.ts +++ b/packages/locounter/src/index.ts @@ -30,7 +30,10 @@ async function countLinesOfCode(directory: string): Promise { name: child.name, count: countLinesOfCode(path), }); - } else if (child.isFile() && child.name.endsWith(".ts")) { + } else if ( + child.isFile() && + (child.name.endsWith(".ts") || child.name.endsWith(".tsx")) + ) { childLocs.push({ name: child.name, count: Bun.file(path) diff --git a/packages/vortex-cli/src/tokens/symbols.ts b/packages/vortex-cli/src/tokens/symbols.ts index 9fff851..0e026e6 100644 --- a/packages/vortex-cli/src/tokens/symbols.ts +++ b/packages/vortex-cli/src/tokens/symbols.ts @@ -17,6 +17,7 @@ function udlrSymbol16(symbols: UDLRSymbolSet) { } const symbols = { + star4: "✦", outline: { rounded: udlrSymbol16({ u: "╵", diff --git a/packages/vortex-cli/src/tree/index.ts b/packages/vortex-cli/src/tree/index.ts index 31d5d1c..dba4fa0 100644 --- a/packages/vortex-cli/src/tree/index.ts +++ b/packages/vortex-cli/src/tree/index.ts @@ -109,12 +109,6 @@ export class Box implements TreeNode { update() { this.yoga.setFlexGrow(this.attributes.grow); - this.yoga.setFlexDirection({ - row: FlexDirection.Row, - "row-reverse": FlexDirection.RowReverse, - column: FlexDirection.Column, - "column-reverse": FlexDirection.ColumnReverse - }[this.attributes.direction ?? "row"]); this.yoga.setGap(Gutter.Row, pixelToTileX(this.attributes.gap ?? 0) as number); this.yoga.setGap(Gutter.Column, pixelToTileY(this.attributes.gap ?? 0) as number); @@ -154,7 +148,7 @@ export class Box implements TreeNode { break; } - switch (this.attributes.flexDirection) { + switch (this.attributes.direction) { case "row": this.yoga.setFlexDirection(FlexDirection.Row); break; @@ -202,6 +196,7 @@ export class Box implements TreeNode { ? pixelToTileY(this.attributes.maxHeight) : (this.attributes.maxHeight as any) ); + this.yoga.setAlwaysFormsContainingBlock(true); } } @@ -276,7 +271,7 @@ export class Text implements TreeNode { italic, bold, underline, - background: "transparent" + background }) } continue; diff --git a/packages/vortex-intrinsics/src/index.ts b/packages/vortex-intrinsics/src/index.ts index dd9d903..36eda58 100644 --- a/packages/vortex-intrinsics/src/index.ts +++ b/packages/vortex-intrinsics/src/index.ts @@ -33,6 +33,7 @@ export const Text = intrinsic< italic?: boolean; underline?: boolean; size?: number | TextSize; + background?: string; }, "vortex:text" >("vortex:text"); diff --git a/packages/wormhole/src/cli.tsx b/packages/wormhole/src/cli.tsx index 24f3bf3..21b972d 100644 --- a/packages/wormhole/src/cli.tsx +++ b/packages/wormhole/src/cli.tsx @@ -14,10 +14,11 @@ cliApp( gap={2} > - This is a{" "} - - *test* + + + • + wormhole @@ -26,6 +27,18 @@ cliApp( *test* + + + a smol frame + , ); From 895af3bd32cfa2d4e3b40d2a4cd220fdde16e887 Mon Sep 17 00:00:00 2001 From: andylovescode Date: Sat, 16 Aug 2025 20:03:11 -0700 Subject: [PATCH 07/13] Build: Use tsdown --- bun.lock | 280 ++++++------------- package.json | 2 +- packages/discovery/package.json | 64 ++--- packages/pippin-plugin-tailwind/package.json | 4 +- packages/pippin/package.json | 4 +- packages/vortex-cache/package.json | 4 +- packages/vortex-cli/package.json | 4 +- packages/vortex-common/package.json | 50 ++-- packages/vortex-core/package.json | 76 ++--- packages/vortex-dom/package.json | 76 ++--- packages/vortex-intrinsics/package.json | 4 +- packages/vortex-prime/package.json | 58 ++-- packages/vortex-ssr/package.json | 60 ++-- packages/wormhole/package.json | 94 +++---- 14 files changed, 338 insertions(+), 442 deletions(-) diff --git a/bun.lock b/bun.lock index 101aabe..1612bfa 100644 --- a/bun.lock +++ b/bun.lock @@ -37,7 +37,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -75,6 +75,7 @@ }, "packages/locounter": { "name": "@vortexjs/locounter", + "version": "0.0.0", "bin": { "locounter": "./src/index.ts", }, @@ -98,7 +99,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -115,7 +116,21 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", + }, + "peerDependencies": { + "typescript": "catalog:", + }, + }, + "packages/vortex-args": { + "name": "@vortexjs/args", + "version": "0.0.1", + "dependencies": { + "@vortexjs/common": "workspace:*", + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -129,7 +144,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -146,7 +161,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -157,7 +172,7 @@ "version": "0.1.0", "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -171,7 +186,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -186,7 +201,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -201,7 +216,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -217,7 +232,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -233,7 +248,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -261,7 +276,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:", + "tsdown": "catalog:", }, "peerDependencies": { "typescript": "catalog:", @@ -269,7 +284,6 @@ }, }, "trustedDependencies": [ - "esbuild", "@biomejs/biome", ], "patchedDependencies": { @@ -291,15 +305,25 @@ "esrap": "https://pkg.pr.new/sveltejs/esrap@76", "oxc-parser": "^0.75.1", "tailwindcss": "^4.1.10", - "tsup": "^8.5.0", + "tsdown": "^0.14.1", "typescript": "^5", "valibot": "^1.1.0", }, "packages": { "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/parser": ["@babel/parser@7.28.3", "", { "dependencies": { "@babel/types": "^7.28.2" }, "bin": "./bin/babel-parser.js" }, "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA=="], + "@babel/runtime": ["@babel/runtime@7.28.2", "", {}, "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA=="], + "@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], + "@biomejs/biome": ["@biomejs/biome@2.0.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.0.6", "@biomejs/cli-darwin-x64": "2.0.6", "@biomejs/cli-linux-arm64": "2.0.6", "@biomejs/cli-linux-arm64-musl": "2.0.6", "@biomejs/cli-linux-x64": "2.0.6", "@biomejs/cli-linux-x64-musl": "2.0.6", "@biomejs/cli-win32-arm64": "2.0.6", "@biomejs/cli-win32-x64": "2.0.6" }, "bin": { "biome": "bin/biome" } }, "sha512-RRP+9cdh5qwe2t0gORwXaa27oTOiQRQvrFf49x2PA1tnpsyU7FIHX4ZOFMtBC4QNtyWsN7Dqkf5EDbg4X+9iqA=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.0.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AzdiNNjNzsE6LfqWyBvcL29uWoIuZUkndu+wwlXW13EKcBHbbKjNQEZIJKYDc6IL+p7bmWGx3v9ZtcRyIoIz5A=="], @@ -358,58 +382,6 @@ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="], - "@inquirer/checkbox": ["@inquirer/checkbox@4.2.0", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/figures": "^1.0.13", "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA=="], "@inquirer/confirm": ["@inquirer/confirm@5.1.14", "", { "dependencies": { "@inquirer/core": "^10.1.15", "@inquirer/type": "^3.0.8" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q=="], @@ -438,8 +410,6 @@ "@inquirer/type": ["@inquirer/type@3.0.8", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw=="], - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], @@ -494,49 +464,41 @@ "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.75.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ThiQUpCG2nYE/bnYM3fjIpcKbxITB/a/cf5VL0VAqtpsGNCzUC7TrwMVUdfBerTBTEZpwxWBf/d1EF1ggrtVfQ=="], - "@oxc-project/types": ["@oxc-project/types@0.75.1", "", {}, "sha512-7ZJy+51qWpZRvynaQUezeYfjCtaSdiXIWFUZIlOuTSfDXpXqnSl/m1IUPLx6XrOy6s0SFv3CLE14vcZy63bz7g=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@oxc-project/runtime": ["@oxc-project/runtime@0.81.0", "", {}, "sha512-zm/LDVOq9FEmHiuM8zO4DWirv0VP2Tv2VsgaiHby9nvpq+FVrcqNYgv+TysLKOITQXWZj/roluTxFvpkHP0Iuw=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.46.0", "", { "os": "android", "cpu": "arm" }, "sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.46.0", "", { "os": "android", "cpu": "arm64" }, "sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.46.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.46.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og=="], + "@oxc-project/types": ["@oxc-project/types@0.75.1", "", {}, "sha512-7ZJy+51qWpZRvynaQUezeYfjCtaSdiXIWFUZIlOuTSfDXpXqnSl/m1IUPLx6XrOy6s0SFv3CLE14vcZy63bz7g=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.46.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg=="], + "@quansync/fs": ["@quansync/fs@0.1.5", "", { "dependencies": { "quansync": "^0.2.11" } }, "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.46.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.32", "", { "os": "android", "cpu": "arm64" }, "sha512-Gs+313LfR4Ka3hvifdag9r44WrdKQaohya7ZXUXzARF7yx0atzFlVZjsvxtKAw1Vmtr4hB/RjUD1jf73SW7zDw=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.46.0", "", { "os": "linux", "cpu": "arm" }, "sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.32", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W8oMqzGcI7wKPXUtS3WJNXzbghHfNiuM1UBAGpVb+XlUCgYRQJd2PRGP7D3WGql3rR3QEhUvSyAuCBAftPQw6Q=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.46.0", "", { "os": "linux", "cpu": "arm" }, "sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.32", "", { "os": "darwin", "cpu": "x64" }, "sha512-pM4c4sKUk37noJrnnDkJknLhCsfZu7aWyfe67bD0GQHfzAPjV16wPeD9CmQg4/0vv+5IfHYaa4VE536xbA+W0Q=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.46.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.32", "", { "os": "freebsd", "cpu": "x64" }, "sha512-M8SUgFlYb5kJJWcFC8gUMRiX4WLFxPKMed3SJ2YrxontgIrEcpizPU8nLNVsRYEStoSfKHKExpQw3OP6fm+5bw=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.46.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.32", "", { "os": "linux", "cpu": "arm" }, "sha512-FuQpbNC/hE//bvv29PFnk0AtpJzdPdYl5CMhlWPovd9g3Kc3lw9TrEPIbL7gRPUdhKAiq6rVaaGvOnXxsa0eww=="], - "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.46.0", "", { "os": "linux", "cpu": "none" }, "sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.32", "", { "os": "linux", "cpu": "arm64" }, "sha512-hRZygRlaGCjcNTNY9GV7dDI18sG1dK3cc7ujHq72LoDad23zFDUGMQjiSxHWK+/r92iMV+j2MiHbvzayxqynsg=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.46.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.32", "", { "os": "linux", "cpu": "arm64" }, "sha512-HzgT6h+CXLs+GKAU0Wvkt3rvcv0CmDBsDjlPhh4GHysOKbG9NjpKYX2zvjx671E9pGbTvcPpwy7gGsy7xpu+8g=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.46.0", "", { "os": "linux", "cpu": "none" }, "sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.32", "", { "os": "linux", "cpu": "x64" }, "sha512-Ab/wbf6gdzphDbsg51UaxsC93foQ7wxhtg0SVCXd25BrV4MAJ1HoDtKN/f4h0maFmJobkqYub2DlmoasUzkvBg=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.46.0", "", { "os": "linux", "cpu": "none" }, "sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.32", "", { "os": "linux", "cpu": "x64" }, "sha512-VoxqGEfh5A1Yx+zBp/FR5QwAbtzbuvky2SVc+ii4g1gLD4zww6mt/hPi5zG+b88zYPFBKHpxMtsz9cWqXU5V5Q=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.46.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.32", "", { "os": "none", "cpu": "arm64" }, "sha512-qZ1ViyOUDGbiZrSAJ/FIAhYUElDfVxxFW6DLT/w4KeoZN3HsF4jmRP95mXtl51/oGrqzU9l9Q2f7/P4O/o2ZZA=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.46.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg=="], + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.32", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.3" }, "cpu": "none" }, "sha512-hEkG3wD+f3wytV0lqwb/uCrXc4r4Ny/DWJFJPfQR3VeMWplhWGgSHNwZc2Q7k86Yi36f9NNzzWmrIuvHI9lCVw=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.46.0", "", { "os": "linux", "cpu": "x64" }, "sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.32", "", { "os": "win32", "cpu": "arm64" }, "sha512-k3MvDf8SiA7uP2ikP0unNouJ2YCrnwi7xcVW+RDgMp5YXVr3Xu6svmT3HGn0tkCKUuPmf+uy8I5uiHt5qWQbew=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.46.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA=="], + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.32", "", { "os": "win32", "cpu": "ia32" }, "sha512-wAi/FxGh7arDOUG45UmnXE1sZUa0hY4cXAO2qWAjFa3f7bTgz/BqwJ7XN5SUezvAJPNkME4fEpInfnBvM25a0w=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.46.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.32", "", { "os": "win32", "cpu": "x64" }, "sha512-Ej0i4PZk8ltblZtzVK8ouaGUacUtxRmTm5S9794mdyU/tYxXjAJNseOfxrnHpMWKjMDrOKbqkPqJ52T9NR4LQQ=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.46.0", "", { "os": "win32", "cpu": "x64" }, "sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.32", "", {}, "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g=="], "@speed-highlight/core": ["@speed-highlight/core@1.2.7", "", {}, "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g=="], @@ -570,14 +532,14 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], - "@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], + "@vortexjs/args": ["@vortexjs/args@workspace:packages/vortex-args"], + "@vortexjs/bun-example": ["@vortexjs/bun-example@workspace:packages/example"], "@vortexjs/cache": ["@vortexjs/cache@workspace:packages/vortex-cache"], @@ -610,8 +572,6 @@ "@vortexjs/wormhole": ["@vortexjs/wormhole@workspace:packages/wormhole"], - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], @@ -620,23 +580,21 @@ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + "ansis": ["ansis@4.1.0", "", {}, "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w=="], "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "ast-kit": ["ast-kit@2.1.2", "", { "dependencies": { "@babel/parser": "^7.28.0", "pathe": "^2.0.3" } }, "sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g=="], "better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="], - "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "birpc": ["birpc@2.5.0", "", {}, "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], - - "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + "bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], @@ -656,34 +614,32 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - - "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="], "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "dts-resolver": ["dts-resolver@2.1.1", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="], "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], - "esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="], - "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], "esrap": ["esrap@https://pkg.pr.new/sveltejs/esrap@76", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }], @@ -702,15 +658,9 @@ "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - "fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -718,6 +668,8 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + "human-id": ["human-id@4.1.1", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg=="], "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], @@ -738,14 +690,12 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - "jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="], - "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], - "js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], @@ -770,46 +720,28 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], - "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], - "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="], - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], - "mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], - "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="], - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="], @@ -826,16 +758,12 @@ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - "package-manager-detector": ["package-manager-detector@0.2.11", "", { "dependencies": { "quansync": "^0.2.7" } }, "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -846,16 +774,8 @@ "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - - "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - - "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - "prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -866,9 +786,13 @@ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rollup": ["rollup@4.46.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.46.0", "@rollup/rollup-android-arm64": "4.46.0", "@rollup/rollup-darwin-arm64": "4.46.0", "@rollup/rollup-darwin-x64": "4.46.0", "@rollup/rollup-freebsd-arm64": "4.46.0", "@rollup/rollup-freebsd-x64": "4.46.0", "@rollup/rollup-linux-arm-gnueabihf": "4.46.0", "@rollup/rollup-linux-arm-musleabihf": "4.46.0", "@rollup/rollup-linux-arm64-gnu": "4.46.0", "@rollup/rollup-linux-arm64-musl": "4.46.0", "@rollup/rollup-linux-loongarch64-gnu": "4.46.0", "@rollup/rollup-linux-ppc64-gnu": "4.46.0", "@rollup/rollup-linux-riscv64-gnu": "4.46.0", "@rollup/rollup-linux-riscv64-musl": "4.46.0", "@rollup/rollup-linux-s390x-gnu": "4.46.0", "@rollup/rollup-linux-x64-gnu": "4.46.0", "@rollup/rollup-linux-x64-musl": "4.46.0", "@rollup/rollup-win32-arm64-msvc": "4.46.0", "@rollup/rollup-win32-ia32-msvc": "4.46.0", "@rollup/rollup-win32-x64-msvc": "4.46.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw=="], + "rolldown": ["rolldown@1.0.0-beta.32", "", { "dependencies": { "@oxc-project/runtime": "=0.81.0", "@oxc-project/types": "=0.81.0", "@rolldown/pluginutils": "1.0.0-beta.32", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.32", "@rolldown/binding-darwin-arm64": "1.0.0-beta.32", "@rolldown/binding-darwin-x64": "1.0.0-beta.32", "@rolldown/binding-freebsd-x64": "1.0.0-beta.32", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.32", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.32", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.32", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.32", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.32", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.32", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.32", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.32", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.32", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.32" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-vxI2sPN07MMaoYKlFrVva5qZ1Y7DAZkgp7MQwTnyHt4FUMz9Sh+YeCzNFV9JYHI6ZNwoGWLCfCViE3XVsRC1cg=="], + + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.15.6", "", { "dependencies": { "@babel/generator": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/types": "^7.28.2", "ast-kit": "^2.1.1", "birpc": "^2.5.0", "debug": "^4.4.1", "dts-resolver": "^2.1.1", "get-tsconfig": "^4.10.1" }, "peerDependencies": { "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "~3.0.3" }, "optionalPeers": ["@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-AxQlyx3Nszob5QLmVUjz/VnC5BevtUo0h8tliuE0egddss7IbtCBU7GOe7biRU0fJNRQJmQjPKXFcc7K98j3+w=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -884,8 +808,6 @@ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "spawndamnit": ["spawndamnit@3.0.1", "", { "dependencies": { "cross-spawn": "^7.0.5", "signal-exit": "^4.0.1" } }, "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg=="], @@ -894,16 +816,10 @@ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], - "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], - "tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="], "tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="], @@ -912,11 +828,7 @@ "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="], - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], @@ -924,16 +836,12 @@ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], - "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + "tsdown": ["tsdown@0.14.1", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", "rolldown": "latest", "rolldown-plugin-dts": "^0.15.6", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "tree-kill": "^1.2.2", "unconfig": "^7.3.2" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-/nBuFDKZeYln9hAxwWG5Cm55/823sNIVI693iVi0xRFHzf9OVUq4b/lx9PH1TErFr/IQ0kd2hutFbJIPM0XQWA=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="], - "turbo": ["turbo@2.5.5", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.5", "turbo-darwin-arm64": "2.5.5", "turbo-linux-64": "2.5.5", "turbo-linux-arm64": "2.5.5", "turbo-windows-64": "2.5.5", "turbo-windows-arm64": "2.5.5" }, "bin": { "turbo": "bin/turbo" } }, "sha512-eZ7wI6KjtT1eBqCnh2JPXWNUAxtoxxfi6VdBdZFvil0ychCOTxbm7YLRBi1JSt7U3c+u3CLxpoPxLdvr/Npr3A=="], "turbo-darwin-64": ["turbo-darwin-64@2.5.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-RYnTz49u4F5tDD2SUwwtlynABNBAfbyT2uU/brJcyh5k6lDLyNfYKdKmqd3K2ls4AaiALWrFKVSBsiVwhdFNzQ=="], @@ -952,7 +860,7 @@ "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + "unconfig": ["unconfig@7.3.2", "", { "dependencies": { "@quansync/fs": "^0.1.1", "defu": "^6.1.4", "jiti": "^2.4.2", "quansync": "^0.2.8" } }, "sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg=="], "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], @@ -960,28 +868,16 @@ "valibot": ["valibot@1.1.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw=="], - "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], - - "whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="], - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - - "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], "@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], @@ -990,6 +886,10 @@ "@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + "@quansync/fs/quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.3", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@tybys/wasm-util": "^0.10.0" } }, "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" }, "bundled": true }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="], @@ -1004,11 +904,7 @@ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - - "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "rolldown/@oxc-project/types": ["@oxc-project/types@0.81.0", "", {}, "sha512-CnOqkybZK8z6Gx7Wb1qF7AEnSzbol1WwcIzxYOr8e91LytGOjo0wCpgoYWZo8sdbpqX+X+TJayIzo4Pv0R/KjA=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], } diff --git a/package.json b/package.json index 39dda32..de0d5de 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "bun-plugin-tailwind": "^0.0.15", "chalk": "^5.4.1", "@types/bun": "latest", - "tsup": "^8.5.0", + "tsdown": "^0.14.1", "typescript": "^5", "@napi-rs/cli": "^3.0.0-alpha.89", "@vortexjs/discovery-win32-x64-msvc": "0.0.0", diff --git a/packages/discovery/package.json b/packages/discovery/package.json index 454f602..08ae407 100644 --- a/packages/discovery/package.json +++ b/packages/discovery/package.json @@ -1,34 +1,34 @@ { - "name": "@vortexjs/discovery", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsup": "catalog:" - }, - "dependencies": { - "@vortexjs/cache": "workspace:*", - "@vortexjs/common": "workspace:*", - "@vortexjs/pippin": "workspace:*", - "esrap": "catalog:", - "oxc-parser": "catalog:" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist", - "test": "bun test" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - } - }, - "version": "0.2.0" + "name": "@vortexjs/discovery", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "dependencies": { + "@vortexjs/cache": "workspace:*", + "@vortexjs/common": "workspace:*", + "@vortexjs/pippin": "workspace:*", + "esrap": "catalog:", + "oxc-parser": "catalog:" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist", + "test": "bun test" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "version": "0.2.0" } diff --git a/packages/pippin-plugin-tailwind/package.json b/packages/pippin-plugin-tailwind/package.json index 46efd11..dcebbe1 100644 --- a/packages/pippin-plugin-tailwind/package.json +++ b/packages/pippin-plugin-tailwind/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:" + "tsdown": "catalog:" }, "dependencies": { "@tailwindcss/node": "catalog:", @@ -23,7 +23,7 @@ "typescript": "catalog:" }, "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist" + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist" }, "exports": { ".": { diff --git a/packages/pippin/package.json b/packages/pippin/package.json index 9bd12ac..3294b68 100644 --- a/packages/pippin/package.json +++ b/packages/pippin/package.json @@ -7,7 +7,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:" + "tsdown": "catalog:" }, "dependencies": { "@jridgewell/source-map": "catalog:", @@ -18,7 +18,7 @@ "typescript": "catalog:" }, "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist" + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist" }, "exports": { ".": { diff --git a/packages/vortex-cache/package.json b/packages/vortex-cache/package.json index 4bc7bd4..a9a6cde 100644 --- a/packages/vortex-cache/package.json +++ b/packages/vortex-cache/package.json @@ -7,7 +7,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:" + "tsdown": "catalog:" }, "dependencies": { "@vortexjs/common": "workspace:*" @@ -16,7 +16,7 @@ "typescript": "catalog:" }, "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist" + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist" }, "exports": { ".": { diff --git a/packages/vortex-cli/package.json b/packages/vortex-cli/package.json index 3e976bb..c4f7405 100644 --- a/packages/vortex-cli/package.json +++ b/packages/vortex-cli/package.json @@ -7,7 +7,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:" + "tsdown": "catalog:" }, "dependencies": { "@vortexjs/common": "workspace:*", @@ -19,7 +19,7 @@ "typescript": "catalog:" }, "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist" + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist" }, "exports": { ".": { diff --git a/packages/vortex-common/package.json b/packages/vortex-common/package.json index aa379a2..4e07337 100644 --- a/packages/vortex-common/package.json +++ b/packages/vortex-common/package.json @@ -1,27 +1,27 @@ { - "name": "@vortexjs/common", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsup": "catalog:" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist", - "test": "bun test" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - } - }, - "version": "0.1.0" + "name": "@vortexjs/common", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist", + "test": "bun test" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "version": "0.1.0" } diff --git a/packages/vortex-core/package.json b/packages/vortex-core/package.json index 85bb30f..91cb179 100644 --- a/packages/vortex-core/package.json +++ b/packages/vortex-core/package.json @@ -1,40 +1,40 @@ { - "name": "@vortexjs/core", - "module": "src/index.ts", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsup": "catalog:" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsup ./src/index.ts ./src/jsx/jsx-runtime.ts ./src/jsx/jsx-dev-runtime.ts --format esm --dts --out-dir dist" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, - "./jsx-runtime": { - "types": "./dist/jsx/jsx-runtime.d.ts", - "import": "./dist/jsx/jsx-runtime.js", - "require": "./dist/jsx/jsx-runtime.cjs" - }, - "./jsx-dev-runtime": { - "types": "./dist/jsx/jsx-dev-runtime.d.ts", - "import": "./dist/jsx/jsx-dev-runtime.js", - "require": "./dist/jsx/jsx-dev-runtime.cjs" - } - }, - "version": "2.5.0", - "dependencies": { - "@vortexjs/common": "workspace:*" - } + "name": "@vortexjs/core", + "module": "src/index.ts", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts ./src/jsx/jsx-runtime.ts ./src/jsx/jsx-dev-runtime.ts --format esm --dts --out-dir dist" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./jsx-runtime": { + "types": "./dist/jsx/jsx-runtime.d.ts", + "import": "./dist/jsx/jsx-runtime.js", + "require": "./dist/jsx/jsx-runtime.cjs" + }, + "./jsx-dev-runtime": { + "types": "./dist/jsx/jsx-dev-runtime.d.ts", + "import": "./dist/jsx/jsx-dev-runtime.js", + "require": "./dist/jsx/jsx-dev-runtime.cjs" + } + }, + "version": "2.5.0", + "dependencies": { + "@vortexjs/common": "workspace:*" + } } diff --git a/packages/vortex-dom/package.json b/packages/vortex-dom/package.json index 643d6ad..156a49f 100644 --- a/packages/vortex-dom/package.json +++ b/packages/vortex-dom/package.json @@ -1,40 +1,40 @@ { - "name": "@vortexjs/dom", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsup": "catalog:" - }, - "dependencies": { - "@vortexjs/core": "workspace:*", - "@vortexjs/common": "workspace:*" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsup ./src/index.ts ./src/jsx/jsx-runtime.ts ./src/jsx/jsx-dev-runtime.ts --format esm --dts --out-dir dist" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, - "./jsx-runtime": { - "types": "./dist/jsx/jsx-runtime.d.ts", - "import": "./dist/jsx/jsx-runtime.js", - "require": "./dist/jsx/jsx-runtime.cjs" - }, - "./jsx-dev-runtime": { - "types": "./dist/jsx/jsx-dev-runtime.d.ts", - "import": "./dist/jsx/jsx-dev-runtime.js", - "require": "./dist/jsx/jsx-dev-runtime.cjs" - } - }, - "version": "2.0.4" + "name": "@vortexjs/dom", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "dependencies": { + "@vortexjs/core": "workspace:*", + "@vortexjs/common": "workspace:*" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts ./src/jsx/jsx-runtime.ts ./src/jsx/jsx-dev-runtime.ts --format esm --dts --out-dir dist" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./jsx-runtime": { + "types": "./dist/jsx/jsx-runtime.d.ts", + "import": "./dist/jsx/jsx-runtime.js", + "require": "./dist/jsx/jsx-runtime.cjs" + }, + "./jsx-dev-runtime": { + "types": "./dist/jsx/jsx-dev-runtime.d.ts", + "import": "./dist/jsx/jsx-dev-runtime.js", + "require": "./dist/jsx/jsx-dev-runtime.cjs" + } + }, + "version": "2.0.4" } diff --git a/packages/vortex-intrinsics/package.json b/packages/vortex-intrinsics/package.json index 9846167..16a1775 100644 --- a/packages/vortex-intrinsics/package.json +++ b/packages/vortex-intrinsics/package.json @@ -7,7 +7,7 @@ }, "devDependencies": { "@types/bun": "catalog:", - "tsup": "catalog:" + "tsdown": "catalog:" }, "dependencies": { "@vortexjs/common": "workspace:*", @@ -17,7 +17,7 @@ "typescript": "catalog:" }, "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist" + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist" }, "exports": { ".": { diff --git a/packages/vortex-prime/package.json b/packages/vortex-prime/package.json index 5b29950..d098832 100644 --- a/packages/vortex-prime/package.json +++ b/packages/vortex-prime/package.json @@ -1,31 +1,31 @@ { - "name": "@vortexjs/prime", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsup": "catalog:" - }, - "dependencies": { - "@vortexjs/core": "workspace:*", - "@vortexjs/dom": "workspace:*", - "@vortexjs/common": "workspace:*" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - } - }, - "version": "1.3.4" + "name": "@vortexjs/prime", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "dependencies": { + "@vortexjs/core": "workspace:*", + "@vortexjs/dom": "workspace:*", + "@vortexjs/common": "workspace:*" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "version": "1.3.4" } diff --git a/packages/vortex-ssr/package.json b/packages/vortex-ssr/package.json index 9feee27..0b09388 100644 --- a/packages/vortex-ssr/package.json +++ b/packages/vortex-ssr/package.json @@ -1,32 +1,32 @@ { - "name": "@vortexjs/ssr", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsup": "catalog:" - }, - "dependencies": { - "@vortexjs/core": "workspace:*", - "@vortexjs/common": "workspace:*", - "@vortexjs/dom": "workspace:*" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsup ./src/index.ts --format esm --dts --out-dir dist", - "test": "bun test" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - } - }, - "version": "0.0.4" + "name": "@vortexjs/ssr", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "dependencies": { + "@vortexjs/core": "workspace:*", + "@vortexjs/common": "workspace:*", + "@vortexjs/dom": "workspace:*" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist", + "test": "bun test" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "version": "0.0.4" } diff --git a/packages/wormhole/package.json b/packages/wormhole/package.json index d42d84e..6ae84f3 100644 --- a/packages/wormhole/package.json +++ b/packages/wormhole/package.json @@ -1,49 +1,49 @@ { - "name": "@vortexjs/wormhole", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "bin": { - "wormhole": "./dist/cli.js", - "wh": "./dist/cli.js" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsup": "catalog:" - }, - "dependencies": { - "@speed-highlight/core": "catalog:", - "@vortexjs/common": "workspace:*", - "@vortexjs/core": "workspace:*", - "@vortexjs/discovery": "workspace:*", - "@vortexjs/dom": "workspace:*", - "@vortexjs/ssr": "workspace:*", - "@vortexjs/pippin-plugin-tailwind": "workspace:", - "@vortexjs/pippin": "workspace:*", - "@vortexjs/cli": "workspace:*", - "@vortexjs/intrinsics": "workspace:*", - "chalk": "catalog:" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsup ./src/index.ts ./src/cli.tsx ./src/virt/route.ts --format esm --dts --out-dir dist --external bun", - "test": "bun test" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, - "./route": { - "types": "./dist/virt/route.d.ts", - "import": "./dist/virt/route.js", - "require": "./dist/virt/route.cjs" - } - }, - "version": "0.2.0" + "name": "@vortexjs/wormhole", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "bin": { + "wormhole": "./dist/cli.js", + "wh": "./dist/cli.js" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "dependencies": { + "@speed-highlight/core": "catalog:", + "@vortexjs/common": "workspace:*", + "@vortexjs/core": "workspace:*", + "@vortexjs/discovery": "workspace:*", + "@vortexjs/dom": "workspace:*", + "@vortexjs/ssr": "workspace:*", + "@vortexjs/pippin-plugin-tailwind": "workspace:", + "@vortexjs/pippin": "workspace:*", + "@vortexjs/cli": "workspace:*", + "@vortexjs/intrinsics": "workspace:*", + "chalk": "catalog:" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts ./src/cli.tsx ./src/virt/route.ts --format esm --dts --out-dir dist --external bun", + "test": "bun test" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./route": { + "types": "./dist/virt/route.d.ts", + "import": "./dist/virt/route.js", + "require": "./dist/virt/route.cjs" + } + }, + "version": "0.2.0" } From 7c31bfd861c021dfc60ed3cd12de644bdfc6437a Mon Sep 17 00:00:00 2001 From: andylovescode Date: Sun, 17 Aug 2025 12:19:10 -0700 Subject: [PATCH 08/13] CLIv2: Add new args parser --- bun.lock | 31 ++-- packages/args/.gitignore | 34 +++++ packages/args/.npmignore | 33 +++++ packages/args/README.md | 3 + packages/args/package.json | 29 ++++ packages/args/src/flags.ts | 42 ++++++ packages/args/src/index.ts | 210 +++++++++++++++++++++++++++ packages/args/tsconfig.json | 27 ++++ packages/vortex-cli/CHANGELOG.md | 13 -- packages/wormhole/package.json | 95 ++++++------ packages/wormhole/src/cli.tsx | 54 +------ packages/wormhole/src/cli/command.ts | 208 -------------------------- packages/wormhole/src/cli/entry.ts | 65 +++++++++ packages/wormhole/src/cli/printer.ts | 3 +- 14 files changed, 511 insertions(+), 336 deletions(-) create mode 100644 packages/args/.gitignore create mode 100644 packages/args/.npmignore create mode 100644 packages/args/README.md create mode 100644 packages/args/package.json create mode 100644 packages/args/src/flags.ts create mode 100644 packages/args/src/index.ts create mode 100644 packages/args/tsconfig.json delete mode 100644 packages/vortex-cli/CHANGELOG.md delete mode 100644 packages/wormhole/src/cli/command.ts create mode 100644 packages/wormhole/src/cli/entry.ts diff --git a/bun.lock b/bun.lock index 1612bfa..4562d86 100644 --- a/bun.lock +++ b/bun.lock @@ -9,6 +9,20 @@ "turbo": "^2.5.5", }, }, + "packages/args": { + "name": "@vortexjs/args", + "version": "0.0.1", + "dependencies": { + "@vortexjs/common": "workspace:*", + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:", + }, + "peerDependencies": { + "typescript": "catalog:", + }, + }, "packages/cataloger": { "name": "@vortexjs/cataloger", "bin": { @@ -122,20 +136,6 @@ "typescript": "catalog:", }, }, - "packages/vortex-args": { - "name": "@vortexjs/args", - "version": "0.0.1", - "dependencies": { - "@vortexjs/common": "workspace:*", - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsdown": "catalog:", - }, - "peerDependencies": { - "typescript": "catalog:", - }, - }, "packages/vortex-cache": { "name": "@vortexjs/cache", "version": "0.0.1", @@ -263,6 +263,7 @@ }, "dependencies": { "@speed-highlight/core": "catalog:", + "@vortexjs/args": "workspace:*", "@vortexjs/cli": "workspace:*", "@vortexjs/common": "workspace:*", "@vortexjs/core": "workspace:*", @@ -538,7 +539,7 @@ "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], - "@vortexjs/args": ["@vortexjs/args@workspace:packages/vortex-args"], + "@vortexjs/args": ["@vortexjs/args@workspace:packages/args"], "@vortexjs/bun-example": ["@vortexjs/bun-example@workspace:packages/example"], diff --git a/packages/args/.gitignore b/packages/args/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/args/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/args/.npmignore b/packages/args/.npmignore new file mode 100644 index 0000000..dfe1eec --- /dev/null +++ b/packages/args/.npmignore @@ -0,0 +1,33 @@ +# dependencies (bun install) +node_modules + +# output +out +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/args/README.md b/packages/args/README.md new file mode 100644 index 0000000..4f2a7f2 --- /dev/null +++ b/packages/args/README.md @@ -0,0 +1,3 @@ +# Vortex Args + +A lightweight, beautiful args parser. diff --git a/packages/args/package.json b/packages/args/package.json new file mode 100644 index 0000000..a533159 --- /dev/null +++ b/packages/args/package.json @@ -0,0 +1,29 @@ +{ + "name": "@vortexjs/args", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "dependencies": { + "@vortexjs/common": "workspace:*" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts --format esm --dts --out-dir dist" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "version": "0.0.1" +} diff --git a/packages/args/src/flags.ts b/packages/args/src/flags.ts new file mode 100644 index 0000000..3f6cb74 --- /dev/null +++ b/packages/args/src/flags.ts @@ -0,0 +1,42 @@ +import { unwrap } from "@vortexjs/common"; + +export interface Flags { + positionals: string[]; + options: Record; +} + +export function parseFlags(args: string[]): Flags { + let p = 0; + + const flags: Flags = { + positionals: [], + options: {}, + }; + + while (p < args.length) { + const arg = unwrap(args[p]); + if (arg.startsWith("--")) { + if (arg.includes("=")) { + const [key, value] = arg.slice(2).split("="); + flags.options[unwrap(key)] = unwrap(value); + } else { + const key = arg.slice(2); + const value = args[p + 1]; + if (value && !value.startsWith("-")) { + flags.options[key] = value; + p++; + } else { + flags.options[key] = true; + } + } + } else if (arg.startsWith("-")) { + const key = arg.slice(1); + flags.options[key] = true; + } else { + flags.positionals.push(arg); + } + p++; + } + + return flags; +} diff --git a/packages/args/src/index.ts b/packages/args/src/index.ts new file mode 100644 index 0000000..b9c5012 --- /dev/null +++ b/packages/args/src/index.ts @@ -0,0 +1,210 @@ +import { parseFlags } from "./flags"; + +export async function parseArgs({ + commands, + args, + showHelp, +}: { + commands: Command[]; + args: string[]; + showHelp(): void; +}): Promise { + const flags = parseFlags(args); + const debugLogging = false; + + if ( + "help" in flags.options || + "h" in flags.options || + flags.positionals[0] === "help" + ) { + showHelp(); + return; + } + + outer: for (const command of commands) { + let ptr = 0; + + const args: Record = {}; + + debugLogging && + console.log(`Checking command: ${command.path.join(" ")}`); + + for (const combinator of command.combinators) { + if (typeof combinator === "string") { + if (ptr >= flags.positionals.length) { + debugLogging && + console.warn( + `command dq'ed due to lack of positionals`, + ); + continue outer; + } + if (flags.positionals[ptr] !== combinator) { + debugLogging && + console.warn( + `command dq'ed due to positional mismatch: expected "${combinator}", got "${flags.positionals[ptr]}"`, + ); + continue outer; + } + + ptr++; + + continue; + } + + if (combinator.type === "positional") { + if (ptr >= flags.positionals.length) { + debugLogging && + console.warn( + `command dq'ed due to lack of positionals for combinator ${combinator.id}`, + ); + continue outer; + } + args[combinator.id] = flags.positionals[ptr++]; + } else if (combinator.type === "stringOption") { + if ( + !(combinator.id in flags.options) && + !("optional" in combinator) + ) { + debugLogging && + console.warn( + `command dq'ed due to lack of option for combinator ${combinator.id}`, + ); + continue outer; + } + args[combinator.id] = flags.options[combinator.id] as + | string + | undefined; + } else if (combinator.type === "booleanOption") { + if ( + !(combinator.id in flags.options) && + !("optional" in combinator) + ) { + debugLogging && + console.warn( + `command dq'ed due to lack of option for combinator ${combinator.id}`, + ); + continue outer; + } + args[combinator.id] = flags.options[combinator.id] === true; + } + } + + if (ptr < flags.positionals.length) { + debugLogging && + console.warn( + `command dq'ed due to excess positionals: ${flags.positionals.slice(ptr).join(", ")}`, + ); + continue; + } + + await command.impl(args); + return; + } + + showHelp(); +} + +export interface Command { + impl(args: T): Promise | void; + combinators: Combinator[]; + path: string[]; +} + +export interface PositionalCombinator { + type: "positional"; + id: ID; +} + +export interface StringOptionCombinator { + type: "stringOption"; + id: ID; +} + +export interface BooleanOptionCombinator { + type: "booleanOption"; + id: ID; +} + +export type OptionalCombinator> = + BaseCombinator & { + optional: true; + }; + +type InternalCombinator = + | PositionalCombinator + | StringOptionCombinator + | BooleanOptionCombinator; + +export type Combinator = + | InternalCombinator + | OptionalCombinator> + | string; + +export type InferArgEach = Item extends OptionalCombinator + ? Partial> + : Item extends PositionalCombinator + ? { [K in ID]: string } + : Item extends StringOptionCombinator + ? { [K in ID]: string } + : Item extends BooleanOptionCombinator + ? { [K in ID]: boolean } + : never; + +export type InferArgs[]> = { + [K in keyof Combinators]: InferArgEach; +}[number] extends infer U + ? (U extends any ? (x: U) => void : never) extends (x: infer I) => void + ? I + : never + : never; + +export function command[]>( + impl: (args: InferArgs) => Promise | void, + ...combinators: Combinators +): Command> { + return { + impl, + combinators, + path: combinators.filter((c) => typeof c === "string"), + }; +} + +export function positional( + id: ID, +): PositionalCombinator { + return { + type: "positional", + id, + }; +} + +export function stringOption( + id: ID, +): StringOptionCombinator { + return { + type: "stringOption", + id, + }; +} + +export function booleanOption( + id: ID, +): BooleanOptionCombinator { + return { + type: "booleanOption", + id, + }; +} + +export function optional>( + combinator: BaseCombinator, +): OptionalCombinator { + if (typeof combinator !== "object") { + throw new Error("Cannot make a string combinator optional"); + } + + return { + ...combinator, + optional: true, + }; +} diff --git a/packages/args/tsconfig.json b/packages/args/tsconfig.json new file mode 100644 index 0000000..d715a95 --- /dev/null +++ b/packages/args/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": ["src/**/*", "test/**/*"] +} diff --git a/packages/vortex-cli/CHANGELOG.md b/packages/vortex-cli/CHANGELOG.md deleted file mode 100644 index d6fb7a8..0000000 --- a/packages/vortex-cli/CHANGELOG.md +++ /dev/null @@ -1,13 +0,0 @@ -# @vortexjs/cache - -## 0.0.1 - -### Patch Changes - -- Updated dependencies [f870d4f] -- Updated dependencies [0885d49] -- Updated dependencies [1f8e9da] -- Updated dependencies [936f5d6] -- Updated dependencies [50075fb] -- Updated dependencies [1f8e9da] - - @vortexjs/common@0.1.0 diff --git a/packages/wormhole/package.json b/packages/wormhole/package.json index 6ae84f3..560030c 100644 --- a/packages/wormhole/package.json +++ b/packages/wormhole/package.json @@ -1,49 +1,50 @@ { - "name": "@vortexjs/wormhole", - "type": "module", - "license": "MIT-0", - "repository": { - "url": "https://github.com/andylovescode/vortex" - }, - "bin": { - "wormhole": "./dist/cli.js", - "wh": "./dist/cli.js" - }, - "devDependencies": { - "@types/bun": "catalog:", - "tsdown": "catalog:" - }, - "dependencies": { - "@speed-highlight/core": "catalog:", - "@vortexjs/common": "workspace:*", - "@vortexjs/core": "workspace:*", - "@vortexjs/discovery": "workspace:*", - "@vortexjs/dom": "workspace:*", - "@vortexjs/ssr": "workspace:*", - "@vortexjs/pippin-plugin-tailwind": "workspace:", - "@vortexjs/pippin": "workspace:*", - "@vortexjs/cli": "workspace:*", - "@vortexjs/intrinsics": "workspace:*", - "chalk": "catalog:" - }, - "peerDependencies": { - "typescript": "catalog:" - }, - "scripts": { - "build": "tsdown ./src/index.ts ./src/cli.tsx ./src/virt/route.ts --format esm --dts --out-dir dist --external bun", - "test": "bun test" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, - "./route": { - "types": "./dist/virt/route.d.ts", - "import": "./dist/virt/route.js", - "require": "./dist/virt/route.cjs" - } - }, - "version": "0.2.0" + "name": "@vortexjs/wormhole", + "type": "module", + "license": "MIT-0", + "repository": { + "url": "https://github.com/andylovescode/vortex" + }, + "bin": { + "wormhole": "./dist/cli.js", + "wh": "./dist/cli.js" + }, + "devDependencies": { + "@types/bun": "catalog:", + "tsdown": "catalog:" + }, + "dependencies": { + "@speed-highlight/core": "catalog:", + "@vortexjs/common": "workspace:*", + "@vortexjs/core": "workspace:*", + "@vortexjs/discovery": "workspace:*", + "@vortexjs/dom": "workspace:*", + "@vortexjs/ssr": "workspace:*", + "@vortexjs/pippin-plugin-tailwind": "workspace:", + "@vortexjs/pippin": "workspace:*", + "@vortexjs/cli": "workspace:*", + "@vortexjs/intrinsics": "workspace:*", + "@vortexjs/args": "workspace:*", + "chalk": "catalog:" + }, + "peerDependencies": { + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown ./src/index.ts ./src/cli.tsx ./src/virt/route.ts --format esm --dts --out-dir dist --external bun", + "test": "bun test" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./route": { + "types": "./dist/virt/route.d.ts", + "import": "./dist/virt/route.js", + "require": "./dist/virt/route.cjs" + } + }, + "version": "0.2.0" } diff --git a/packages/wormhole/src/cli.tsx b/packages/wormhole/src/cli.tsx index 21b972d..446ba86 100644 --- a/packages/wormhole/src/cli.tsx +++ b/packages/wormhole/src/cli.tsx @@ -1,55 +1,5 @@ #!/usr/bin/env bun -import { cliApp, colors } from "@vortexjs/cli"; -import { Frame, Text } from "@vortexjs/intrinsics"; +import { cliMain } from "./cli/entry"; -cliApp( - - - - - • - - wormhole - - - - This is not a{" "} - - *test* - - - - - a smol frame - - , -); - -// const projectDir = process.cwd(); - -// const lt = new Lifetime(); - -// const state = new Project(projectDir, lt); - -// await state.init(); - -// DevServer(state); - -// StatusBoard(state); +await cliMain(process.argv.slice(2)); diff --git a/packages/wormhole/src/cli/command.ts b/packages/wormhole/src/cli/command.ts deleted file mode 100644 index 5eb8854..0000000 --- a/packages/wormhole/src/cli/command.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { unwrap } from "@vortexjs/common"; - -export type Argument = - | { - type: "positional"; - optional: boolean; - id: string; - } - | { - type: "string"; - optional: boolean; - aliases: string[]; - id: string; - } - | { - type: "flag"; - id: string; - aliases: string[]; - }; - -export interface ArgsParser { - strings: Record; - flags: string[]; - consumePositional(): string | undefined; -} - -export interface RunnableCommand { - runnable_impl: { - handle(parser: ArgsParser): Promise | void; - }; -} - -export type BlankObject = { - [key in never]: never; -}; - -export interface Command - extends RunnableCommand { - data: { - description?: string; - arguments: Argument[]; - callback: (args: Args) => Promise | void; - }; - optional: { - string( - id: ID, - ...aliases: string[] - ): Command; - positional( - id: string, - ): Command; - }; - flag( - id: ID, - ...aliases: string[] - ): Command; - string( - id: ID, - ...aliases: string[] - ): Command; - positional( - id: string, - ): Command; - impl(callback: (args: Args) => Promise | void): Command; -} - -export function cmd() { - const self: Command = { - runnable_impl: { - async handle(parser) { - const args: Record = {}; - - for (const arg of self.data.arguments) { - if (arg.type === "positional") { - const value = parser.consumePositional(); - - if (value === undefined && !arg.optional) { - throw new Error( - `Missing required positidonal argument: ${arg.id}`, - ); - } - - args[arg.id] = value; - } else if (arg.type === "string") { - let value: string | undefined; - - for (const alias of arg.aliases) { - if (alias in parser.strings) { - value = parser.strings[alias]; - break; - } - } - - if (value === undefined && !arg.optional) { - throw new Error( - `Missing required string argument: ${arg.id}`, - ); - } - - args[arg.id] = value; - } else if (arg.type === "flag") { - args[arg.id] = arg.aliases.some((alias) => - parser.flags.includes(alias), - ); - } - } - - await self.data.callback(args as any); - }, - }, - data: { - description: undefined, - arguments: [], - callback(_args: any): Promise | void { - throw new Error("Function not implemented."); - }, - }, - optional: { - string(id, ...aliases) { - self.data.arguments.push({ - type: "string", - optional: true, - id, - aliases: [id, ...aliases], - }); - - return self; - }, - positional(id) { - self.data.arguments.push({ - type: "positional", - optional: false, - id, - }); - - return self as any; - }, - }, - flag(id, ...aliases) { - self.data.arguments.push({ - type: "flag", - id, - aliases: [id, ...aliases], - }); - - return self as any; - }, - string(id, ...aliases) { - self.data.arguments.push({ - type: "string", - optional: false, - id, - aliases: [id, ...aliases], - }); - - return self; - }, - positional(id) { - self.data.arguments.push({ - type: "positional", - optional: false, - id, - }); - - return self as any; - }, - impl(callback) { - self.data.callback = callback; - return self; - }, - }; -} - -export function parseArgs(args: string[]) { - const flags: string[] = []; - const strings: Record = {}; - const positional: string[] = []; - - for (let i = 0; i < args.length; i++) { - const arg = unwrap(args[i]); - - if (arg.startsWith("--")) { - if (arg.includes("=")) { - const [key, value] = arg.slice(2).split("=", 2); - strings[unwrap(key)] = unwrap(value); - } else if ( - i < args.length - 1 && - !unwrap(args[i + 1]).startsWith("-") - ) { - strings[arg.slice(2)] = unwrap(args[++i]); - } else { - flags.push(arg.slice(2)); - } - } else if (arg.startsWith("-")) { - flags.push(arg.slice(1)); - } else { - positional.push(arg); - } - } - - return { - flags, - strings, - consumePositional() { - return positional.shift(); - }, - } as ArgsParser; -} diff --git a/packages/wormhole/src/cli/entry.ts b/packages/wormhole/src/cli/entry.ts new file mode 100644 index 0000000..ffa5265 --- /dev/null +++ b/packages/wormhole/src/cli/entry.ts @@ -0,0 +1,65 @@ +import chalk from "chalk"; +import { createPrinter } from "./printer"; +import { colors } from "@vortexjs/cli"; +import { version } from "../../package.json" assert { type: "json" }; +import { command, parseArgs } from "@vortexjs/args"; +import { Project } from "~/state"; +import { Lifetime } from "@vortexjs/core"; +import { DevServer } from "~/dev/dev-server"; +import { StatusBoard } from "./statusboard"; + +function showHelp() { + const printer = createPrinter(); + + { + using _head = printer.group("Meta"); + + printer.log(`This is the Wormhole CLI. Wormhole is a metaframework for Vortex, designed to make development easier.`); + printer.log(`Wormhole is beta software, so please keep that in the back of your mind.`); + + printer.gap(); + printer.log(`Version: ${version}`); + printer.log(`Bun version: ${Bun.version}`); + } + + { + using _head = printer.group("Usage"); + + const commands = [ + ["wh help", "Show this help command"], + ["wh dev", "Start the development server"], + ["wh build [platform]", "Build for a certain platform"] + ]; + + const firstColumnWidth = Math.max(...commands.map(c => c[0]!.length)) + 2; + + for (const [command, description] of commands) { + printer.log( + `${chalk.hex(Bun.color(colors.emerald[400], "hex")!)(command!.padEnd(firstColumnWidth))} ${description}`, + ); + } + } + printer.show(); + + process.exit(1); +} + +const commands = [ + command(async () => { + const lt = new Lifetime(); + const state = new Project(process.cwd(), lt); + + await state.init(); + + DevServer(state); + StatusBoard(state); + }, "dev") +] + +export async function cliMain(args: string[]) { + await parseArgs({ + commands, + args, + showHelp + }); +} diff --git a/packages/wormhole/src/cli/printer.ts b/packages/wormhole/src/cli/printer.ts index 78b463f..4d4decc 100644 --- a/packages/wormhole/src/cli/printer.ts +++ b/packages/wormhole/src/cli/printer.ts @@ -1,3 +1,4 @@ +import { colors } from "@vortexjs/cli"; import chalk from "chalk"; export function createPrinter() { @@ -39,7 +40,7 @@ export function createPrinter() { group(heading: string, styled = true) { this.gap(); this.log( - styled ? chalk.hex("#34d399").bold(`# ${heading}`) : heading, + styled ? chalk.hex(Bun.color(colors.blue[400], "hex")!).bold(`# ${heading}`) : heading, ); this.gap(); From 23ccd575d2761695025deaecbc791fd9450fd407 Mon Sep 17 00:00:00 2001 From: andylovescode Date: Sun, 17 Aug 2025 23:33:18 -0700 Subject: [PATCH 09/13] Wormhole: Make statusboard use CLIv2 --- packages/vortex-cli/src/corebind/index.ts | 139 ++--- packages/vortex-cli/src/index.ts | 19 +- packages/vortex-cli/src/render/ansi.ts | 91 ++-- packages/vortex-cli/src/render/index.ts | 442 +++++++-------- packages/vortex-cli/src/tree/index.ts | 626 +++++++++++----------- packages/vortex-common/src/dev.ts | 8 + packages/vortex-intrinsics/src/index.ts | 6 + packages/wormhole/src/cli/statusboard.ts | 85 --- packages/wormhole/src/cli/statusboard.tsx | 113 ++++ packages/wormhole/src/cli/theme.ts | 11 + 10 files changed, 828 insertions(+), 712 deletions(-) delete mode 100644 packages/wormhole/src/cli/statusboard.ts create mode 100644 packages/wormhole/src/cli/statusboard.tsx create mode 100644 packages/wormhole/src/cli/theme.ts diff --git a/packages/vortex-cli/src/corebind/index.ts b/packages/vortex-cli/src/corebind/index.ts index 88f9b64..089f13b 100644 --- a/packages/vortex-cli/src/corebind/index.ts +++ b/packages/vortex-cli/src/corebind/index.ts @@ -3,72 +3,75 @@ import { Box, type TreeNode, Text } from "../tree"; import { fontWeightToPrimitiveBoldness, Frame, Text as TextIntrinsic, type FontWeight } from "@vortexjs/intrinsics"; import { jsx } from "@vortexjs/core/jsx-runtime"; -export function cli(): Renderer { - return { - createNode(type: string, hydration?: undefined): TreeNode { - if (type === "box") { - return new Box(); - } - if (type === "text") { - return new Text([]); - } - throw new Error(`Unknown node type: ${type}`); - }, - setAttribute(node: TreeNode, name: string, value: any): void { - if (node instanceof Box) { - (node.attributes as any)[name] = value; - node.update(); - } else if (node instanceof Text) { - if (name === "weight") { - const weight = value as FontWeight; - const isBold = fontWeightToPrimitiveBoldness(weight); - node.style.bold = isBold === "bold"; - return; - } - (node.style as any)[name] = value; - } else { - throw new Error("setAttribute can only be called on Box and Text nodes."); - } - }, - createTextNode(hydration?: undefined): TreeNode { - return new Text([]); - }, - setTextContent(node: TreeNode, text: string): void { - if (node instanceof Text) { - node.children = [text]; - } else { - throw new Error("setTextContent can only be called on Text nodes."); - } - }, - setChildren(node: TreeNode, children: TreeNode[]): void { - if ('children' in node) { - node.children = children; - } - }, - getHydrationContext(node: TreeNode): undefined { - return undefined; - }, - addEventListener(node: TreeNode, name: string, event: (event: any) => void): Lifetime { - throw new Error("Function not implemented."); - }, - bindValue(node: TreeNode, name: string, value: Store): Lifetime { - if (node instanceof Box) { - return value.subscribe((newValue) => { - this.setAttribute(node, name, newValue); - }); - } - throw new Error("bindValue can only be called on Box nodes."); - }, - setStyle(node: TreeNode, name: string, value: string | undefined): void { - throw new Error("Function not implemented."); - }, - implementations: [ - implementIntrinsic(Frame, (props) => { - return jsx("box", props); - }), - implementIntrinsic(TextIntrinsic, (props) => { - return jsx("text", props); - }) - ] - } +export function cli({ onUpdate }: { onUpdate?(): void; } = {}): Renderer { + return { + createNode(type: string, hydration?: undefined): TreeNode { + if (type === "box") { + return new Box(); + } + if (type === "text") { + return new Text([]); + } + throw new Error(`Unknown node type: ${type}`); + }, + setAttribute(node: TreeNode, name: string, value: any): void { + onUpdate?.(); + if (node instanceof Box) { + (node.attributes as any)[name] = value; + node.update(); + } else if (node instanceof Text) { + if (name === "weight") { + const weight = value as FontWeight; + const isBold = fontWeightToPrimitiveBoldness(weight); + node.style.bold = isBold === "bold"; + return; + } + (node.style as any)[name] = value; + } else { + throw new Error("setAttribute can only be called on Box and Text nodes."); + } + }, + createTextNode(hydration?: undefined): TreeNode { + return new Text([]); + }, + setTextContent(node: TreeNode, text: string): void { + onUpdate?.(); + if (node instanceof Text) { + node.children = [text]; + } else { + throw new Error("setTextContent can only be called on Text nodes."); + } + }, + setChildren(node: TreeNode, children: TreeNode[]): void { + onUpdate?.(); + if ('children' in node) { + node.children = children; + } + }, + getHydrationContext(node: TreeNode): undefined { + return undefined; + }, + addEventListener(node: TreeNode, name: string, event: (event: any) => void): Lifetime { + throw new Error("Function not implemented."); + }, + bindValue(node: TreeNode, name: string, value: Store): Lifetime { + if (node instanceof Box) { + return value.subscribe((newValue) => { + this.setAttribute(node, name, newValue); + }); + } + throw new Error("bindValue can only be called on Box nodes."); + }, + setStyle(node: TreeNode, name: string, value: string | undefined): void { + throw new Error("Function not implemented."); + }, + implementations: [ + implementIntrinsic(Frame, (props) => { + return jsx("box", props); + }), + implementIntrinsic(TextIntrinsic, (props) => { + return jsx("text", props); + }) + ] + } } diff --git a/packages/vortex-cli/src/index.ts b/packages/vortex-cli/src/index.ts index 86a2830..21bcc57 100644 --- a/packages/vortex-cli/src/index.ts +++ b/packages/vortex-cli/src/index.ts @@ -1,4 +1,4 @@ -import { ContextScope, type JSXNode, render } from "@vortexjs/core"; +import { type JSXNode, render } from "@vortexjs/core"; import { Direction, Edge, type Node } from "yoga-layout"; import { cli } from "./corebind"; import { Canvas, Renderer } from "./render"; @@ -29,7 +29,6 @@ function printYogaNode(node: Node) { export function cliApp(root: JSXNode) { const renderer = new Renderer(); const internalRoot = new Box(); - const context = new ContextScope(); function paint() { const width = process.stdout.columns; @@ -50,20 +49,21 @@ export function cliApp(root: JSXNode) { let paintImmediate = 0; function queuePaint() { - if (paintImmediate) return; + if (paintImmediate !== 0) return; paintImmediate = setTimeout(() => { paint(); paintImmediate = 0; }, 10) as unknown as number; } - context.streaming.onUpdate(queuePaint); - render({ root: internalRoot, - renderer: cli(), + renderer: cli({ + onUpdate() { + queuePaint(); + }, + }), component: root, - context, }); process.stdout.on("resize", () => { @@ -71,6 +71,11 @@ export function cliApp(root: JSXNode) { }); queuePaint(); + + setInterval(() => { + //@ts-expect-error bun hack + process.stdout._refreshSize(); + }, 500); } export { symbols, colors }; diff --git a/packages/vortex-cli/src/render/ansi.ts b/packages/vortex-cli/src/render/ansi.ts index f498344..9cbec5b 100644 --- a/packages/vortex-cli/src/render/ansi.ts +++ b/packages/vortex-cli/src/render/ansi.ts @@ -1,47 +1,52 @@ import type { FileSink } from "bun"; export class ANSIWriter { - constructor(public terminal: FileSink) { } - - csi() { - this.terminal.write("\x1b["); - } - - write(text: string) { - this.terminal.write(text); - } - - moveTo(x: number, y: number) { - this.csi(); - this.terminal.write(`${y + 1};${x + 1}H`); - } - - setBackground(color: string) { - const { r, g, b } = Bun.color(color, "{rgb}")!; - - this.csi(); - this.terminal.write(`48;2;${r};${g};${b}m`); - } - - setForeground(color: string) { - const { r, g, b } = Bun.color(color, "{rgb}")!; - - this.csi(); - this.terminal.write(`38;2;${r};${g};${b}m`); - } - - setItalic(italic: boolean) { - this.csi(); - this.terminal.write(italic ? "3m" : "23m"); - } - - setBold(bold: boolean) { - this.csi(); - this.terminal.write(bold ? "1m" : "22m"); - } - - setUnderline(underline: boolean) { - this.csi(); - this.terminal.write(underline ? "4m" : "24m"); - } + constructor(public terminal: FileSink) { } + + csi() { + this.terminal.write("\x1b["); + } + + write(text: string) { + this.terminal.write(text); + } + + moveTo(x: number, y: number) { + this.csi(); + this.terminal.write(`${y + 1};${x + 1}H`); + } + + setCursorVisible(visible: boolean) { + this.csi(); + this.terminal.write(visible ? "?25h" : "?25l"); + }; + + setBackground(color: string) { + const { r, g, b } = Bun.color(color, "{rgb}")!; + + this.csi(); + this.terminal.write(`48;2;${r};${g};${b}m`); + } + + setForeground(color: string) { + const { r, g, b } = Bun.color(color, "{rgb}")!; + + this.csi(); + this.terminal.write(`38;2;${r};${g};${b}m`); + } + + setItalic(italic: boolean) { + this.csi(); + this.terminal.write(italic ? "3m" : "23m"); + } + + setBold(bold: boolean) { + this.csi(); + this.terminal.write(bold ? "1m" : "22m"); + } + + setUnderline(underline: boolean) { + this.csi(); + this.terminal.write(underline ? "4m" : "24m"); + } } diff --git a/packages/vortex-cli/src/render/index.ts b/packages/vortex-cli/src/render/index.ts index 3ea95a3..f1aa7da 100644 --- a/packages/vortex-cli/src/render/index.ts +++ b/packages/vortex-cli/src/render/index.ts @@ -2,225 +2,247 @@ import symbols from "../tokens/symbols"; import { ANSIWriter } from "./ansi"; export interface Cell { - text: string; - background: string; - foreground: string; - bold: boolean; - italic: boolean; - underline: boolean; + text: string; + background: string; + foreground: string; + bold: boolean; + italic: boolean; + underline: boolean; } export type BoxStyle = "outline-round" | "outline-square" | "background-square" | "none"; export class Canvas { - constructor(public width: number, public height: number) { - this.clipEndX = width; - this.clipEndY = height; - this.buffer = new Array(width * height).fill({ - text: " ", - background: "black", - foreground: "white", - bold: false, - italic: false, - underline: false - }).map(x => structuredClone(x)); - } - - put(x: number, y: number, cell: Cell) { - if (x < this.clipStartX || x >= this.clipEndX || y < - this.clipStartY || y >= this.clipEndY) { - return; - } - - const index = y * this.width + x; - this.buffer[index]!.text = cell.text; - if (cell.background !== "transparent") { - this.buffer[index]!.background = cell.background; - } - this.buffer[index]!.bold = cell.bold; - this.buffer[index]!.italic = cell.italic; - this.buffer[index]!.underline = cell.underline; - this.buffer[index]!.foreground = cell.foreground; - } - - box(type: BoxStyle, x1: number, y1: number, x2: number, y2: number, color: string) { - if (type === "none") { - return; - } - - for (let y = y1; y <= y2; y++) { - for (let x = x1; x <= x2; x++) { - if (type === "background-square") { - this.put(x, y, { - text: " ", - background: color, - foreground: "white", - bold: false, - italic: false, - underline: false - }); - continue; - } - - const vertical = x === x1 || x === x2; - const horizontal = y === y1 || y === y2; - - const up = (y > y1) && vertical; - const down = (y < y2) && vertical; - const left = (x > x1) && horizontal; - const right = (x < x2) && horizontal; - const symbol = ({ - "outline-round": symbols.outline.rounded, - "outline-square": symbols.outline.square, - })[type]({ - up, - down, - left, - right, - }); - - if (!(up || down || left || right)) { - if (type === "outline-round" || type === "outline-square") { - continue; // Skip the inner area for outlines - } - - this.put(x, y, { - text: " ", - background: color, - foreground: "transparent", - bold: false, - italic: false, - underline: false, - }); - } else { - this.put(x, y, { - text: symbol, - background: "transparent", - foreground: color, - bold: false, - italic: false, - underline: false - }); - } - } - } - } - - clip(x1: number, y1: number, x2: number, y2: number) { - const prevClipStartX = this.clipStartX; - const prevClipStartY = this.clipStartY; - const prevClipEndX = this.clipEndX; - const prevClipEndY = this.clipEndY; - - this.clipStartX = Math.max(0, x1, this.clipStartX); - this.clipStartY = Math.max(0, y1, this.clipStartY); - this.clipEndX = Math.min(this.width, x2, this.clipEndX); - this.clipEndY = Math.min(this.height, y2, this.clipEndY); - - const self = this; - - return { - [Symbol.dispose]() { - self.clipStartX = prevClipStartX; - self.clipStartY = prevClipStartY; - self.clipEndX = prevClipEndX; - self.clipEndY = prevClipEndY; - } - } - } - - clipStartX = 0; - clipStartY = 0; - clipEndX = 0; - clipEndY = 0; - buffer: Cell[]; + offsetX = 0; + offsetY = 0; + + constructor(public width: number, public height: number) { + this.clipEndX = width; + this.clipEndY = height; + this.buffer = new Array(width * height).fill({ + text: " ", + background: "black", + foreground: "white", + bold: false, + italic: false, + underline: false + }).map(x => structuredClone(x)); + } + + put(x: number, y: number, cell: Cell) { + const rx = x + this.offsetX; + const ry = y + this.offsetY; + + if (rx < this.clipStartX || rx >= this.clipEndX || ry < + this.clipStartY || ry >= this.clipEndY) { + return; + } + + const index = ry * this.width + rx; + this.buffer[index]!.text = cell.text; + if (cell.background !== "transparent") { + this.buffer[index]!.background = cell.background; + } + this.buffer[index]!.bold = cell.bold; + this.buffer[index]!.italic = cell.italic; + this.buffer[index]!.underline = cell.underline; + this.buffer[index]!.foreground = cell.foreground; + } + + box(type: BoxStyle, x1: number, y1: number, x2: number, y2: number, color: string) { + if (type === "none") { + return; + } + + for (let y = y1; y <= y2; y++) { + for (let x = x1; x <= x2; x++) { + if (type === "background-square") { + this.put(x, y, { + text: " ", + background: color, + foreground: "white", + bold: false, + italic: false, + underline: false + }); + continue; + } + + const vertical = x === x1 || x === x2; + const horizontal = y === y1 || y === y2; + + const up = (y > y1) && vertical; + const down = (y < y2) && vertical; + const left = (x > x1) && horizontal; + const right = (x < x2) && horizontal; + const symbol = ({ + "outline-round": symbols.outline.rounded, + "outline-square": symbols.outline.square, + })[type]({ + up, + down, + left, + right, + }); + + if (!(up || down || left || right)) { + if (type === "outline-round" || type === "outline-square") { + continue; // Skip the inner area for outlines + } + + this.put(x, y, { + text: " ", + background: color, + foreground: "transparent", + bold: false, + italic: false, + underline: false, + }); + } else { + this.put(x, y, { + text: symbol, + background: "transparent", + foreground: color, + bold: false, + italic: false, + underline: false + }); + } + } + } + } + + clip(x1: number, y1: number, x2: number, y2: number) { + const prevClipStartX = this.clipStartX; + const prevClipStartY = this.clipStartY; + const prevClipEndX = this.clipEndX; + const prevClipEndY = this.clipEndY; + + this.clipStartX = Math.max(0, x1 + this.offsetX, this.clipStartX); + this.clipStartY = Math.max(0, y1 + this.offsetY, this.clipStartY); + this.clipEndX = Math.min(this.width, x2 + this.offsetX, this.clipEndX); + this.clipEndY = Math.min(this.height, y2 + this.offsetY, this.clipEndY); + + const self = this; + + return { + [Symbol.dispose]() { + self.clipStartX = prevClipStartX; + self.clipStartY = prevClipStartY; + self.clipEndX = prevClipEndX; + self.clipEndY = prevClipEndY; + } + } + } + + offset(x: number, y: number) { + this.offsetX += x; + this.offsetY += y; + + const self = this; + + return { + [Symbol.dispose]() { + self.offsetX -= x; + self.offsetY -= y; + } + } + } + + clipStartX = 0; + clipStartY = 0; + clipEndX = 0; + clipEndY = 0; + buffer: Cell[]; } function getCellKey(cell: Cell) { - return `${cell.text}-${cell.background}-${cell.foreground}`; + return `${cell.text}-${cell.background}-${cell.foreground}`; } export class Renderer { - currentCells: string[] = []; - currentWidth = 0; - currentHeight = 0; - ansi = new ANSIWriter(Bun.stdout.writer()); - currentBGColor = ""; - currentFGColor = ""; - currentX = 0; - currentY = 0; - currentBold: boolean | undefined = undefined; - currentUnderline: boolean | undefined = undefined; - currentItalic: boolean | undefined = undefined; - - setBGColor(color: string) { - if (this.currentBGColor === color) return; - this.currentBGColor = color; - this.ansi.setBackground(color); - } - - setFGColor(color: string) { - if (this.currentFGColor === color) return; - this.currentFGColor = color; - this.ansi.setForeground(color); - } - - setPosition(x: number, y: number) { - if (this.currentX === x && this.currentY === y) return; - this.currentX = x; - this.currentY = y; - this.ansi.moveTo(x, y); - } - - setBold(bold: boolean) { - if (this.currentBold === bold) return; - this.currentBold = bold; - this.ansi.setBold(bold); - } - - setItalic(italic: boolean) { - if (this.currentItalic === italic) return; - this.currentItalic = italic; - this.ansi.setItalic(italic); - } - - setUnderline(underline: boolean) { - if (this.currentUnderline === underline) return; - this.currentUnderline = underline; - this.ansi.setUnderline(underline); - } - - render(canvas: Canvas) { - if (this.currentWidth !== canvas.width || this.currentHeight !== canvas.height) { - this.currentCells = []; - } - - this.currentX = -1; - this.currentY = -1; - this.currentBold = undefined; - this.currentUnderline = undefined; - this.currentItalic = undefined; - - this.currentWidth = canvas.width; - this.currentHeight = canvas.height; - - for (let y = 0; y < canvas.height; y++) { - for (let x = 0; x < canvas.width; x++) { - const cell = canvas.buffer[y * canvas.width + x]!; - const key = getCellKey(cell); - - if (this.currentCells[y * canvas.width + x] !== key) { - this.setPosition(x, y); - this.setBGColor(cell.background); - this.setFGColor(cell.foreground); - this.setItalic(cell.italic); - this.setBold(cell.bold); - this.setUnderline(cell.underline); - this.ansi.write(cell.text); - this.currentX++; - this.currentCells[y * canvas.width + x] = key; - } - } - } - } + currentCells: string[] = []; + currentWidth = 0; + currentHeight = 0; + ansi = new ANSIWriter(Bun.stdout.writer()); + currentBGColor = ""; + currentFGColor = ""; + currentX = 0; + currentY = 0; + currentBold: boolean | undefined = undefined; + currentUnderline: boolean | undefined = undefined; + currentItalic: boolean | undefined = undefined; + + setBGColor(color: string) { + if (this.currentBGColor === color) return; + this.currentBGColor = color; + this.ansi.setBackground(color); + } + + setFGColor(color: string) { + if (this.currentFGColor === color) return; + this.currentFGColor = color; + this.ansi.setForeground(color); + } + + setPosition(x: number, y: number) { + if (this.currentX === x && this.currentY === y) return; + this.currentX = x; + this.currentY = y; + this.ansi.moveTo(x, y); + } + + setBold(bold: boolean) { + if (this.currentBold === bold) return; + this.currentBold = bold; + this.ansi.setBold(bold); + } + + setItalic(italic: boolean) { + if (this.currentItalic === italic) return; + this.currentItalic = italic; + this.ansi.setItalic(italic); + } + + setUnderline(underline: boolean) { + if (this.currentUnderline === underline) return; + this.currentUnderline = underline; + this.ansi.setUnderline(underline); + } + + render(canvas: Canvas) { + if (this.currentWidth !== canvas.width || this.currentHeight !== canvas.height) { + this.currentCells = []; + } + + this.currentX = -1; + this.currentY = -1; + this.currentBold = undefined; + this.currentUnderline = undefined; + this.currentItalic = undefined; + + this.currentWidth = canvas.width; + this.currentHeight = canvas.height; + + for (let y = 0; y < canvas.height; y++) { + for (let x = 0; x < canvas.width; x++) { + const cell = canvas.buffer[y * canvas.width + x]!; + const key = getCellKey(cell); + + if (this.currentCells[y * canvas.width + x] !== key) { + this.setPosition(x, y); + this.setBGColor(cell.background); + this.setFGColor(cell.foreground); + this.setItalic(cell.italic); + this.setBold(cell.bold); + this.setUnderline(cell.underline); + this.ansi.write(cell.text); + this.currentX++; + this.currentCells[y * canvas.width + x] = key; + } + } + } + + this.ansi.setCursorVisible(false); + } } diff --git a/packages/vortex-cli/src/tree/index.ts b/packages/vortex-cli/src/tree/index.ts index dba4fa0..6377849 100644 --- a/packages/vortex-cli/src/tree/index.ts +++ b/packages/vortex-cli/src/tree/index.ts @@ -1,347 +1,375 @@ -import Yoga, { Direction, Display, Edge, FlexDirection, Gutter, Justify, MeasureMode, type Node } from "yoga-layout"; +import Yoga, { Direction, Display, Edge, FlexDirection, Gutter, Justify, MeasureMode, PositionType, type Node } from "yoga-layout"; import type { BoxStyle, Canvas } from "../render"; import { resolveUDLRDescription, type Frame } from "@vortexjs/intrinsics"; import type { IntrinsicComponent } from "@vortexjs/core"; export interface TreeNode { - yoga: Node; - render(canvas: Canvas): void; + yoga: Node; + render(canvas: Canvas): void; } function getYogaConfig() { - const config = Yoga.Config.create(); + const config = Yoga.Config.create(); - config.setPointScaleFactor(1); - config.setUseWebDefaults(false); + config.setPointScaleFactor(1); + config.setUseWebDefaults(false); - return config; + return config; } const config = getYogaConfig(); export type FrameProps = Omit - ? Props - : never, "children">; + ? Props + : never, "children">; function pixelToTileX(px: number | T): number | T { - if (Number.isNaN(Number(px))) { - return px; - } + if (Number.isNaN(Number(px))) { + return px; + } - return Math.round(Number(px) / (10 / 4)); + return Math.round(Number(px) / (10 / 4)); } function pixelToTileY(px: number | T): number | T { - if (Number.isNaN(Number(px))) { - return px; - } + if (Number.isNaN(Number(px))) { + return px; + } - return Math.round(Number(px) / (25 / 4)); + return Math.round(Number(px) / (25 / 4)); } export class Box implements TreeNode { - yoga: Node; - attributes: FrameProps = {}; - private _children: TreeNode[] = []; - - get children(): TreeNode[] { - return this._children; - } - - set children(children: TreeNode[]) { - this._children = children; - - while (this.yoga.getChildCount() > 0) { - this.yoga.removeChild(this.yoga.getChild(0)); - } - - for (const child of children) { - this.yoga.insertChild(child.yoga, this.yoga.getChildCount()); - } - } - - constructor() { - this.yoga = Yoga.Node.create(config); - } - - render(canvas: Canvas): void { - const layout = this.yoga.getComputedLayout(); - - const left = layout.left; - const top = layout.top; - const right = left + layout.width; - const bottom = top + layout.height; - - if (this.attributes.background) { - const backgroundColor = typeof this.attributes.background === "string" - ? this.attributes.background - : this.attributes.background.color ?? "black"; - - if (this.attributes.background === "transparent") { - canvas.box("none", left, top, right, bottom, backgroundColor); - } else { - canvas.box("background-square", left, top, right - 1, bottom - 1, backgroundColor); - } - } - - if (this.attributes.border) { - const borderWidth = typeof this.attributes.border == "string" ? 1 : this.attributes.border.width ?? 1; - const borderColor = typeof this.attributes.border === "string" - ? this.attributes.border - : this.attributes.border.color ?? "white"; - const radius = typeof this.attributes.border === "string" - ? 0 - : this.attributes.border.radius ?? 0; - - if (borderWidth > 0) { - canvas.box(radius > 0 ? "outline-round" : "outline-square", left, top, right - 1, bottom - 1, borderColor); - } - } - - const border = this.attributes.border ? 1 : 0; - - using _clip = canvas.clip(left + border, top + border, right - border, bottom - border); - - for (const child of this.children) { - child.render(canvas); - } - } - - update() { - this.yoga.setFlexGrow(this.attributes.grow); - - this.yoga.setGap(Gutter.Row, pixelToTileX(this.attributes.gap ?? 0) as number); - this.yoga.setGap(Gutter.Column, pixelToTileY(this.attributes.gap ?? 0) as number); - - const padding = resolveUDLRDescription(this.attributes.padding ?? 0); - - this.yoga.setPadding(Edge.Top, pixelToTileY(padding.top) as number); - this.yoga.setPadding(Edge.Right, pixelToTileX(padding.right) as number); - this.yoga.setPadding(Edge.Bottom, pixelToTileY(padding.bottom) as number); - this.yoga.setPadding(Edge.Left, pixelToTileX(padding.left) as number); - - const margin = resolveUDLRDescription(this.attributes.margin ?? 0); - - this.yoga.setMargin(Edge.Top, pixelToTileY(margin.top) as number); - this.yoga.setMargin(Edge.Right, pixelToTileX(margin.right) as number); - this.yoga.setMargin(Edge.Bottom, pixelToTileY(margin.bottom) as number); - this.yoga.setMargin(Edge.Left, pixelToTileX(margin.left) as number); - - switch (this.attributes.justifyContent) { - case "flex-start": - this.yoga.setJustifyContent(Justify.FlexStart); - break; - case "flex-end": - this.yoga.setJustifyContent(Justify.FlexEnd); - break; - case "center": - this.yoga.setJustifyContent(Justify.Center); - break; - case "space-between": - this.yoga.setJustifyContent(Justify.SpaceBetween); - break; - case "space-around": - this.yoga.setJustifyContent(Justify.SpaceAround); - break; - case "space-evenly": - this.yoga.setJustifyContent(Justify.SpaceEvenly); - break; - } - - switch (this.attributes.direction) { - case "row": - this.yoga.setFlexDirection(FlexDirection.Row); - break; - case "column": - this.yoga.setFlexDirection(FlexDirection.Column); - break; - case "row-reverse": - this.yoga.setFlexDirection(FlexDirection.RowReverse); - break; - case "column-reverse": - this.yoga.setFlexDirection(FlexDirection.ColumnReverse); - break; - } - - if (this.attributes.border) { - this.yoga.setBorder(Edge.All, 1) - } - this.yoga.setWidth( - typeof this.attributes.width === "number" - ? pixelToTileX(this.attributes.width) - : (this.attributes.width as any) - ); - this.yoga.setHeight( - typeof this.attributes.height === "number" - ? pixelToTileY(this.attributes.height) - : (this.attributes.height as any) - ); - this.yoga.setMinWidth( - typeof this.attributes.minWidth === "number" - ? pixelToTileX(this.attributes.minWidth) - : (this.attributes.minWidth as any) - ); - this.yoga.setMinHeight( - typeof this.attributes.minHeight === "number" - ? pixelToTileY(this.attributes.minHeight) - : (this.attributes.minHeight as any) - ); - this.yoga.setMaxWidth( - typeof this.attributes.maxWidth === "number" - ? pixelToTileX(this.attributes.maxWidth) - : (this.attributes.maxWidth as any) - ); - this.yoga.setMaxHeight( - typeof this.attributes.maxHeight === "number" - ? pixelToTileY(this.attributes.maxHeight) - : (this.attributes.maxHeight as any) - ); - this.yoga.setAlwaysFormsContainingBlock(true); - } + yoga: Node; + attributes: FrameProps = {}; + private _children: TreeNode[] = []; + + get children(): TreeNode[] { + return this._children; + } + + set children(children: TreeNode[]) { + this._children = children; + + while (this.yoga.getChildCount() > 0) { + this.yoga.removeChild(this.yoga.getChild(0)); + } + + for (const child of children) { + this.yoga.insertChild(child.yoga, this.yoga.getChildCount()); + } + } + + constructor() { + this.yoga = Yoga.Node.create(config); + } + + render(canvas: Canvas): void { + const layout = this.yoga.getComputedLayout(); + + const left = layout.left; + const top = layout.top; + const right = left + layout.width; + const bottom = top + layout.height; + + if (this.attributes.background) { + const backgroundColor = typeof this.attributes.background === "string" + ? this.attributes.background + : this.attributes.background.color ?? "black"; + + if (this.attributes.background === "transparent") { + canvas.box("none", left, top, right, bottom, backgroundColor); + } else { + canvas.box("background-square", left, top, right - 1, bottom - 1, backgroundColor); + } + } + + if (this.attributes.border) { + const borderWidth = typeof this.attributes.border == "string" ? 1 : this.attributes.border.width ?? 1; + const borderColor = typeof this.attributes.border === "string" + ? this.attributes.border + : this.attributes.border.color ?? "white"; + const radius = typeof this.attributes.border === "string" + ? 0 + : this.attributes.border.radius ?? 0; + + if (borderWidth > 0) { + canvas.box(radius > 0 ? "outline-round" : "outline-square", left, top, right - 1, bottom - 1, borderColor); + } + } + + const border = this.attributes.border ? 1 : 0; + + using _clip = this.attributes.clip ? canvas.clip(left + border, top + border, right - border, bottom - border) : undefined; + using _off = canvas.offset(left, top); + + for (const child of this.children) { + child.render(canvas); + } + } + + update() { + this.yoga.setFlexGrow(this.attributes.grow); + + this.yoga.setGap(Gutter.Row, pixelToTileX(this.attributes.gap ?? 0) as number); + this.yoga.setGap(Gutter.Column, pixelToTileY(this.attributes.gap ?? 0) as number); + + switch (this.attributes.position ?? "relative") { + case "absolute": + this.yoga.setPositionType(PositionType.Absolute); + break; + case "relative": + this.yoga.setPositionType(PositionType.Relative); + break; + case "static": + this.yoga.setPositionType(PositionType.Static); + break; + } + + if (this.attributes.left !== undefined) { + this.yoga.setPosition(Edge.Left, pixelToTileX(this.attributes.left) as number); + } + if (this.attributes.top !== undefined) { + this.yoga.setPosition(Edge.Top, pixelToTileY(this.attributes.top) as + number); + } + if (this.attributes.right !== undefined) { + this.yoga.setPosition(Edge.Right, pixelToTileX(this.attributes.right) as number); + } + if (this.attributes.bottom !== undefined) { + this.yoga.setPosition(Edge.Bottom, pixelToTileY(this.attributes.bottom) as + number); + } + + const padding = resolveUDLRDescription(this.attributes.padding ?? 0); + + this.yoga.setPadding(Edge.Top, pixelToTileY(padding.top) as number); + this.yoga.setPadding(Edge.Right, pixelToTileX(padding.right) as number); + this.yoga.setPadding(Edge.Bottom, pixelToTileY(padding.bottom) as number); + this.yoga.setPadding(Edge.Left, pixelToTileX(padding.left) as number); + + const margin = resolveUDLRDescription(this.attributes.margin ?? 0); + + this.yoga.setMargin(Edge.Top, pixelToTileY(margin.top) as number); + this.yoga.setMargin(Edge.Right, pixelToTileX(margin.right) as number); + this.yoga.setMargin(Edge.Bottom, pixelToTileY(margin.bottom) as number); + this.yoga.setMargin(Edge.Left, pixelToTileX(margin.left) as number); + + switch (this.attributes.justifyContent) { + case "flex-start": + this.yoga.setJustifyContent(Justify.FlexStart); + break; + case "flex-end": + this.yoga.setJustifyContent(Justify.FlexEnd); + break; + case "center": + this.yoga.setJustifyContent(Justify.Center); + break; + case "space-between": + this.yoga.setJustifyContent(Justify.SpaceBetween); + break; + case "space-around": + this.yoga.setJustifyContent(Justify.SpaceAround); + break; + case "space-evenly": + this.yoga.setJustifyContent(Justify.SpaceEvenly); + break; + } + + switch (this.attributes.direction) { + case "row": + this.yoga.setFlexDirection(FlexDirection.Row); + break; + case "column": + this.yoga.setFlexDirection(FlexDirection.Column); + break; + case "row-reverse": + this.yoga.setFlexDirection(FlexDirection.RowReverse); + break; + case "column-reverse": + this.yoga.setFlexDirection(FlexDirection.ColumnReverse); + break; + } + + if (this.attributes.border) { + this.yoga.setBorder(Edge.All, 1) + } + this.yoga.setWidth( + typeof this.attributes.width === "number" + ? pixelToTileX(this.attributes.width) + : (this.attributes.width as any) + ); + this.yoga.setHeight( + typeof this.attributes.height === "number" + ? pixelToTileY(this.attributes.height) + : (this.attributes.height as any) + ); + this.yoga.setMinWidth( + typeof this.attributes.minWidth === "number" + ? pixelToTileX(this.attributes.minWidth) + : (this.attributes.minWidth as any) + ); + this.yoga.setMinHeight( + typeof this.attributes.minHeight === "number" + ? pixelToTileY(this.attributes.minHeight) + : (this.attributes.minHeight as any) + ); + this.yoga.setMaxWidth( + typeof this.attributes.maxWidth === "number" + ? pixelToTileX(this.attributes.maxWidth) + : (this.attributes.maxWidth as any) + ); + this.yoga.setMaxHeight( + typeof this.attributes.maxHeight === "number" + ? pixelToTileY(this.attributes.maxHeight) + : (this.attributes.maxHeight as any) + ); + this.yoga.setAlwaysFormsContainingBlock(true); + } } function getTextSegments(text: string): string[] { - return text.split(/(\s+)/).filter(segment => segment.length > 0); + return text.split(/(\s+)/).filter(segment => segment.length > 0); } export function layoutText(text: TextSlice[], maxWidth: number): TextSlice[][] { - let lines: TextSlice[][] = []; + let lines: TextSlice[][] = []; - let currentLine: TextSlice[] = []; - let width = 0; + let currentLine: TextSlice[] = []; + let width = 0; - for (const segment of text) { - if (width + segment.text.length > maxWidth || segment.text === "\n") { - lines.push(currentLine); - currentLine = []; - width = 0; - } + for (const segment of text) { + if (width + segment.text.length > maxWidth || segment.text === "\n") { + lines.push(currentLine); + currentLine = []; + width = 0; + } - currentLine.push(segment); - width += segment.text.length; - } + currentLine.push(segment); + width += segment.text.length; + } - if (currentLine.length > 0) { - lines.push(currentLine); - } + if (currentLine.length > 0) { + lines.push(currentLine); + } - return lines; + return lines; } type TextChild = Text | string; type TextSlice = { - text: string; + text: string; } & TextStyle; type TextStyle = { - color: string; - underline: boolean; - italic: boolean; - bold: boolean; - background: string; + color: string; + underline: boolean; + italic: boolean; + bold: boolean; + background: string; } export class Text implements TreeNode { - yoga: Node; - children: TextChild[]; - style: Partial = {}; - realize(style: TextStyle = { - color: "white", - underline: false, - italic: false, - bold: false, - background: "transparent" - }): TextSlice[] { - const color = this.style.color ?? style.color; - const italic = this.style.italic ?? style.italic; - const bold = this.style.bold ?? style.bold; - const underline = this.style.underline ?? style.underline; - const background = this.style.background ?? style.background; - - let slices: TextSlice[] = []; - - for (const child of this.children) { - if (typeof child === "string") { - const segments = getTextSegments(child); - for (const segment of segments) { - slices.push({ - text: segment, - color, - italic, - bold, - underline, - background - }) - } - continue; - } - - const realized = child.realize({ - color, - italic, - bold, - underline, - background - }); - - slices.push(...realized); - } - - return slices; - } - - constructor(text: TextChild[]) { - this.children = text; - this.yoga = Yoga.Node.create(config); - this.yoga.setMeasureFunc((width, widthMode, height, heightMode) => { - const maxW = widthMode === MeasureMode.Undefined ? Number.MAX_VALUE : width; - const realized = this.realize(); - const lines = layoutText(realized, maxW); - - let measuredWidth = Math.min(...lines.map(x => x.length)); - if (widthMode === MeasureMode.AtMost) { - measuredWidth = Math.min(...realized.map(x => x.text.length)); - } else if (widthMode === MeasureMode.Exactly) { - measuredWidth = width; - } - - let measuredHeight = lines.length; - if (heightMode === MeasureMode.AtMost) { - measuredHeight = Math.min(measuredHeight, height); - } else if (heightMode === MeasureMode.Exactly) { - measuredHeight = height; - } - - return { width: measuredWidth, height: measuredHeight }; - }); - } - - render(canvas: Canvas): void { - const { left, top, width } = this.yoga.getComputedLayout(); - - const lines = layoutText(this.realize(), width); - - let y = top; - - for (const line of lines) { - let x = left; - - for (const segment of line) { - for (const character of segment.text) { - canvas.put(x, y, { - background: segment.background, - foreground: segment.color, - text: character, - bold: segment.bold, - underline: segment.underline, - italic: segment.italic - }) - x++; - } - } - - y++; - } - } + yoga: Node; + children: TextChild[]; + style: Partial = {}; + realize(style: TextStyle = { + color: "white", + underline: false, + italic: false, + bold: false, + background: "transparent" + }): TextSlice[] { + const color = this.style.color ?? style.color; + const italic = this.style.italic ?? style.italic; + const bold = this.style.bold ?? style.bold; + const underline = this.style.underline ?? style.underline; + const background = this.style.background ?? style.background; + + let slices: TextSlice[] = []; + + for (const child of this.children) { + if (typeof child === "string") { + const segments = getTextSegments(child); + for (const segment of segments) { + slices.push({ + text: segment, + color, + italic, + bold, + underline, + background + }) + } + continue; + } + + const realized = child.realize({ + color, + italic, + bold, + underline, + background + }); + + slices.push(...realized); + } + + return slices; + } + + constructor(text: TextChild[]) { + this.children = text; + this.yoga = Yoga.Node.create(config); + this.yoga.setMeasureFunc((width, widthMode, height, heightMode) => { + const maxW = widthMode === MeasureMode.Undefined ? Number.MAX_VALUE : width; + const realized = this.realize(); + const lines = layoutText(realized, maxW); + + let measuredWidth = Math.min(...lines.map(x => x.length)); + if (widthMode === MeasureMode.AtMost) { + measuredWidth = Math.min(...realized.map(x => x.text.length)); + } else if (widthMode === MeasureMode.Exactly) { + measuredWidth = width; + } + + let measuredHeight = lines.length; + if (heightMode === MeasureMode.AtMost) { + measuredHeight = Math.min(measuredHeight, height); + } else if (heightMode === MeasureMode.Exactly) { + measuredHeight = height; + } + + return { width: measuredWidth, height: measuredHeight }; + }); + } + + render(canvas: Canvas): void { + const { left, top, width } = this.yoga.getComputedLayout(); + + const lines = layoutText(this.realize(), width); + + let y = top; + + for (const line of lines) { + let x = left; + + for (const segment of line) { + for (const character of segment.text) { + canvas.put(x, y, { + background: segment.background, + foreground: segment.color, + text: character, + bold: segment.bold, + underline: segment.underline, + italic: segment.italic + }) + x++; + } + } + + y++; + } + } } diff --git a/packages/vortex-common/src/dev.ts b/packages/vortex-common/src/dev.ts index 59b146e..32bae95 100644 --- a/packages/vortex-common/src/dev.ts +++ b/packages/vortex-common/src/dev.ts @@ -3,6 +3,14 @@ export function TODO(whatToDo: string): never { } export function trace(message: string) { + if (!("process" in globalThis) || process.env.DEBUG_PERF !== "1") { + return { + [Symbol.dispose]() { + // No-op + }, + }; + } + console.time(message); return { diff --git a/packages/vortex-intrinsics/src/index.ts b/packages/vortex-intrinsics/src/index.ts index 36eda58..4b90642 100644 --- a/packages/vortex-intrinsics/src/index.ts +++ b/packages/vortex-intrinsics/src/index.ts @@ -100,6 +100,7 @@ export const Frame = intrinsic< direction?: "row" | "column" | "row-reverse" | "column-reverse"; grow?: number; gap?: number | string; + clip?: boolean; alignItems?: | "flex-start" | "flex-end" @@ -113,6 +114,11 @@ export const Frame = intrinsic< | "space-between" | "space-around" | "space-evenly"; + position?: "absolute" | "relative" | "static"; + left?: number; + top?: number; + right?: number; + bottom?: number; }, "vortex:frame" >("vortex:frame"); diff --git a/packages/wormhole/src/cli/statusboard.ts b/packages/wormhole/src/cli/statusboard.ts deleted file mode 100644 index 4246fef..0000000 --- a/packages/wormhole/src/cli/statusboard.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { - awaited, - flatten, - Lifetime, - useDerived, - useEffect, -} from "@vortexjs/core"; -import chalk from "chalk"; -import { createPrinter } from "~/cli/printer"; -import type { Project } from "~/state"; -import { showErrors } from "~/build/errors"; - -export function StatusBoard(state: Project) { - const lt = state.lt; - using _hlt = Lifetime.changeHookLifetime(lt); - - const lines = useDerived(async (get) => { - const printer = createPrinter(); - - using _p = printer.indent(); - - printer.gap(); - printer.log(chalk.hex("#3b82f6")("wormhole")); - printer.gap(); - - { - using _g = printer.group("Tasks"); - const currentTasks = get(tasks); - - if (currentTasks.length === 0) { - printer.log("No tasks available."); - } else { - for (const task of currentTasks) { - printer.log(`- ${task.name}`); - } - } - } - - const errors = get(state.errors); - - if (errors.length > 0) { - using _g = printer.group(`${errors.length} Errors`); - - await showErrors(state, printer); - } - - return printer.lines; - }); - - const awaitedLines = flatten(useDerived((get) => awaited(get(lines)))); - - useEffect((get) => { - const lines = get(awaitedLines); - - if (!lines) return; - - console.clear(); - - for (const line of lines) { - console.log(line); - } - }); -} - -import { getImmediateValue, type Store, useState } from "@vortexjs/core"; - -export interface Task { - [Symbol.dispose]: () => void; - name: string; -} - -export const tasks: Store = useState([]); - -export function addTask(props: Omit): Task { - const task: Task = { - ...props, - [Symbol.dispose]: () => { - tasks.set(getImmediateValue(tasks).filter((t) => t !== task)); - }, - }; - - tasks.set([...getImmediateValue(tasks), task]); - - return task; -} diff --git a/packages/wormhole/src/cli/statusboard.tsx b/packages/wormhole/src/cli/statusboard.tsx new file mode 100644 index 0000000..a8824ac --- /dev/null +++ b/packages/wormhole/src/cli/statusboard.tsx @@ -0,0 +1,113 @@ +import { + awaited, + flatten, + Lifetime, + list, + store, + useDerived, + useEffect, + useInterval, + useTimeout, +} from "@vortexjs/core"; +import type { Project } from "~/state"; +import { getImmediateValue, type Store, useState } from "@vortexjs/core"; +import { cliApp } from "@vortexjs/cli"; +import { Frame, Text } from "@vortexjs/intrinsics"; +import { theme } from "./theme"; + +function Throbber() { + let i = store(0); + + useInterval(100, () => { + i.set(getImmediateValue(i) + 1); + }) + + const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + + const frame = useDerived((get) => { + return frames[get(i) % frames.length]; + }); + + return + {frame} + +} + +function TaskView({ task }: { task: Task }) { + return + {" "} + {task.name} + +} + +export function StatusBoard(state: Project) { + const lt = state.lt; + using _hlt = Lifetime.changeHookLifetime(lt); + + const uptime = useState("uptime"); + let starting = Date.now(); + + setInterval(() => { + const uptimeMs = Date.now() - starting; + let ms = uptimeMs; + let seconds = Math.floor(ms / 1000); + ms -= seconds * 1000; + let minutes = Math.floor(seconds / 60); + seconds -= minutes * 60; + let hours = Math.floor(minutes / 60); + minutes -= hours * 60; + let days = Math.floor(hours / 24); + hours -= days * 24; + + uptime.set( + `${days}d ${hours}h ${minutes}m ${seconds}s` + ); + }, 100); + + cliApp( + + + + + • + + wormhole + + + {uptime} + + + + Tasks + + + {list(tasks).show((item) => ( + + ))} + + + + Errors + + ) +} + +export interface Task { + [Symbol.dispose]: () => void; + name: string; +} + +export const tasks: Store = useState([]); + +export function addTask(props: Omit): Task { + const task: Task = { + ...props, + [Symbol.dispose]: () => { + tasks.set(getImmediateValue(tasks).filter((t) => t !== task)); + }, + }; + + tasks.set([...getImmediateValue(tasks), task]); + + return task; +} diff --git a/packages/wormhole/src/cli/theme.ts b/packages/wormhole/src/cli/theme.ts new file mode 100644 index 0000000..532503b --- /dev/null +++ b/packages/wormhole/src/cli/theme.ts @@ -0,0 +1,11 @@ +import { colors } from "@vortexjs/cli"; + +export const theme = { + background: "#111111", + backgroundRaised: "#151515", + backgroundLower: colors.black, + text: colors.zinc[50], + textMuted: colors.zinc[400], + accent: "#8000FF", + border: "#202020" +} From bb769809c38840b3d0d58a3ef33beaabd56334d0 Mon Sep 17 00:00:00 2001 From: andylovescode Date: Mon, 18 Aug 2025 00:04:36 -0700 Subject: [PATCH 10/13] Wormhole: Add custom logger --- packages/wormhole/src/cli/statusboard.tsx | 201 ++++++++++++++++++---- 1 file changed, 170 insertions(+), 31 deletions(-) diff --git a/packages/wormhole/src/cli/statusboard.tsx b/packages/wormhole/src/cli/statusboard.tsx index a8824ac..4dbab73 100644 --- a/packages/wormhole/src/cli/statusboard.tsx +++ b/packages/wormhole/src/cli/statusboard.tsx @@ -11,9 +11,10 @@ import { } from "@vortexjs/core"; import type { Project } from "~/state"; import { getImmediateValue, type Store, useState } from "@vortexjs/core"; -import { cliApp } from "@vortexjs/cli"; +import { cliApp, colors } from "@vortexjs/cli"; import { Frame, Text } from "@vortexjs/intrinsics"; import { theme } from "./theme"; +import type { HTTPMethod } from "~/shared/http-method"; function Throbber() { let i = store(0); @@ -44,6 +45,156 @@ export function StatusBoard(state: Project) { const lt = state.lt; using _hlt = Lifetime.changeHookLifetime(lt); + cliApp( + + + ) +} + +function Prefix({ text, color }: { text: string; color: string }) { + const width = 6; + + return + {text.padEnd(width, " ")} + +} + +function getMethodColor(method: HTTPMethod): string { + switch (method) { + case "GET": + return colors.blue[400]; + case "POST": + return colors.emerald[400]; + case "DELETE": + return colors.red[400]; + case "PATCH": + return colors.yellow[400]; + } +} + +function StatusCode({ code }: { code: number }) { + let accent = colors.gray[400]; + + if (code >= 400 && code < 500) { + accent = colors.rose[400]; + } + if (code >= 500) { + accent = colors.red[400]; + } + if (code >= 300 && code < 400) { + accent = colors.emerald[400]; + } + + return + {code.toString().padStart(3, "0")} + +} + +function NetworkTag({ tag }: { tag: RequestTag }) { + let color: string; + + switch (tag) { + case "api": + color = colors.blue[400]; + break; + case "static": + color = colors.emerald[400]; + break; + case "ssr": + color = colors.yellow[400]; + break; + case "query": + color = colors.purple[400]; + break; + case "mutation": + color = colors.orange[400]; + break; + default: + color = colors.gray[400]; + } + + return + {" "}({tag}) + ; +} + +function LogView({ log }: { log: Log }) { + if (log.type === "raw") { + return + {log.message} + ; + } else if (log.type === "request") { + const urlLength = 25; + + return + {log.url.padEnd(urlLength)} + {list(log.tags).show(tag => )} + ; + } + return <>; +} + +function LogPanel() { + enableConsoleLogShim(); + + return + Logger + + + {list(logs).show(log => )} + + + +} + +export interface Task { + [Symbol.dispose]: () => void; + name: string; +} + +export type RequestTag = "api" | "static" | "ssr" | "query" | "mutation"; + +export type Log = { + type: "raw"; + message: string; + color?: string; +} | { + type: "request"; + method: HTTPMethod; + url: string; + responseCode: number; + tags: RequestTag[]; +} + +export const tasks: Store = useState([]); +export const logs: Store = useState([]); + +export function addLog(log: Log) { + logs.set([...getImmediateValue(logs), log]); +} + +function enableConsoleLogShim() { + const formatArgs = (args: any[]) => { + return args.map(x => typeof x === "string" ? x : Bun.inspect(x)).join(" ") + } + + console.log = (...args: any[]) => { + const formatted = formatArgs(args); + addLog({ type: "raw", message: formatted }); + } + + console.error = (...args: any[]) => { + const formatted = formatArgs(args); + addLog({ type: "raw", message: formatted, color: colors.red[400] }); + } + + console.warn = (...args: any[]) => { + const formatted = formatArgs(args); + addLog({ type: "raw", message: formatted, color: colors.yellow[400] }); + } +} + +function LeftPanel() { const uptime = useState("uptime"); let starting = Date.now(); @@ -64,41 +215,29 @@ export function StatusBoard(state: Project) { ); }, 100); - cliApp( - - - - - • - - wormhole + return + + + + • - - {uptime} - - - - Tasks - - - {list(tasks).show((item) => ( - - ))} + wormhole + + + {uptime} + + + + Tasks - - - Errors - - ) -} -export interface Task { - [Symbol.dispose]: () => void; - name: string; + {list(tasks).show((item) => ( + + ))} + + ; } -export const tasks: Store = useState([]); - export function addTask(props: Omit): Task { const task: Task = { ...props, From 8f528a35ff40220ec57cdaf84684c0f34c53148b Mon Sep 17 00:00:00 2001 From: andylovescode Date: Mon, 18 Aug 2025 09:29:57 -0700 Subject: [PATCH 11/13] Wormhole: Add request logging and 404 pages --- packages/discovery/src/api.ts | 2 +- packages/discovery/src/transpile/route.ts | 2 +- .../src/features/home/index.tsx | 95 +++-- packages/wormhole/src/build/adapters/dev.ts | 327 +++++++-------- packages/wormhole/src/build/build.ts | 175 ++++---- packages/wormhole/src/build/build_analysis.ts | 172 ++++---- packages/wormhole/src/build/router.ts | 376 +++++++++--------- packages/wormhole/src/cli/statusboard.tsx | 370 ++++++++--------- packages/wormhole/src/dev/dev-server.ts | 256 ++++++------ packages/wormhole/src/index.ts | 1 + packages/wormhole/src/runtime/entrypoint.tsx | 158 ++++---- packages/wormhole/src/virt/route.ts | 1 + 12 files changed, 994 insertions(+), 941 deletions(-) diff --git a/packages/discovery/src/api.ts b/packages/discovery/src/api.ts index 657bdcd..a953127 100644 --- a/packages/discovery/src/api.ts +++ b/packages/discovery/src/api.ts @@ -16,7 +16,7 @@ export type Discovery = type: "route_frame"; path: string; exported: string; - frameType: "page" | "layout"; + frameType: "page" | "layout" | "notFound"; } | { type: "api"; diff --git a/packages/discovery/src/transpile/route.ts b/packages/discovery/src/transpile/route.ts index 8a431fc..feb6d51 100644 --- a/packages/discovery/src/transpile/route.ts +++ b/packages/discovery/src/transpile/route.ts @@ -41,7 +41,7 @@ export function handleRouteFunction( return; } - const validKeys = ["page", "layout"] as const; + const validKeys = ["page", "layout", "notFound"] as const; for (const key of keys) { if (!validKeys.includes(key as any)) { diff --git a/packages/example-wormhole/src/features/home/index.tsx b/packages/example-wormhole/src/features/home/index.tsx index d735308..e7715a7 100644 --- a/packages/example-wormhole/src/features/home/index.tsx +++ b/packages/example-wormhole/src/features/home/index.tsx @@ -2,54 +2,61 @@ import route, { query } from "@vortexjs/wormhole/route"; import * as v from "valibot"; route("/", { - page() { - return ( - <> -

- Welcome to Wormhole, {Object.entries(globalThis).length} -

-

- This is an example app, go to the{" "} - docs -

- - - ); - }, - layout({ children }) { - return ( - <> - Wormhole Example - {children} - - ); - }, + page() { + return ( + <> +

+ Welcome to Wormhole, {Object.entries(globalThis).length} +

+

+ This is an example app, go to the{" "} + docs +

+ + + ); + }, + layout({ children }) { + return ( + <> + Wormhole Example + {children} + + ); + }, + notFound() { + return ( + <> +

404 not found

+ + ) + } }); route("/docs/[...page]", { - page({ page }) { - return ( - <> -

Documentation for {page.join(", ")}

-

This is the documentation page for {page.join(", ")}.

- - ); - }, + page({ page }) { + return ( + <> +

Documentation for {page.join(", ")}

+

This is the documentation page for {page.join(", ")}.

+ + ); + }, }); export const add = query("/api/add", { - schema: v.object({ - a: v.number(), - b: v.number() - }), - impl({ a, b }) { - return a + b; - } + schema: v.object({ + a: v.number(), + b: v.number() + }), + impl({ a, b }) { + return a + b; + } }) diff --git a/packages/wormhole/src/build/adapters/dev.ts b/packages/wormhole/src/build/adapters/dev.ts index 8939bfa..217df92 100644 --- a/packages/wormhole/src/build/adapters/dev.ts +++ b/packages/wormhole/src/build/adapters/dev.ts @@ -6,76 +6,79 @@ import { addTask } from "~/cli/statusboard"; import { join } from "node:path"; export interface DevAdapterResult { - clientEntry: string; - serverEntry: string; - cssEntry: string; - outdir: string; + clientEntry: string; + serverEntry: string; + cssEntry: string; + outdir: string; } export interface DevAdapter extends BuildAdapter { - buildForLocation(build: Build, server: TargetLocation): Promise; - buildCSS(build: Build): Promise; + buildForLocation(build: Build, server: TargetLocation): Promise; + buildCSS(build: Build): Promise; } export function DevAdapter(): DevAdapter { - return { - async buildForLocation(build: Build, location: TargetLocation) { - using _task = addTask({ - name: `Rebuilding development ${location}` - }); - let codegenSource = ""; - - codegenSource += `import { INTERNAL_entrypoint } from "@vortexjs/wormhole";`; - codegenSource += `import { Lifetime } from "@vortexjs/core";` - - if (location === "client") { - codegenSource += `import { html } from "@vortexjs/dom";`; - } - - codegenSource += `export function main(props) {`; - - const imports: Export[] = []; - - function getExportIndex(exp: Export): number { - const index = imports.findIndex(x => x.file === exp.file && x.name === exp.name); - if (index === -1) { - imports.push(exp); - return imports.length - 1; - } - return index; - } - - const entrypointProps: EntrypointProps = { - routes: build.routes.filter(x => x.type === "route").map(x => ({ - matcher: x.matcher, - frames: x.frames.map((frame) => ({ - index: getExportIndex(frame), - })), - })) - } - - codegenSource += 'const loaders = ['; - - for (const exp of imports) { - const reexporterName = "proxy-" + Bun.hash(`${exp.file}-${exp.name}`).toString(36); - - const path = await build.writeCodegenned(reexporterName, `export { ${JSON.stringify(exp.name)} } from ${JSON.stringify(exp.file)}`); - - codegenSource += `(async () => (await import(${JSON.stringify(path)}))[${JSON.stringify(exp.name)}]),`; - } - - codegenSource += '];'; - - if (location === "server") { - codegenSource += `const renderer = props.renderer;`; - codegenSource += `const root = props.root;`; - } else { - codegenSource += `const renderer = html();`; - codegenSource += `const root = document.documentElement;`; - } - - codegenSource += `return INTERNAL_entrypoint({ - props: ${JSON.stringify(entrypointProps)}, + return { + async buildForLocation(build: Build, location: TargetLocation) { + using _task = addTask({ + name: `Rebuilding development ${location}` + }); + let codegenSource = ""; + + codegenSource += `import { INTERNAL_entrypoint } from "@vortexjs/wormhole";`; + codegenSource += `import { Lifetime } from "@vortexjs/core";` + + if (location === "client") { + codegenSource += `import { html } from "@vortexjs/dom";`; + } + + const imports: Export[] = []; + + function getExportIndex(exp: Export): number { + const index = imports.findIndex(x => x.file === exp.file && x.name === exp.name); + if (index === -1) { + imports.push(exp); + return imports.length - 1; + } + return index; + } + + const entrypointProps: EntrypointProps = { + routes: build.routes.filter(x => x.type === "route").map(x => ({ + matcher: x.matcher, + frames: x.frames.map((frame) => ({ + index: getExportIndex(frame), + })), + is404: x.is404, + })) + } + + codegenSource += `const entrypointProps = JSON.parse(${JSON.stringify(JSON.stringify(entrypointProps))});`; + + codegenSource += `export function main(props) {`; + + codegenSource += 'const loaders = ['; + + for (const exp of imports) { + const reexporterName = "proxy-" + Bun.hash(`${exp.file}-${exp.name}`).toString(36); + + const path = await build.writeCodegenned(reexporterName, `export { ${JSON.stringify(exp.name)} } from ${JSON.stringify(exp.file)}`); + + codegenSource += `(async () => (await import(${JSON.stringify(path)}))[${JSON.stringify(exp.name)}]),`; + } + + codegenSource += '];'; + + if (location === "server") { + codegenSource += `const renderer = props.renderer;`; + codegenSource += `const root = props.root;`; + } else { + codegenSource += `const renderer = html();`; + codegenSource += `const root = document.documentElement;`; + } + + codegenSource += `return INTERNAL_entrypoint({ + props: entrypointProps, loaders, renderer, root, @@ -84,98 +87,106 @@ export function DevAdapter(): DevAdapter { lifetime: props.lifetime ?? new Lifetime(), });`; - codegenSource += `}`; - - if (location === "server") { - codegenSource += `import {INTERNAL_tryHandleAPI} from "@vortexjs/wormhole";` - codegenSource += `export async function tryHandleAPI(request) {`; - - const apiIndicies: Export[] = []; - - const apiRoutes = build.routes.filter(x => x.type === "api"); - - const getApiExportIndex = (exp: Export): number => { - const index = apiIndicies.findIndex(x => x.file === exp.file && x.name === exp.name); - if (index === -1) { - apiIndicies.push(exp); - return apiIndicies.length - 1; - } - return index; - } - - codegenSource += `const apis = ${JSON.stringify(apiRoutes.map(x => ({ - matcher: x.matcher, - impl: getApiExportIndex(x.impl), - schema: getApiExportIndex(x.schema), - method: x.method, - })))};`; - - codegenSource += `return INTERNAL_tryHandleAPI(request, apis, [`; - - for (const exp of apiIndicies) { - const reexporterName = "proxy-" + Bun.hash(`${exp.file}-${exp.name}`).toString(36); - - const path = await build.writeCodegenned(reexporterName, `export { ${JSON.stringify(exp.name)} } from ${JSON.stringify(exp.file)}`); - - codegenSource += `(async () => (await import(${JSON.stringify(path)}))[${JSON.stringify(exp.name)}]),`; - } - - codegenSource += `]);`; - - codegenSource += `}`; - } - - if (location === "client") { - codegenSource += `window.wormhole = {};`; - codegenSource += `window.wormhole.hydrate = main;`; - } - - const filename = `entrypoint-${location}`; - - const path = await build.writeCodegenned(filename, codegenSource); - - const bundled = await build.bundle({ - target: location, - inputPaths: { - main: path, - }, - dev: true - }) - - return bundled.outputs.main; - }, - async buildCSS(build: Build) { - let codegenCSS = ""; - - const appCSSPath = join(build.project.projectDir, "src", "app.css"); - - if (await Bun.file(appCSSPath).exists()) { - codegenCSS += `@import "${appCSSPath}";`; - } - - const cssPath = await build.writeCodegenned("styles", codegenCSS, "css"); - - const bundled = await build.bundle({ - target: "client", - inputPaths: { - main: cssPath, - }, - dev: true - }); - - return bundled.outputs.main; - }, - async run(build) { - const clientEntry = this.buildForLocation(build, "client"); - const serverEntry = this.buildForLocation(build, "server"); - const cssEntry = this.buildCSS(build); - - return { - clientEntry: await clientEntry, - serverEntry: await serverEntry, - cssEntry: await cssEntry, - outdir: build.outputPath - } - } - }; + codegenSource += `}`; + + if (location === "server") { + codegenSource += `import {INTERNAL_tryHandleAPI} from "@vortexjs/wormhole";` + codegenSource += `export async function tryHandleAPI(request) {`; + + const apiIndicies: Export[] = []; + + const apiRoutes = build.routes.filter(x => x.type === "api"); + + const getApiExportIndex = (exp: Export): number => { + const index = apiIndicies.findIndex(x => x.file === exp.file && x.name === exp.name); + if (index === -1) { + apiIndicies.push(exp); + return apiIndicies.length - 1; + } + return index; + } + + codegenSource += `const apis = ${JSON.stringify(apiRoutes.map(x => ({ + matcher: x.matcher, + impl: getApiExportIndex(x.impl), + schema: getApiExportIndex(x.schema), + method: x.method, + })))};`; + + codegenSource += `return INTERNAL_tryHandleAPI(request, apis, [`; + + for (const exp of apiIndicies) { + const reexporterName = "proxy-" + Bun.hash(`${exp.file}-${exp.name}`).toString(36); + + const path = await build.writeCodegenned(reexporterName, `export { ${JSON.stringify(exp.name)} } from ${JSON.stringify(exp.file)}`); + + codegenSource += `(async () => (await import(${JSON.stringify(path)}))[${JSON.stringify(exp.name)}]),`; + } + + codegenSource += `]);`; + + codegenSource += `}`; + } + + if (location === "server") { + codegenSource += `import { matchPath } from "@vortexjs/wormhole";`; + codegenSource += `export function isRoute404(pathname) {`; + codegenSource += `const route = entrypointProps.routes.find(x => matchPath(x.matcher, pathname).matched);`; + codegenSource += `return route ? route.is404 : false;`; + codegenSource += `}`; + } + + if (location === "client") { + codegenSource += `window.wormhole = {};`; + codegenSource += `window.wormhole.hydrate = main;`; + } + + const filename = `entrypoint-${location}`; + + const path = await build.writeCodegenned(filename, codegenSource); + + const bundled = await build.bundle({ + target: location, + inputPaths: { + main: path, + }, + dev: true + }) + + return bundled.outputs.main; + }, + async buildCSS(build: Build) { + let codegenCSS = ""; + + const appCSSPath = join(build.project.projectDir, "src", "app.css"); + + if (await Bun.file(appCSSPath).exists()) { + codegenCSS += `@import "${appCSSPath}";`; + } + + const cssPath = await build.writeCodegenned("styles", codegenCSS, "css"); + + const bundled = await build.bundle({ + target: "client", + inputPaths: { + main: cssPath, + }, + dev: true + }); + + return bundled.outputs.main; + }, + async run(build) { + const clientEntry = this.buildForLocation(build, "client"); + const serverEntry = this.buildForLocation(build, "server"); + const cssEntry = this.buildCSS(build); + + return { + clientEntry: await clientEntry, + serverEntry: await serverEntry, + cssEntry: await cssEntry, + outdir: build.outputPath + } + } + }; } diff --git a/packages/wormhole/src/build/build.ts b/packages/wormhole/src/build/build.ts index d570818..dfa2cfd 100644 --- a/packages/wormhole/src/build/build.ts +++ b/packages/wormhole/src/build/build.ts @@ -11,26 +11,27 @@ import { rm, rmdir } from "node:fs/promises"; import type { HTTPMethod } from "~/shared/http-method"; export interface BuildBaseRoute { - type: Type; - matcher: RoutePath; + type: Type; + matcher: RoutePath; } export interface BuildAPIRoute extends BuildBaseRoute<"api"> { - impl: Export; - schema: Export; - method: HTTPMethod; + impl: Export; + schema: Export; + method: HTTPMethod; } export interface BuildPageRoute extends BuildBaseRoute<"route"> { - frames: Export[]; + frames: Export[]; + is404: boolean; } export type BuildRoute = BuildAPIRoute | BuildPageRoute; export interface BuildAdapter { - run( - build: Build, - ): Promise; + run( + build: Build, + ): Promise; } export type TargetLocation = "client" | "server"; @@ -47,82 +48,82 @@ export type TargetLocation = "client" | "server"; * 3. Pass the adapter data back up */ export class Build { - routes: BuildRoute[] = []; - outputPath: string; - workingPath: string; - - constructor(public project: Project, public adapter: BuildAdapter) { - const bb = this.project.paths.wormhole.buildBox(crypto.randomUUID()); - this.outputPath = bb.output.path; - this.workingPath = bb.codegenned.path; - } - - async writeCodegenned( - name: string, - content: string, - ext = "tsx" - ): Promise { - const path = join(this.workingPath, `${name}.${ext}`); - - await Bun.write(path, content); - - return path; - } - - analyze = Build_analyze; - - async bundle( - { inputPaths, target, dev = false }: { - inputPaths: Record, - target: TargetLocation; - dev?: boolean; - } - ): Promise<{ - outputs: Record; - }> { - const entrypoints = Object.values(inputPaths); - const p = pippin(); - - // Check for tailwind - if (getImmediateValue(this.project.config).tailwind?.enabled) { - p.add(pippinPluginTailwind()); - } - - p.add(pippinPluginDiscovery({ - target - })); - - const build = await Bun.build({ - plugins: [p], - splitting: true, - entrypoints, - outdir: this.outputPath, - target: target === "server" ? "bun" : "browser", - sourcemap: dev ? "inline" : "none", - naming: { - entry: "[name].[ext]", - }, - minify: !dev, - }); - - const results: Record = {} as any; - - for (const [id, entry] of Object.entries(inputPaths)) { - const name = basename(entry as string); - const fileName = name.slice(0, name.lastIndexOf(".")); - const path = join(this.outputPath, fileName + ".js"); - - results[id as Files] = path; - } - - return { - outputs: results - }; - } - - async run() { - await this.project.index.instance.ready; - await this.analyze(); - return await this.adapter.run(this); - } + routes: BuildRoute[] = []; + outputPath: string; + workingPath: string; + + constructor(public project: Project, public adapter: BuildAdapter) { + const bb = this.project.paths.wormhole.buildBox(crypto.randomUUID()); + this.outputPath = bb.output.path; + this.workingPath = bb.codegenned.path; + } + + async writeCodegenned( + name: string, + content: string, + ext = "tsx" + ): Promise { + const path = join(this.workingPath, `${name}.${ext}`); + + await Bun.write(path, content); + + return path; + } + + analyze = Build_analyze; + + async bundle( + { inputPaths, target, dev = false }: { + inputPaths: Record, + target: TargetLocation; + dev?: boolean; + } + ): Promise<{ + outputs: Record; + }> { + const entrypoints = Object.values(inputPaths); + const p = pippin(); + + // Check for tailwind + if (getImmediateValue(this.project.config).tailwind?.enabled) { + p.add(pippinPluginTailwind()); + } + + p.add(pippinPluginDiscovery({ + target + })); + + const build = await Bun.build({ + plugins: [p], + splitting: true, + entrypoints, + outdir: this.outputPath, + target: target === "server" ? "bun" : "browser", + sourcemap: dev ? "inline" : "none", + naming: { + entry: "[name].[ext]", + }, + minify: !dev, + }); + + const results: Record = {} as any; + + for (const [id, entry] of Object.entries(inputPaths)) { + const name = basename(entry as string); + const fileName = name.slice(0, name.lastIndexOf(".")); + const path = join(this.outputPath, fileName + ".js"); + + results[id as Files] = path; + } + + return { + outputs: results + }; + } + + async run() { + await this.project.index.instance.ready; + await this.analyze(); + return await this.adapter.run(this); + } } diff --git a/packages/wormhole/src/build/build_analysis.ts b/packages/wormhole/src/build/build_analysis.ts index 3e9155c..7d3d01b 100644 --- a/packages/wormhole/src/build/build_analysis.ts +++ b/packages/wormhole/src/build/build_analysis.ts @@ -6,103 +6,105 @@ import { SKL } from "@vortexjs/common"; import type { HTTPMethod } from "~/shared/http-method"; export async function Build_analyze(this: Build) { - await this.project.index.instance.ready; + await this.project.index.instance.ready; - // P1 Routes: API routes - const apiDiscoveries = getImmediateValue(this.project.index.instance.discoveries).filter(x => x.type === "api"); + // P1 Routes: API routes + const apiDiscoveries = getImmediateValue(this.project.index.instance.discoveries).filter(x => x.type === "api"); - for (const discovery of apiDiscoveries) { - this.routes.push({ - type: "api", - impl: { - name: discovery.exported.impl, - file: discovery.filePath, - }, - schema: { - name: discovery.exported.schema, - file: discovery.filePath, - }, - matcher: parseRoute(discovery.endpoint), - method: discovery.method as HTTPMethod, - }) - } + for (const discovery of apiDiscoveries) { + this.routes.push({ + type: "api", + impl: { + name: discovery.exported.impl, + file: discovery.filePath, + }, + schema: { + name: discovery.exported.schema, + file: discovery.filePath, + }, + matcher: parseRoute(discovery.endpoint), + method: discovery.method as HTTPMethod, + }) + } - // P2 Routes: Page routes - // Page routes are more complex, as they can have multiple frames and layouts, additionally, we convert from a complex tree to a flat list of matchers - const pageDiscoveries = getImmediateValue(this.project.index.instance.discoveries).filter(x => x.type === "route_frame"); + // P2 Routes: Page routes + // Page routes are more complex, as they can have multiple frames and layouts, additionally, we convert from a complex tree to a flat list of matchers + const pageDiscoveries = getImmediateValue(this.project.index.instance.discoveries).filter(x => x.type === "route_frame"); - const routeTree = RouterNode({ - routes: pageDiscoveries.map(x => ({ - path: x.path, - frame: { - name: x.exported, - file: x.filePath, - }, - frameType: x.frameType - })), - errors: this.project.routingErrors, - }); + const routeTree = RouterNode({ + routes: pageDiscoveries.map(x => ({ + path: x.path, + frame: { + name: x.exported, + file: x.filePath, + }, + frameType: x.frameType + })), + errors: this.project.routingErrors, + }); - flattenNode({ - build: this, - node: routeTree, - path: [], - layouts: [], - }); + flattenNode({ + build: this, + node: routeTree, + path: [], + layouts: [], + }); } function flattenNode( - { - build, - node, - path, - layouts - }: { - build: Build, - node: RouterNode, - path: RoutePath, - layouts: Export[], - } + { + build, + node, + path, + layouts + }: { + build: Build, + node: RouterNode, + path: RoutePath, + layouts: Export[], + } ) { - if (node.fallbackTransition && node.isSpreadFallback) { - path.push({ type: "spread", name: node.fallbackTransition.id }); - } + if (node.fallbackTransition && node.isSpreadFallback) { + path.push({ type: "spread", name: node.fallbackTransition.id }); + } - if (node.page) { - build.routes.push({ - type: "route", - matcher: path, - frames: [...layouts, node.page], - }); - } + if (node.page) { + build.routes.push({ + type: "route", + matcher: path, + frames: [...layouts, node.page], + is404: false, + }); + } - if (node.layout) { - layouts.push(node.layout); - } + if (node.layout) { + layouts.push(node.layout); + } - for (const [key, child] of Object.entries(node.cases)) { - flattenNode({ - build, - node: child, - path: [...path, { type: "static", match: key }], - layouts: [...layouts], - }); - } + for (const [key, child] of Object.entries(node.cases)) { + flattenNode({ + build, + node: child, + path: [...path, { type: "static", match: key }], + layouts: [...layouts], + }); + } - if (node.fallbackTransition && !node.isSpreadFallback) { - flattenNode({ - build, - node: node.fallbackTransition.node, - path: [...path, { type: "slug", name: node.fallbackTransition.id }], - layouts: [...layouts], - }) - } + if (node.fallbackTransition && !node.isSpreadFallback) { + flattenNode({ + build, + node: node.fallbackTransition.node, + path: [...path, { type: "slug", name: node.fallbackTransition.id }], + layouts: [...layouts], + }) + } - if (node.notFoundPage) { - build.routes.push({ - type: "route", - matcher: [...path, { type: "spread", name: "404" }], - frames: [...layouts, node.notFoundPage], - }); - } + if (node.notFoundPage) { + build.routes.push({ + type: "route", + matcher: [...path, { type: "spread", name: "404" }], + frames: [...layouts, node.notFoundPage], + is404: true, + }); + } } diff --git a/packages/wormhole/src/build/router.ts b/packages/wormhole/src/build/router.ts index dac930a..6972c1f 100644 --- a/packages/wormhole/src/build/router.ts +++ b/packages/wormhole/src/build/router.ts @@ -5,212 +5,222 @@ import type { Export } from "~/local/export"; // Parsing export type RoutePath = RouteSegment[]; export type RouteSegment = - | { type: "spread"; name: string } - | { type: "static"; match: string } - | { type: "slug"; name: string }; + | { type: "spread"; name: string } + | { type: "static"; match: string } + | { type: "slug"; name: string }; export function parseRoute(route: string): RoutePath { - const segments = route - .split("/") - .map((x) => x.trim()) - .filter((x) => x !== ""); - const result: RoutePath = []; - - for (const segment of segments) { - if (segment.startsWith("[") && segment.endsWith("]")) { - const body = segment.slice(1, -1); - - if (body.startsWith("...")) { - result.push({ type: "spread", name: body.slice(3) }); - } else { - result.push({ type: "slug", name: body }); - } - } else { - result.push({ - type: "static", - match: segment, - }); - } - } - - return result; + const segments = route + .split("/") + .map((x) => x.trim()) + .filter((x) => x !== ""); + const result: RoutePath = []; + + for (const segment of segments) { + if (segment.startsWith("[") && segment.endsWith("]")) { + const body = segment.slice(1, -1); + + if (body.startsWith("...")) { + result.push({ type: "spread", name: body.slice(3) }); + } else { + result.push({ type: "slug", name: body }); + } + } else { + result.push({ + type: "static", + match: segment, + }); + } + } + + return result; } export function printRoutePath(path: RoutePath): string { - return path - .map((segment) => { - if (segment.type === "static") { - return segment.match; - } else if (segment.type === "slug") { - return `[${segment.name}]`; - } else if (segment.type === "spread") { - return `[...${segment.name}]`; - } - }) - .join("/"); + return path + .map((segment) => { + if (segment.type === "static") { + return segment.match; + } else if (segment.type === "slug") { + return `[${segment.name}]`; + } else if (segment.type === "spread") { + return `[...${segment.name}]`; + } + }) + .join("/"); } // Tree generation export interface RouterNode { - cases: Record; // Cases for static segments - layout?: Export; // Layout page, if any - page?: Export; // Page to render for this route - notFoundPage?: Export; // Not found page, if any - fallbackTransition?: { - node: RouterNode; - id: string; // the param key to add the value to - }; // Epsilon transition, where it doesn't match any special cases - isSpreadFallback?: boolean; - sourcePath: string; + cases: Record; // Cases for static segments + layout?: Export; // Layout page, if any + page?: Export; // Page to render for this route + notFoundPage?: Export; // Not found page, if any + fallbackTransition?: { + node: RouterNode; + id: string; // the param key to add the value to + }; // Epsilon transition, where it doesn't match any special cases + isSpreadFallback?: boolean; + sourcePath: string; }; export interface InputRoute { - path: string; // The path of the route - frame: Export; // The frame to render for this route - frameType: "page" | "layout"; + path: string; // The path of the route + frame: Export; // The frame to render for this route + frameType: "page" | "layout" | "notFound"; } function makeBlankNode(source: string): RouterNode { - return { - cases: {}, - sourcePath: source, - } + return { + cases: {}, + sourcePath: source, + } } export function RouterNode({ routes, errors }: { - routes: InputRoute[]; - errors: UpdatableErrorCollection; + routes: InputRoute[]; + errors: UpdatableErrorCollection; }) { - const tree: RouterNode = makeBlankNode(""); - const errorList: WormholeError[] = []; - - for (const route of routes) { - const parsed = parseRoute(route.path); - let currentNode = tree; - - for (const segment of parsed) { - if (segment.type === "static") { - // Could be a one-liner, but this is more readable - const nextNode = currentNode.cases[segment.match] ?? makeBlankNode(route.path); - currentNode.cases[segment.match] = nextNode; - currentNode = nextNode; - } else if (segment.type === "slug") { - if (!currentNode.fallbackTransition) { - currentNode.fallbackTransition = { - node: makeBlankNode(route.path), - id: segment.name, - }; - } else { - if (currentNode.isSpreadFallback) { - errorList.push( - MessageError( - `When I was trying to add the route '${route.path}', I ran into a problem.`, - `The slug segment '[${segment.name}]' conflicts with a spread segment defined by '${currentNode.fallbackTransition.node.sourcePath}'` - ), - ); - } - } - currentNode = currentNode.fallbackTransition.node; - } else if (segment.type === "spread") { - if (!currentNode.fallbackTransition) { - currentNode.fallbackTransition = { - node: currentNode, - id: segment.name, - }; - } else { - if (!currentNode.isSpreadFallback) { - errorList.push( - MessageError( - `When I was trying to add the route '${route.path}', I ran into a problem.`, - `The spread segment '[...${segment.name}]' conflicts with a slug segment defined by '${currentNode.fallbackTransition.node.sourcePath}'` - ), - ); - } - } - - currentNode.isSpreadFallback = true; - } - } - - if (route.frameType === "page") { - if (currentNode.page) { - errorList.push( - MessageError( - `There are multiple different definitions of '${route.path}' with conflicting pages. Each path can only have one page.`, - ), - ); - } - - currentNode.page = route.frame; - } else if (route.frameType === "layout") { - if (currentNode.layout) { - errorList.push( - MessageError( - `There are multiple different definitions of '${route.path}' with conflicting layouts. Each path can only have one layout.`, - ), - ); - } - - currentNode.layout = route.frame; - } - } - - errors.update(errorList); - - return tree; + const tree: RouterNode = makeBlankNode(""); + const errorList: WormholeError[] = []; + + for (const route of routes) { + const parsed = parseRoute(route.path); + let currentNode = tree; + + for (const segment of parsed) { + if (segment.type === "static") { + // Could be a one-liner, but this is more readable + const nextNode = currentNode.cases[segment.match] ?? makeBlankNode(route.path); + currentNode.cases[segment.match] = nextNode; + currentNode = nextNode; + } else if (segment.type === "slug") { + if (!currentNode.fallbackTransition) { + currentNode.fallbackTransition = { + node: makeBlankNode(route.path), + id: segment.name, + }; + } else { + if (currentNode.isSpreadFallback) { + errorList.push( + MessageError( + `When I was trying to add the route '${route.path}', I ran into a problem.`, + `The slug segment '[${segment.name}]' conflicts with a spread segment defined by '${currentNode.fallbackTransition.node.sourcePath}'` + ), + ); + } + } + currentNode = currentNode.fallbackTransition.node; + } else if (segment.type === "spread") { + if (!currentNode.fallbackTransition) { + currentNode.fallbackTransition = { + node: currentNode, + id: segment.name, + }; + } else { + if (!currentNode.isSpreadFallback) { + errorList.push( + MessageError( + `When I was trying to add the route '${route.path}', I ran into a problem.`, + `The spread segment '[...${segment.name}]' conflicts with a slug segment defined by '${currentNode.fallbackTransition.node.sourcePath}'` + ), + ); + } + } + + currentNode.isSpreadFallback = true; + } + } + + if (route.frameType === "page") { + if (currentNode.page) { + errorList.push( + MessageError( + `There are multiple different definitions of '${route.path}' with conflicting pages. Each path can only have one page.`, + ), + ); + } + + currentNode.page = route.frame; + } else if (route.frameType === "layout") { + if (currentNode.layout) { + errorList.push( + MessageError( + `There are multiple different definitions of '${route.path}' with conflicting layouts. Each path can only have one layout.`, + ), + ); + } + + currentNode.layout = route.frame; + } else if (route.frameType === "notFound") { + if (currentNode.notFoundPage) { + errorList.push( + MessageError( + `There are multiple different definitions of '${route.path}' with conflicting not-found pages. Each path can only have one not-found page.`, + ), + ); + } + + currentNode.notFoundPage = route.frame; + } + } + + errors.update(errorList); + + return tree; } // AI DISCLOSURE: This function is almost entirely AI-generated, with minimal human edits. export function matchPath(matcher: RoutePath, path: string): { - matched: false + matched: false } | { - matched: true; - params: Record; - spreads: Record; + matched: true; + params: Record; + spreads: Record; } { - const pathSegments = path.split("/").map(x => x.trim()).filter(x => x !== ""); - - function recursiveMatch( - matcherIdx: number, - pathIdx: number, - params: Record, - spreads: Record - ): { matched: false } | { matched: true, params: Record, spreads: Record } { - // Base case: both matcher and path are fully consumed - if (matcherIdx === matcher.length && pathIdx === pathSegments.length) { - return { matched: true, params: { ...params }, spreads: { ...spreads } }; - } - // If matcher is consumed but path is not, or vice versa, fail - if (matcherIdx === matcher.length || (pathIdx > pathSegments.length)) { - return { matched: false }; - } - - const segment = unwrap(matcher[matcherIdx]); - - if (segment.type === "static") { - if (pathIdx >= pathSegments.length || pathSegments[pathIdx] !== segment.match) { - return { matched: false }; - } - return recursiveMatch(matcherIdx + 1, pathIdx + 1, params, spreads); - } else if (segment.type === "slug") { - if (pathIdx >= pathSegments.length) { - return { matched: false }; - } - const newParams = { ...params, [segment.name]: pathSegments[pathIdx] }; - return recursiveMatch(matcherIdx + 1, pathIdx + 1, newParams as Record, spreads); - } else if (segment.type === "spread") { - // Try all possible splits for the spread segment - for (let take = 0; take <= pathSegments.length - pathIdx; take++) { - const newSpreads = { ...spreads, [segment.name]: pathSegments.slice(pathIdx, pathIdx + take) }; - const result = recursiveMatch(matcherIdx + 1, pathIdx + take, params, newSpreads); - if (result.matched) { - return result; - } - } - return { matched: false }; - } - return { matched: false }; - } - - return recursiveMatch(0, 0, {}, {}); + const pathSegments = path.split("/").map(x => x.trim()).filter(x => x !== ""); + + function recursiveMatch( + matcherIdx: number, + pathIdx: number, + params: Record, + spreads: Record + ): { matched: false } | { matched: true, params: Record, spreads: Record } { + // Base case: both matcher and path are fully consumed + if (matcherIdx === matcher.length && pathIdx === pathSegments.length) { + return { matched: true, params: { ...params }, spreads: { ...spreads } }; + } + // If matcher is consumed but path is not, or vice versa, fail + if (matcherIdx === matcher.length || (pathIdx > pathSegments.length)) { + return { matched: false }; + } + + const segment = unwrap(matcher[matcherIdx]); + + if (segment.type === "static") { + if (pathIdx >= pathSegments.length || pathSegments[pathIdx] !== segment.match) { + return { matched: false }; + } + return recursiveMatch(matcherIdx + 1, pathIdx + 1, params, spreads); + } else if (segment.type === "slug") { + if (pathIdx >= pathSegments.length) { + return { matched: false }; + } + const newParams = { ...params, [segment.name]: pathSegments[pathIdx] }; + return recursiveMatch(matcherIdx + 1, pathIdx + 1, newParams as Record, spreads); + } else if (segment.type === "spread") { + // Try all possible splits for the spread segment + for (let take = 0; take <= pathSegments.length - pathIdx; take++) { + const newSpreads = { ...spreads, [segment.name]: pathSegments.slice(pathIdx, pathIdx + take) }; + const result = recursiveMatch(matcherIdx + 1, pathIdx + take, params, newSpreads); + if (result.matched) { + return result; + } + } + return { matched: false }; + } + return { matched: false }; + } + + return recursiveMatch(0, 0, {}, {}); } diff --git a/packages/wormhole/src/cli/statusboard.tsx b/packages/wormhole/src/cli/statusboard.tsx index 4dbab73..992dd77 100644 --- a/packages/wormhole/src/cli/statusboard.tsx +++ b/packages/wormhole/src/cli/statusboard.tsx @@ -1,13 +1,13 @@ import { - awaited, - flatten, - Lifetime, - list, - store, - useDerived, - useEffect, - useInterval, - useTimeout, + awaited, + flatten, + Lifetime, + list, + store, + useDerived, + useEffect, + useInterval, + useTimeout, } from "@vortexjs/core"; import type { Project } from "~/state"; import { getImmediateValue, type Store, useState } from "@vortexjs/core"; @@ -17,236 +17,236 @@ import { theme } from "./theme"; import type { HTTPMethod } from "~/shared/http-method"; function Throbber() { - let i = store(0); + let i = store(0); - useInterval(100, () => { - i.set(getImmediateValue(i) + 1); - }) + useInterval(100, () => { + i.set(getImmediateValue(i) + 1); + }) - const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; - const frame = useDerived((get) => { - return frames[get(i) % frames.length]; - }); + const frame = useDerived((get) => { + return frames[get(i) % frames.length]; + }); - return - {frame} - + return + {frame} + } function TaskView({ task }: { task: Task }) { - return - {" "} - {task.name} - + return + {" "} + {task.name} + } export function StatusBoard(state: Project) { - const lt = state.lt; - using _hlt = Lifetime.changeHookLifetime(lt); + const lt = state.lt; + using _hlt = Lifetime.changeHookLifetime(lt); - cliApp( - - - ) + cliApp( + + + ) } function Prefix({ text, color }: { text: string; color: string }) { - const width = 6; + const width = 6; - return - {text.padEnd(width, " ")} - + return + {text.padEnd(width, " ")} + } function getMethodColor(method: HTTPMethod): string { - switch (method) { - case "GET": - return colors.blue[400]; - case "POST": - return colors.emerald[400]; - case "DELETE": - return colors.red[400]; - case "PATCH": - return colors.yellow[400]; - } + switch (method) { + case "GET": + return colors.blue[400]; + case "POST": + return colors.emerald[400]; + case "DELETE": + return colors.red[400]; + case "PATCH": + return colors.yellow[400]; + } } function StatusCode({ code }: { code: number }) { - let accent = colors.gray[400]; - - if (code >= 400 && code < 500) { - accent = colors.rose[400]; - } - if (code >= 500) { - accent = colors.red[400]; - } - if (code >= 300 && code < 400) { - accent = colors.emerald[400]; - } - - return - {code.toString().padStart(3, "0")} - + let accent = colors.gray[400]; + + if (code >= 400 && code < 500) { + accent = colors.rose[400]; + } + if (code >= 500) { + accent = colors.red[400]; + } + if (code >= 300 && code < 400) { + accent = colors.emerald[400]; + } + + return + {code.toString().padStart(3, "0")} + } function NetworkTag({ tag }: { tag: RequestTag }) { - let color: string; - - switch (tag) { - case "api": - color = colors.blue[400]; - break; - case "static": - color = colors.emerald[400]; - break; - case "ssr": - color = colors.yellow[400]; - break; - case "query": - color = colors.purple[400]; - break; - case "mutation": - color = colors.orange[400]; - break; - default: - color = colors.gray[400]; - } - - return - {" "}({tag}) - ; + let color: string; + + switch (tag) { + case "api": + color = colors.blue[400]; + break; + case "static": + color = colors.emerald[400]; + break; + case "ssr": + color = colors.yellow[400]; + break; + case "query": + color = colors.purple[400]; + break; + case "mutation": + color = colors.orange[400]; + break; + default: + color = colors.gray[400]; + } + + return + {" "}({tag}) + ; } function LogView({ log }: { log: Log }) { - if (log.type === "raw") { - return - {log.message} - ; - } else if (log.type === "request") { - const urlLength = 25; - - return - {log.url.padEnd(urlLength)} - {list(log.tags).show(tag => )} - ; - } - return <>; + if (log.type === "raw") { + return + {log.message} + ; + } else if (log.type === "request") { + const urlLength = 25; + + return + {log.url.padEnd(urlLength)} + {list(log.tags).show(tag => )} + ; + } + return <>; } function LogPanel() { - enableConsoleLogShim(); - - return - Logger - - - {list(logs).show(log => )} - - - + enableConsoleLogShim(); + + return + Logger + + + {list(logs).show(log => )} + + + } export interface Task { - [Symbol.dispose]: () => void; - name: string; + [Symbol.dispose]: () => void; + name: string; } export type RequestTag = "api" | "static" | "ssr" | "query" | "mutation"; export type Log = { - type: "raw"; - message: string; - color?: string; + type: "raw"; + message: string; + color?: string; } | { - type: "request"; - method: HTTPMethod; - url: string; - responseCode: number; - tags: RequestTag[]; + type: "request"; + method: HTTPMethod; + url: string; + responseCode: number; + tags: RequestTag[]; } export const tasks: Store = useState([]); export const logs: Store = useState([]); export function addLog(log: Log) { - logs.set([...getImmediateValue(logs), log]); + logs.set([...getImmediateValue(logs), log]); } function enableConsoleLogShim() { - const formatArgs = (args: any[]) => { - return args.map(x => typeof x === "string" ? x : Bun.inspect(x)).join(" ") - } - - console.log = (...args: any[]) => { - const formatted = formatArgs(args); - addLog({ type: "raw", message: formatted }); - } - - console.error = (...args: any[]) => { - const formatted = formatArgs(args); - addLog({ type: "raw", message: formatted, color: colors.red[400] }); - } - - console.warn = (...args: any[]) => { - const formatted = formatArgs(args); - addLog({ type: "raw", message: formatted, color: colors.yellow[400] }); - } + const formatArgs = (args: any[]) => { + return args.map(x => typeof x === "string" ? x : Bun.inspect(x)).join(" ") + } + + console.log = (...args: any[]) => { + const formatted = formatArgs(args); + addLog({ type: "raw", message: formatted }); + } + + console.error = (...args: any[]) => { + const formatted = formatArgs(args); + addLog({ type: "raw", message: formatted, color: colors.red[400] }); + } + + console.warn = (...args: any[]) => { + const formatted = formatArgs(args); + addLog({ type: "raw", message: formatted, color: colors.yellow[400] }); + } } function LeftPanel() { - const uptime = useState("uptime"); - let starting = Date.now(); - - setInterval(() => { - const uptimeMs = Date.now() - starting; - let ms = uptimeMs; - let seconds = Math.floor(ms / 1000); - ms -= seconds * 1000; - let minutes = Math.floor(seconds / 60); - seconds -= minutes * 60; - let hours = Math.floor(minutes / 60); - minutes -= hours * 60; - let days = Math.floor(hours / 24); - hours -= days * 24; - - uptime.set( - `${days}d ${hours}h ${minutes}m ${seconds}s` - ); - }, 100); - - return - - - - • - - wormhole - - - {uptime} - - - - Tasks - - - {list(tasks).show((item) => ( - - ))} - - ; + const uptime = useState("uptime"); + let starting = Date.now(); + + setInterval(() => { + const uptimeMs = Date.now() - starting; + let ms = uptimeMs; + let seconds = Math.floor(ms / 1000); + ms -= seconds * 1000; + let minutes = Math.floor(seconds / 60); + seconds -= minutes * 60; + let hours = Math.floor(minutes / 60); + minutes -= hours * 60; + let days = Math.floor(hours / 24); + hours -= days * 24; + + uptime.set( + `${days}d ${hours}h ${minutes}m ${seconds}s` + ); + }, 100); + + return + + + + • + + wormhole + + + {uptime} + + + + Tasks + + + {list(tasks).show((item) => ( + + ))} + + ; } export function addTask(props: Omit): Task { - const task: Task = { - ...props, - [Symbol.dispose]: () => { - tasks.set(getImmediateValue(tasks).filter((t) => t !== task)); - }, - }; + const task: Task = { + ...props, + [Symbol.dispose]: () => { + tasks.set(getImmediateValue(tasks).filter((t) => t !== task)); + }, + }; - tasks.set([...getImmediateValue(tasks), task]); + tasks.set([...getImmediateValue(tasks), task]); - return task; + return task; } diff --git a/packages/wormhole/src/dev/dev-server.ts b/packages/wormhole/src/dev/dev-server.ts index eac2369..2566d90 100644 --- a/packages/wormhole/src/dev/dev-server.ts +++ b/packages/wormhole/src/dev/dev-server.ts @@ -3,158 +3,176 @@ import type { Project } from "~/state"; import { createHTMLRoot, ssr, printHTML, createCodegenStream, diffInto } from "@vortexjs/ssr"; import { DevAdapter, type DevAdapterResult } from "~/build/adapters/dev"; import { Build } from "~/build/build"; -import { addTask } from "~/cli/statusboard"; +import { addLog, addTask, type RequestTag } from "~/cli/statusboard"; import { join } from "node:path"; import { watch } from "node:fs"; +import type { HTTPMethod } from "~/shared/http-method"; export interface DevServer { - lifetime: Lifetime; - server: Bun.Server; - processRequest(request: Request): Promise; - rebuild(): Promise; - buildResult: Promise; - project: Project; + lifetime: Lifetime; + server: Bun.Server; + processRequest(request: Request, tags: RequestTag[]): Promise; + rebuild(): Promise; + buildResult: Promise; + project: Project; } export function DevServer(project: Project): DevServer { - const server = Bun.serve({ - port: 3141, - routes: { - "/*": async (req) => { - return new Response(); - } - }, - development: true - }); - - project.lt.onClosed(server.stop); - - const self: DevServer = { - lifetime: project.lt, - server, - processRequest: DevServer_processRequest, - rebuild: DevServer_rebuild, - buildResult: new Promise(() => { }), - project - } - - server.reload({ - routes: { - "/*": (req) => { - return self.processRequest(req); - } - } - }); - - const devServerTask = addTask({ - name: `Development server running @ ${server.url.toString()}` - }); - - project.lt.onClosed(devServerTask[Symbol.dispose]); - - self.rebuild(); - - // Watch sourcedir - const watcher = watch(join(project.projectDir, "src"), { recursive: true }); - - let isWaitingForRebuild = false; - - watcher.on("change", async (eventType, filename) => { - if (isWaitingForRebuild) return; - isWaitingForRebuild = true; - await self.buildResult; - isWaitingForRebuild = false; - self.rebuild(); - }); - - project.lt.onClosed(() => { - watcher.close(); - }); - - return self; + const server = Bun.serve({ + port: 3141, + routes: { + "/*": async (req) => { + return new Response(); + } + }, + development: true + }); + + project.lt.onClosed(server.stop); + + const self: DevServer = { + lifetime: project.lt, + server, + processRequest: DevServer_processRequest, + rebuild: DevServer_rebuild, + buildResult: new Promise(() => { }), + project + } + + server.reload({ + routes: { + "/*": async (req) => { + const tags: RequestTag[] = []; + const response = await self.processRequest(req, tags); + + addLog({ + type: "request", + url: new URL(req.url).pathname, + method: req.method as HTTPMethod, + responseCode: response.status, + tags + }) + + return response; + } + } + }); + + const devServerTask = addTask({ + name: `Development server running @ ${server.url.toString()}` + }); + + project.lt.onClosed(devServerTask[Symbol.dispose]); + + self.rebuild(); + + // Watch sourcedir + const watcher = watch(join(project.projectDir, "src"), { recursive: true }); + + let isWaitingForRebuild = false; + + watcher.on("change", async (eventType, filename) => { + if (isWaitingForRebuild) return; + isWaitingForRebuild = true; + await self.buildResult; + isWaitingForRebuild = false; + self.rebuild(); + }); + + project.lt.onClosed(() => { + watcher.close(); + }); + + return self; } async function DevServer_rebuild(this: DevServer): Promise { - const build = new Build(this.project, DevAdapter()); - this.buildResult = build.run(); - await this.buildResult; + const build = new Build(this.project, DevAdapter()); + this.buildResult = build.run(); + await this.buildResult; } interface ServerEntrypoint { - main(props: { - renderer: Renderer, - root: RendererNode, - pathname: string, - context: ContextScope, - lifetime: Lifetime - }): void; - tryHandleAPI(request: Request): Promise; + main(props: { + renderer: Renderer, + root: RendererNode, + pathname: string, + context: ContextScope, + lifetime: Lifetime + }): void; + tryHandleAPI(request: Request): Promise; + isRoute404(pathname: string): boolean; } -async function DevServer_processRequest(this: DevServer, request: Request): Promise { - const built = await this.buildResult; +async function DevServer_processRequest(this: DevServer, request: Request, tags: RequestTag[]): Promise { + const built = await this.buildResult; - const serverPath = built.serverEntry; - const serverEntrypoint = (await import(serverPath + `?v=${Date.now()}`)) as ServerEntrypoint; + const serverPath = built.serverEntry; + const serverEntrypoint = (await import(serverPath + `?v=${Date.now()}`)) as ServerEntrypoint; - // Priority 1: API routes - const apiResponse = await serverEntrypoint.tryHandleAPI(request); + // Priority 1: API routes + const apiResponse = await serverEntrypoint.tryHandleAPI(request); - if (apiResponse !== undefined && apiResponse !== null) { - return apiResponse; - } + if (apiResponse !== undefined && apiResponse !== null) { + tags.push("api"); + return apiResponse; + } - // Priority 2: Static files - const filePath = join(built.outdir, new URL(request.url).pathname); + // Priority 2: Static files + const filePath = join(built.outdir, new URL(request.url).pathname); - if (await Bun.file(filePath).exists()) { - return new Response(Bun.file(filePath)); - } + if (await Bun.file(filePath).exists()) { + tags.push("static"); + return new Response(Bun.file(filePath)); + } - // Priority 3: SSR - const root = createHTMLRoot(); - const renderer = ssr(); + // Priority 3: SSR + const root = createHTMLRoot(); + const renderer = ssr(); - const context = new ContextScope(); + const context = new ContextScope(); - const lifetime = new Lifetime(); + const lifetime = new Lifetime(); - serverEntrypoint.main({ - root, - renderer, - pathname: new URL(request.url).pathname, - context, - lifetime - }); + serverEntrypoint.main({ + root, + renderer, + pathname: new URL(request.url).pathname, + context, + lifetime + }); - const html = printHTML(root); + const html = printHTML(root); - const { readable, writable } = new TransformStream(); - const writer = writable.getWriter(); + const { readable, writable } = new TransformStream(); + const writer = writable.getWriter(); - writer.write(html); + writer.write(html); - let currentSnapshot = structuredClone(root); + let currentSnapshot = structuredClone(root); - context.streaming.onUpdate(() => { - const codegen = diffInto(currentSnapshot, root); + context.streaming.onUpdate(() => { + const codegen = diffInto(currentSnapshot, root); - const code = codegen.getCode(); + const code = codegen.getCode(); - currentSnapshot = structuredClone(root); + currentSnapshot = structuredClone(root); - writer.write(``); - }); + writer.write(``); + }); - await context.streaming.onDoneLoading; + await context.streaming.onDoneLoading; - writer.write(``); - writer.close(); - lifetime.close(); + writer.write(``); + writer.close(); + lifetime.close(); - return new Response(readable, { - headers: { - 'Content-Type': "text/html" - } - }) + tags.push("ssr"); + + return new Response(readable, { + status: serverEntrypoint.isRoute404(new URL(request.url).pathname) ? 404 : 200, + headers: { + 'Content-Type': "text/html" + } + }) } diff --git a/packages/wormhole/src/index.ts b/packages/wormhole/src/index.ts index e3ce6fd..ed5fa59 100644 --- a/packages/wormhole/src/index.ts +++ b/packages/wormhole/src/index.ts @@ -1 +1,2 @@ +export * from "./build/router"; export * from "./runtime"; diff --git a/packages/wormhole/src/runtime/entrypoint.tsx b/packages/wormhole/src/runtime/entrypoint.tsx index 0cdb5d7..732bd1c 100644 --- a/packages/wormhole/src/runtime/entrypoint.tsx +++ b/packages/wormhole/src/runtime/entrypoint.tsx @@ -1,112 +1,114 @@ import { unwrap } from "@vortexjs/common"; import { - useAwait, - ContextScope, - flatten, - type JSXNode, - Lifetime, - render, - type Renderer, - useDerived, - useState, - useStreaming, + useAwait, + ContextScope, + flatten, + type JSXNode, + Lifetime, + render, + type Renderer, + useDerived, + useState, + useStreaming, + when, } from "@vortexjs/core"; import { matchPath, type RoutePath } from "~/build/router"; import { initializeClientSideRouting, usePathname } from "~/runtime/csr"; export interface EntrypointImport { - index: number; + index: number; } export interface EntrypointRoute { - matcher: RoutePath; - frames: EntrypointImport[]; + matcher: RoutePath; + frames: EntrypointImport[]; + is404: boolean; } export interface EntrypointProps { - routes: EntrypointRoute[]; + routes: EntrypointRoute[]; } function App({ - pathname: pathnameToUse, - props, - loaders + pathname: pathnameToUse, + props, + loaders }: { - pathname?: string, - props: EntrypointProps - loaders: (() => Promise)[], + pathname?: string, + props: EntrypointProps + loaders: (() => Promise)[], }) { - if ("location" in globalThis) { - initializeClientSideRouting(); - } + if ("location" in globalThis) { + initializeClientSideRouting(); + } - useStreaming(); + useStreaming(); - const awaited = useAwait(); + const awaited = useAwait(); - const pathname = pathnameToUse ? useState(pathnameToUse) : usePathname(); - const route = useDerived((get) => { - const path = get(pathname); - return props.routes.find((r) => matchPath(r.matcher, path)); - }); - const framesPromise = useDerived(async (get) => { - const rot = unwrap(get(route)); - const frames = []; + const pathname = pathnameToUse ? useState(pathnameToUse) : usePathname(); + const route = useDerived((get) => { + const path = get(pathname); + return props.routes.find((r) => matchPath(r.matcher, path)); + }); + const framesPromise = useDerived(async (get) => { + const rot = unwrap(get(route)); + const frames = []; - for (const frame of rot.frames) { - frames.push(await unwrap(loaders[frame.index])()); - } + for (const frame of rot.frames) { + frames.push(await unwrap(loaders[frame.index])()); + } - return frames; - }); - const frames = flatten(useDerived((get) => { - return awaited(get(framesPromise)) - })); - const hierarchy = useDerived((get) => { - let node = <>; + return frames; + }); + const frames = flatten(useDerived((get) => { + return awaited(get(framesPromise)) + })); + const hierarchy = useDerived((get) => { + let node = <>; - const framesResolved = get(frames); + const framesResolved = get(frames); - if (!framesResolved) { - return

loading

- } + if (!framesResolved) { + return

loading

+ } - for (const Frame of framesResolved.toReversed()) { - node = - {node} - - } + for (const Frame of framesResolved.toReversed()) { + node = + {node} + + } - return node; - }) + return node; + }); - return - - - - - - {hierarchy} - - ; + return + + + + + + {hierarchy} + + ; } export function INTERNAL_entrypoint({ - props, - loaders, - renderer, - root, - pathname, - lifetime = new Lifetime(), - context + props, + loaders, + renderer, + root, + pathname, + lifetime = new Lifetime(), + context }: { - props: EntrypointProps, loaders: (() => Promise)[], renderer: Renderer, root: Root, pathname?: string, - lifetime?: Lifetime, context: ContextScope + props: EntrypointProps, loaders: (() => Promise)[], renderer: Renderer, root: Root, pathname?: string, + lifetime?: Lifetime, context: ContextScope }) { - render({ - context, - renderer, - root, - component: - }).cascadesFrom(lifetime); + render({ + context, + renderer, + root, + component: + }).cascadesFrom(lifetime); } diff --git a/packages/wormhole/src/virt/route.ts b/packages/wormhole/src/virt/route.ts index a067ba1..eb67910 100644 --- a/packages/wormhole/src/virt/route.ts +++ b/packages/wormhole/src/virt/route.ts @@ -10,6 +10,7 @@ export type RouteFunction = ( export type RouteParams = { page?: (props: PageProps) => void; layout?: (props: LayoutProps) => void; + notFound?: (props: LayoutProps) => void; }; export type APIFunction = ( From 8d3b93af939b96bef59a24cf758def54029c0b2c Mon Sep 17 00:00:00 2001 From: andylovescode Date: Mon, 18 Aug 2025 18:03:44 -0700 Subject: [PATCH 12/13] Wormhole: Preload routes before trying to render on client --- packages/wormhole/src/runtime/entrypoint.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/wormhole/src/runtime/entrypoint.tsx b/packages/wormhole/src/runtime/entrypoint.tsx index 732bd1c..77603f5 100644 --- a/packages/wormhole/src/runtime/entrypoint.tsx +++ b/packages/wormhole/src/runtime/entrypoint.tsx @@ -93,7 +93,7 @@ function App({ ; } -export function INTERNAL_entrypoint({ +export async function INTERNAL_entrypoint({ props, loaders, renderer, @@ -105,6 +105,17 @@ export function INTERNAL_entrypoint({ props: EntrypointProps, loaders: (() => Promise)[], renderer: Renderer, root: Root, pathname?: string, lifetime?: Lifetime, context: ContextScope }) { + preload: if ("window" in globalThis) { + // We need to preload the routes so we don't flash the 'loading' state + const path = pathname ?? window.location.pathname; + const route = props.routes.find((r) => matchPath(r.matcher, path)); + if (!route) break preload; + + for (const frame of route.frames) { + await unwrap(loaders[frame.index])(); + } + } + render({ context, renderer, From 7718ad3b4b006e983b407cfd624f12774714eed6 Mon Sep 17 00:00:00 2001 From: andylovescode Date: Mon, 18 Aug 2025 18:10:42 -0700 Subject: [PATCH 13/13] CLIv2: Buffer ANSI output On certain low-end devices like the Microsoft Surface, the overhead of sending text data to the kernel in small chunks made rendering extremely slow. This fixes that. --- packages/vortex-cli/src/render/ansi.ts | 103 +++--- packages/vortex-cli/src/render/index.ts | 465 ++++++++++++------------ 2 files changed, 288 insertions(+), 280 deletions(-) diff --git a/packages/vortex-cli/src/render/ansi.ts b/packages/vortex-cli/src/render/ansi.ts index 9cbec5b..a719911 100644 --- a/packages/vortex-cli/src/render/ansi.ts +++ b/packages/vortex-cli/src/render/ansi.ts @@ -1,52 +1,59 @@ import type { FileSink } from "bun"; export class ANSIWriter { - constructor(public terminal: FileSink) { } - - csi() { - this.terminal.write("\x1b["); - } - - write(text: string) { - this.terminal.write(text); - } - - moveTo(x: number, y: number) { - this.csi(); - this.terminal.write(`${y + 1};${x + 1}H`); - } - - setCursorVisible(visible: boolean) { - this.csi(); - this.terminal.write(visible ? "?25h" : "?25l"); - }; - - setBackground(color: string) { - const { r, g, b } = Bun.color(color, "{rgb}")!; - - this.csi(); - this.terminal.write(`48;2;${r};${g};${b}m`); - } - - setForeground(color: string) { - const { r, g, b } = Bun.color(color, "{rgb}")!; - - this.csi(); - this.terminal.write(`38;2;${r};${g};${b}m`); - } - - setItalic(italic: boolean) { - this.csi(); - this.terminal.write(italic ? "3m" : "23m"); - } - - setBold(bold: boolean) { - this.csi(); - this.terminal.write(bold ? "1m" : "22m"); - } - - setUnderline(underline: boolean) { - this.csi(); - this.terminal.write(underline ? "4m" : "24m"); - } + buffer = ""; + + constructor(public terminal: FileSink) { } + + csi() { + this.write("\x1b["); + } + + flush() { + this.terminal.write(this.buffer); + this.buffer = ""; + } + + write(text: string) { + this.buffer += text; + } + + moveTo(x: number, y: number) { + this.csi(); + this.write(`${y + 1};${x + 1}H`); + } + + setCursorVisible(visible: boolean) { + this.csi(); + this.write(visible ? "?25h" : "?25l"); + }; + + setBackground(color: string) { + const { r, g, b } = Bun.color(color, "{rgb}")!; + + this.csi(); + this.write(`48;2;${r};${g};${b}m`); + } + + setForeground(color: string) { + const { r, g, b } = Bun.color(color, "{rgb}")!; + + this.csi(); + this.write(`38;2;${r};${g};${b}m`); + } + + setItalic(italic: boolean) { + this.csi(); + this.write(italic ? "3m" : "23m"); + } + + setBold(bold: boolean) { + this.csi(); + this.write(bold ? "1m" : "22m"); + } + + setUnderline(underline: boolean) { + this.csi(); + this.write(underline ? "4m" : "24m"); + } } diff --git a/packages/vortex-cli/src/render/index.ts b/packages/vortex-cli/src/render/index.ts index f1aa7da..3e8085a 100644 --- a/packages/vortex-cli/src/render/index.ts +++ b/packages/vortex-cli/src/render/index.ts @@ -2,247 +2,248 @@ import symbols from "../tokens/symbols"; import { ANSIWriter } from "./ansi"; export interface Cell { - text: string; - background: string; - foreground: string; - bold: boolean; - italic: boolean; - underline: boolean; + text: string; + background: string; + foreground: string; + bold: boolean; + italic: boolean; + underline: boolean; } export type BoxStyle = "outline-round" | "outline-square" | "background-square" | "none"; export class Canvas { - offsetX = 0; - offsetY = 0; - - constructor(public width: number, public height: number) { - this.clipEndX = width; - this.clipEndY = height; - this.buffer = new Array(width * height).fill({ - text: " ", - background: "black", - foreground: "white", - bold: false, - italic: false, - underline: false - }).map(x => structuredClone(x)); - } - - put(x: number, y: number, cell: Cell) { - const rx = x + this.offsetX; - const ry = y + this.offsetY; - - if (rx < this.clipStartX || rx >= this.clipEndX || ry < - this.clipStartY || ry >= this.clipEndY) { - return; - } - - const index = ry * this.width + rx; - this.buffer[index]!.text = cell.text; - if (cell.background !== "transparent") { - this.buffer[index]!.background = cell.background; - } - this.buffer[index]!.bold = cell.bold; - this.buffer[index]!.italic = cell.italic; - this.buffer[index]!.underline = cell.underline; - this.buffer[index]!.foreground = cell.foreground; - } - - box(type: BoxStyle, x1: number, y1: number, x2: number, y2: number, color: string) { - if (type === "none") { - return; - } - - for (let y = y1; y <= y2; y++) { - for (let x = x1; x <= x2; x++) { - if (type === "background-square") { - this.put(x, y, { - text: " ", - background: color, - foreground: "white", - bold: false, - italic: false, - underline: false - }); - continue; - } - - const vertical = x === x1 || x === x2; - const horizontal = y === y1 || y === y2; - - const up = (y > y1) && vertical; - const down = (y < y2) && vertical; - const left = (x > x1) && horizontal; - const right = (x < x2) && horizontal; - const symbol = ({ - "outline-round": symbols.outline.rounded, - "outline-square": symbols.outline.square, - })[type]({ - up, - down, - left, - right, - }); - - if (!(up || down || left || right)) { - if (type === "outline-round" || type === "outline-square") { - continue; // Skip the inner area for outlines - } - - this.put(x, y, { - text: " ", - background: color, - foreground: "transparent", - bold: false, - italic: false, - underline: false, - }); - } else { - this.put(x, y, { - text: symbol, - background: "transparent", - foreground: color, - bold: false, - italic: false, - underline: false - }); - } - } - } - } - - clip(x1: number, y1: number, x2: number, y2: number) { - const prevClipStartX = this.clipStartX; - const prevClipStartY = this.clipStartY; - const prevClipEndX = this.clipEndX; - const prevClipEndY = this.clipEndY; - - this.clipStartX = Math.max(0, x1 + this.offsetX, this.clipStartX); - this.clipStartY = Math.max(0, y1 + this.offsetY, this.clipStartY); - this.clipEndX = Math.min(this.width, x2 + this.offsetX, this.clipEndX); - this.clipEndY = Math.min(this.height, y2 + this.offsetY, this.clipEndY); - - const self = this; - - return { - [Symbol.dispose]() { - self.clipStartX = prevClipStartX; - self.clipStartY = prevClipStartY; - self.clipEndX = prevClipEndX; - self.clipEndY = prevClipEndY; - } - } - } - - offset(x: number, y: number) { - this.offsetX += x; - this.offsetY += y; - - const self = this; - - return { - [Symbol.dispose]() { - self.offsetX -= x; - self.offsetY -= y; - } - } - } - - clipStartX = 0; - clipStartY = 0; - clipEndX = 0; - clipEndY = 0; - buffer: Cell[]; + offsetX = 0; + offsetY = 0; + + constructor(public width: number, public height: number) { + this.clipEndX = width; + this.clipEndY = height; + this.buffer = new Array(width * height).fill({ + text: " ", + background: "black", + foreground: "white", + bold: false, + italic: false, + underline: false + }).map(x => structuredClone(x)); + } + + put(x: number, y: number, cell: Cell) { + const rx = x + this.offsetX; + const ry = y + this.offsetY; + + if (rx < this.clipStartX || rx >= this.clipEndX || ry < + this.clipStartY || ry >= this.clipEndY) { + return; + } + + const index = ry * this.width + rx; + this.buffer[index]!.text = cell.text; + if (cell.background !== "transparent") { + this.buffer[index]!.background = cell.background; + } + this.buffer[index]!.bold = cell.bold; + this.buffer[index]!.italic = cell.italic; + this.buffer[index]!.underline = cell.underline; + this.buffer[index]!.foreground = cell.foreground; + } + + box(type: BoxStyle, x1: number, y1: number, x2: number, y2: number, color: string) { + if (type === "none") { + return; + } + + for (let y = y1; y <= y2; y++) { + for (let x = x1; x <= x2; x++) { + if (type === "background-square") { + this.put(x, y, { + text: " ", + background: color, + foreground: "white", + bold: false, + italic: false, + underline: false + }); + continue; + } + + const vertical = x === x1 || x === x2; + const horizontal = y === y1 || y === y2; + + const up = (y > y1) && vertical; + const down = (y < y2) && vertical; + const left = (x > x1) && horizontal; + const right = (x < x2) && horizontal; + const symbol = ({ + "outline-round": symbols.outline.rounded, + "outline-square": symbols.outline.square, + })[type]({ + up, + down, + left, + right, + }); + + if (!(up || down || left || right)) { + if (type === "outline-round" || type === "outline-square") { + continue; // Skip the inner area for outlines + } + + this.put(x, y, { + text: " ", + background: color, + foreground: "transparent", + bold: false, + italic: false, + underline: false, + }); + } else { + this.put(x, y, { + text: symbol, + background: "transparent", + foreground: color, + bold: false, + italic: false, + underline: false + }); + } + } + } + } + + clip(x1: number, y1: number, x2: number, y2: number) { + const prevClipStartX = this.clipStartX; + const prevClipStartY = this.clipStartY; + const prevClipEndX = this.clipEndX; + const prevClipEndY = this.clipEndY; + + this.clipStartX = Math.max(0, x1 + this.offsetX, this.clipStartX); + this.clipStartY = Math.max(0, y1 + this.offsetY, this.clipStartY); + this.clipEndX = Math.min(this.width, x2 + this.offsetX, this.clipEndX); + this.clipEndY = Math.min(this.height, y2 + this.offsetY, this.clipEndY); + + const self = this; + + return { + [Symbol.dispose]() { + self.clipStartX = prevClipStartX; + self.clipStartY = prevClipStartY; + self.clipEndX = prevClipEndX; + self.clipEndY = prevClipEndY; + } + } + } + + offset(x: number, y: number) { + this.offsetX += x; + this.offsetY += y; + + const self = this; + + return { + [Symbol.dispose]() { + self.offsetX -= x; + self.offsetY -= y; + } + } + } + + clipStartX = 0; + clipStartY = 0; + clipEndX = 0; + clipEndY = 0; + buffer: Cell[]; } function getCellKey(cell: Cell) { - return `${cell.text}-${cell.background}-${cell.foreground}`; + return `${cell.text}-${cell.background}-${cell.foreground}`; } export class Renderer { - currentCells: string[] = []; - currentWidth = 0; - currentHeight = 0; - ansi = new ANSIWriter(Bun.stdout.writer()); - currentBGColor = ""; - currentFGColor = ""; - currentX = 0; - currentY = 0; - currentBold: boolean | undefined = undefined; - currentUnderline: boolean | undefined = undefined; - currentItalic: boolean | undefined = undefined; - - setBGColor(color: string) { - if (this.currentBGColor === color) return; - this.currentBGColor = color; - this.ansi.setBackground(color); - } - - setFGColor(color: string) { - if (this.currentFGColor === color) return; - this.currentFGColor = color; - this.ansi.setForeground(color); - } - - setPosition(x: number, y: number) { - if (this.currentX === x && this.currentY === y) return; - this.currentX = x; - this.currentY = y; - this.ansi.moveTo(x, y); - } - - setBold(bold: boolean) { - if (this.currentBold === bold) return; - this.currentBold = bold; - this.ansi.setBold(bold); - } - - setItalic(italic: boolean) { - if (this.currentItalic === italic) return; - this.currentItalic = italic; - this.ansi.setItalic(italic); - } - - setUnderline(underline: boolean) { - if (this.currentUnderline === underline) return; - this.currentUnderline = underline; - this.ansi.setUnderline(underline); - } - - render(canvas: Canvas) { - if (this.currentWidth !== canvas.width || this.currentHeight !== canvas.height) { - this.currentCells = []; - } - - this.currentX = -1; - this.currentY = -1; - this.currentBold = undefined; - this.currentUnderline = undefined; - this.currentItalic = undefined; - - this.currentWidth = canvas.width; - this.currentHeight = canvas.height; - - for (let y = 0; y < canvas.height; y++) { - for (let x = 0; x < canvas.width; x++) { - const cell = canvas.buffer[y * canvas.width + x]!; - const key = getCellKey(cell); - - if (this.currentCells[y * canvas.width + x] !== key) { - this.setPosition(x, y); - this.setBGColor(cell.background); - this.setFGColor(cell.foreground); - this.setItalic(cell.italic); - this.setBold(cell.bold); - this.setUnderline(cell.underline); - this.ansi.write(cell.text); - this.currentX++; - this.currentCells[y * canvas.width + x] = key; - } - } - } - - this.ansi.setCursorVisible(false); - } + currentCells: string[] = []; + currentWidth = 0; + currentHeight = 0; + ansi = new ANSIWriter(Bun.stdout.writer()); + currentBGColor = ""; + currentFGColor = ""; + currentX = 0; + currentY = 0; + currentBold: boolean | undefined = undefined; + currentUnderline: boolean | undefined = undefined; + currentItalic: boolean | undefined = undefined; + + setBGColor(color: string) { + if (this.currentBGColor === color) return; + this.currentBGColor = color; + this.ansi.setBackground(color); + } + + setFGColor(color: string) { + if (this.currentFGColor === color) return; + this.currentFGColor = color; + this.ansi.setForeground(color); + } + + setPosition(x: number, y: number) { + if (this.currentX === x && this.currentY === y) return; + this.currentX = x; + this.currentY = y; + this.ansi.moveTo(x, y); + } + + setBold(bold: boolean) { + if (this.currentBold === bold) return; + this.currentBold = bold; + this.ansi.setBold(bold); + } + + setItalic(italic: boolean) { + if (this.currentItalic === italic) return; + this.currentItalic = italic; + this.ansi.setItalic(italic); + } + + setUnderline(underline: boolean) { + if (this.currentUnderline === underline) return; + this.currentUnderline = underline; + this.ansi.setUnderline(underline); + } + + render(canvas: Canvas) { + if (this.currentWidth !== canvas.width || this.currentHeight !== canvas.height) { + this.currentCells = []; + } + + this.currentX = -1; + this.currentY = -1; + this.currentBold = undefined; + this.currentUnderline = undefined; + this.currentItalic = undefined; + + this.currentWidth = canvas.width; + this.currentHeight = canvas.height; + + for (let y = 0; y < canvas.height; y++) { + for (let x = 0; x < canvas.width; x++) { + const cell = canvas.buffer[y * canvas.width + x]!; + const key = getCellKey(cell); + + if (this.currentCells[y * canvas.width + x] !== key) { + this.setPosition(x, y); + this.setBGColor(cell.background); + this.setFGColor(cell.foreground); + this.setItalic(cell.italic); + this.setBold(cell.bold); + this.setUnderline(cell.underline); + this.ansi.write(cell.text); + this.currentX++; + this.currentCells[y * canvas.width + x] = key; + } + } + } + + this.ansi.setCursorVisible(false); + this.ansi.flush(); + } }