a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals

Compare changes

Choose any two refs to compare.

+126
.github/ISSUE_TEMPLATE/bug_report.yml
··· 1 + name: Bug Report 2 + description: Report a bug or unexpected behavior in VoltX.js 3 + title: "[Bug]: " 4 + labels: ["bug", "needs-triage"] 5 + body: 6 + - type: markdown 7 + attributes: 8 + value: | 9 + Thanks for taking the time to report this bug! Please provide as much detail as possible to help us reproduce and fix the issue. 10 + 11 + - type: textarea 12 + id: description 13 + attributes: 14 + label: Bug Description 15 + description: A clear and concise description of what the bug is 16 + placeholder: Describe the bug... 17 + validations: 18 + required: true 19 + 20 + - type: textarea 21 + id: reproduction 22 + attributes: 23 + label: Steps to Reproduce 24 + description: Detailed steps to reproduce the behavior 25 + placeholder: | 26 + 1. Mount a component with... 27 + 2. Click on... 28 + 3. Observe... 29 + validations: 30 + required: true 31 + 32 + - type: textarea 33 + id: expected 34 + attributes: 35 + label: Expected Behavior 36 + description: What you expected to happen 37 + placeholder: Describe what should happen... 38 + validations: 39 + required: true 40 + 41 + - type: textarea 42 + id: actual 43 + attributes: 44 + label: Actual Behavior 45 + description: What actually happened 46 + placeholder: Describe what actually happens... 47 + validations: 48 + required: true 49 + 50 + - type: dropdown 51 + id: mode 52 + attributes: 53 + label: Usage Mode 54 + description: Which mode are you using? 55 + options: 56 + - Declarative (HTML attributes with charge()) 57 + - Programmatic (JavaScript with mount()) 58 + - Both 59 + - Not sure 60 + validations: 61 + required: true 62 + 63 + - type: textarea 64 + id: code 65 + attributes: 66 + label: Minimal Reproduction 67 + description: Minimal code example that reproduces the issue 68 + placeholder: | 69 + <!-- Your HTML and JavaScript here --> 70 + render: html 71 + validations: 72 + required: true 73 + 74 + - type: input 75 + id: version 76 + attributes: 77 + label: VoltX Version 78 + description: Which version of VoltX are you using? 79 + placeholder: e.g., 0.5.1 80 + validations: 81 + required: true 82 + 83 + - type: dropdown 84 + id: browser 85 + attributes: 86 + label: Browser 87 + description: Which browser(s) does this occur in? 88 + multiple: true 89 + options: 90 + - Chrome 91 + - Firefox 92 + - Safari 93 + - Edge 94 + - Other 95 + validations: 96 + required: true 97 + 98 + - type: textarea 99 + id: environment 100 + attributes: 101 + label: Environment Details 102 + description: Additional environment information (OS, Node version if relevant, etc.) 103 + placeholder: | 104 + - OS: macOS 14.0 105 + - Node: v20.0.0 106 + - Package manager: pnpm 8.0.0 107 + 108 + - type: textarea 109 + id: additional 110 + attributes: 111 + label: Additional Context 112 + description: Any other context, screenshots, or information about the problem 113 + placeholder: Add any other context here... 114 + 115 + - type: checkboxes 116 + id: confirmations 117 + attributes: 118 + label: Confirmations 119 + description: Please confirm the following 120 + options: 121 + - label: I have searched existing issues to ensure this bug hasn't been reported 122 + required: true 123 + - label: I have tested with the latest version of VoltX 124 + required: true 125 + - label: I have provided a minimal reproduction of the issue 126 + required: true
+11
.github/ISSUE_TEMPLATE/config.yml
··· 1 + blank_issues_enabled: false 2 + contact_links: 3 + - name: Question or Discussion 4 + url: https://github.com/stormlightlabs/volt/discussions 5 + about: Ask questions, share ideas, or discuss VoltX.js with the community 6 + - name: Documentation 7 + url: https://stormlightlabs.github.io/volt 8 + about: Read the official VoltX.js documentation 9 + - name: Security Vulnerability 10 + url: https://github.com/stormlightlabs/volt/security/advisories/new 11 + about: Report security vulnerabilities privately
+92
.github/ISSUE_TEMPLATE/documentation.yml
··· 1 + name: Documentation Improvement 2 + description: Report issues or suggest improvements to VoltX.js documentation 3 + title: "[Docs]: " 4 + labels: ["documentation"] 5 + body: 6 + - type: markdown 7 + attributes: 8 + value: | 9 + Thanks for helping improve VoltX documentation! Clear docs are essential for a great developer experience. 10 + 11 + - type: dropdown 12 + id: doc-type 13 + attributes: 14 + label: Documentation Type 15 + description: What type of documentation needs improvement? 16 + options: 17 + - API documentation 18 + - Guide or tutorial 19 + - Examples 20 + - README 21 + - Code comments 22 + - Error messages 23 + - Migration guide 24 + - Other 25 + validations: 26 + required: true 27 + 28 + - type: dropdown 29 + id: issue-type 30 + attributes: 31 + label: Issue Type 32 + description: What kind of issue is this? 33 + options: 34 + - Missing documentation 35 + - Incorrect or outdated information 36 + - Unclear or confusing explanation 37 + - Broken link or example 38 + - Typo or formatting issue 39 + - Missing code example 40 + - Other 41 + validations: 42 + required: true 43 + 44 + - type: input 45 + id: location 46 + attributes: 47 + label: Documentation Location 48 + description: Link or path to the documentation that needs improvement 49 + placeholder: e.g., https://voltx.dev/guide/reactivity or docs/guide/reactivity.md 50 + validations: 51 + required: true 52 + 53 + - type: textarea 54 + id: problem 55 + attributes: 56 + label: What's Wrong or Missing? 57 + description: Describe the current state of the documentation and what's problematic 58 + placeholder: | 59 + Current state: The documentation says... 60 + Problem: This is confusing because... 61 + validations: 62 + required: true 63 + 64 + - type: textarea 65 + id: suggestion 66 + attributes: 67 + label: Suggested Improvement 68 + description: How would you improve this documentation? 69 + placeholder: | 70 + Proposed change: Add an example showing... 71 + Or: Clarify that... 72 + 73 + - type: textarea 74 + id: context 75 + attributes: 76 + label: Additional Context 77 + description: Why is this important? Who would benefit from this improvement? 78 + placeholder: | 79 + - This affects beginners trying to... 80 + - This is a common question in discussions 81 + - This would help with migration from... 82 + 83 + - type: checkboxes 84 + id: confirmations 85 + attributes: 86 + label: Confirmations 87 + description: Please confirm the following 88 + options: 89 + - label: I have checked that this documentation issue hasn't already been reported 90 + required: true 91 + - label: I have reviewed the current documentation thoroughly 92 + required: true
+129
.github/ISSUE_TEMPLATE/feature_request.yml
··· 1 + name: Feature Request 2 + description: Suggest a new feature or enhancement for VoltX.js 3 + title: "[Feature]: " 4 + labels: ["enhancement", "needs-triage"] 5 + body: 6 + - type: markdown 7 + attributes: 8 + value: | 9 + Thanks for suggesting a new feature! Please provide detailed information to help us understand and evaluate your proposal. 10 + 11 + - type: textarea 12 + id: problem 13 + attributes: 14 + label: Problem Statement 15 + description: Describe the problem or use case this feature would solve 16 + placeholder: | 17 + What problem are you trying to solve? What use case would this enable? 18 + Example: "As a developer, I need to..." 19 + validations: 20 + required: true 21 + 22 + - type: textarea 23 + id: solution 24 + attributes: 25 + label: Proposed Solution 26 + description: Describe your proposed solution or feature 27 + placeholder: | 28 + How would you like this to work? What API or attributes would you use? 29 + Be specific about the implementation approach if possible. 30 + validations: 31 + required: true 32 + 33 + - type: textarea 34 + id: alternatives 35 + attributes: 36 + label: Alternatives Considered 37 + description: What alternative solutions or workarounds have you considered? 38 + placeholder: | 39 + - Alternative 1: ... 40 + - Alternative 2: ... 41 + - Current workaround: ... 42 + 43 + - type: dropdown 44 + id: scope 45 + attributes: 46 + label: Feature Scope 47 + description: What part of VoltX would this feature affect? 48 + options: 49 + - Core reactivity (signals, computed, effects) 50 + - Binding system (data-volt-* attributes) 51 + - Expression evaluator 52 + - Plugin system 53 + - HTTP bindings 54 + - Lifecycle hooks 55 + - SSR/Hydration 56 + - Documentation/Examples 57 + - Developer tooling 58 + - Other 59 + validations: 60 + required: true 61 + 62 + - type: dropdown 63 + id: priority 64 + attributes: 65 + label: Priority/Impact 66 + description: How important is this feature to your workflow? 67 + options: 68 + - Critical - Blocking my adoption of VoltX 69 + - High - Would significantly improve my development experience 70 + - Medium - Nice to have, but not urgent 71 + - Low - Minor improvement 72 + validations: 73 + required: true 74 + 75 + - type: textarea 76 + id: size-impact 77 + attributes: 78 + label: Size Constraint Consideration 79 + description: How might this feature impact the 15 KB gzipped constraint? 80 + placeholder: | 81 + Consider: 82 + - Could this be implemented as a plugin instead of core? 83 + - Does this replace existing functionality? 84 + - What's the estimated size impact? 85 + validations: 86 + required: true 87 + 88 + - type: dropdown 89 + id: mode 90 + attributes: 91 + label: Usage Mode 92 + description: Which mode would primarily benefit from this feature? 93 + options: 94 + - Declarative (HTML attributes) 95 + - Programmatic (JavaScript API) 96 + - Both 97 + - Plugin 98 + validations: 99 + required: true 100 + 101 + - type: textarea 102 + id: example 103 + attributes: 104 + label: Example Usage 105 + description: Show how you'd like to use this feature 106 + placeholder: | 107 + <!-- Example HTML or JavaScript --> 108 + render: html 109 + 110 + - type: textarea 111 + id: additional 112 + attributes: 113 + label: Additional Context 114 + description: Any other context, mockups, or references 115 + placeholder: | 116 + - Links to similar features in other frameworks 117 + - Mockups or diagrams 118 + - Related issues or discussions 119 + 120 + - type: checkboxes 121 + id: confirmations 122 + attributes: 123 + label: Confirmations 124 + description: Please confirm the following 125 + options: 126 + - label: I have searched existing issues to ensure this hasn't been requested 127 + required: true 128 + - label: I have considered whether this could be implemented as a plugin 129 + required: true
+7 -46
README.md
··· 11 11 12 12 Volt is a monorepo centered around the VoltX.js runtimeโ€”a lightweight, declarative alternative to component-centric UI frameworks. The repo also ships the Volt CLI and the documentation site that demonstrates and explains the runtime. 13 13 14 - ## Philosophy/Goals 15 - 16 - - Behavior is declared via `data-volt-*` attributes. 17 - - HTML drives the UI, not components. 18 - - Core under **20 KB gzipped**, zero dependencies. 19 - - Signals update the DOM directly without a virtual DOM. 20 - - Native Server-Sent Events (SSE) and WebSocket patch updates. 21 - - No reactivity scheduler, no VDOM diffing. 22 - - Extend behavior declaratively (persist, scroll, animate, etc.). 23 - - Progressive enhancement, i.e. works with static HTML out of the box. 14 + ## Local Development 24 15 25 - ### Values 26 - 27 - - Never exceed 15 KB for the core runtime. 28 - - No custom build systems โ€” work with any backend. 29 - - All source in TypeScript, no magical DSLs. 30 - - Every feature ships with a test harness. 31 - 32 - ## Concepts 33 - 34 - | Concept | Description | 35 - | -------- | ------------------------------------------------------------------------------------------------- | 36 - | Signals | Reactive primitives that automatically update DOM bindings when changed. | 37 - | Bindings | `data-volt-text`, `data-volt-html`, `data-volt-class` connect attributes or text to expressions. | 38 - | Actions | `data-volt-on-click`, `data-volt-on-input`, etc. attach event handlers declaratively. | 39 - | Streams | `data-volt-stream="/events"` listens for SSE or WebSocket updates and applies JSON patches. | 40 - | Plugins | Modular extensions (`data-volt-persist`, `data-volt-surge`, `data-volt-shift`, etc.) to enhance the core. | 41 - 42 - ## VoltX.css 43 - 44 - VoltX ships with an optional classless CSS framework inspired by Pico CSS and Tufte CSS. It provides beautiful, semantic styling for HTML elements without requiring any CSS classesโ€”just write semantic markup and it looks great out of the box. 45 - 46 - Features include typography with modular scale, Tufte-style sidenotes, styled form elements, dialogs, accordions, tooltips, tables, and more. See the framework's [README](./lib/README.md#voltxcss) for installation and usage details. 47 - 48 - Here are some highlights 49 - 50 - ![VoltX Typography](./docs/images/voltx-css_typography.png) 51 - 52 - ![VoltX Structured Content](./docs/images/voltx-css_structured-content.png) 53 - 54 - ![VoltX Components](./docs/images/voltx-css_components.png) 55 - 56 - ## Packages 16 + ### Packages 57 17 58 18 ```sh 59 19 volt/ 60 20 โ”œโ”€โ”€ lib/ VoltX.js runtime published to npm (`voltx.js`) and JSR (`@voltx/core`) 61 - โ”œโ”€โ”€ dev/ Volt CLI and local tooling 21 + โ”œโ”€โ”€ dev/ VoltX dev CLI and local tooling 22 + โ”œโ”€โ”€ cli/ Project scaffolding and management CLI (`create-voltx`) 62 23 โ””โ”€โ”€ docs/ VitePress documentation site 63 24 ``` 64 25 65 - ## Getting Started 26 + ### Getting Started 66 27 67 28 - Runtime usage: see [`lib/README.md`](./lib/README.md) for installation guides and quick-start examples. 68 29 - Local development: `pnpm install` then `pnpm --filter lib dev` run package-specific scripts (`build`, `test`, etc.). 69 30 - Review [contribution](./CONTRIBUTING.md) guidelines 70 - - Documentation: `pnpm --filter docs docs:dev` launches the VitePress site. 31 + - Documentation: `pnpm docs:dev` launches the VitePress site. 71 32 72 33 ### Working on New Features 73 34 ··· 94 55 95 56 ## License 96 57 97 - MIT License ยฉ 2025 Stormlight Labs 58 + [MIT License](./lib/LICENSE) ยฉ 2025 Stormlight Labs
+151 -157
ROADMAP.md
··· 3 3 - [Completed](#completed) 4 4 - [TODO](#to-do) 5 5 - [Parking Lot](#parking-lot) 6 - - [Examples (Planned)](#examples) 7 6 8 7 | Version | State | Milestone | 9 8 | ------- | ----- | -------------------------------------------------------------------------------- | ··· 19 18 | v0.4.0 | โœ“ | [Animation & Transitions](#animation--transitions) | 20 19 | v0.5.0 | โœ“ | [Navigation & History API Routing](#navigation--history-api-routing) | 21 20 | | โœ“ | [Refactor](#evaluator--binder-hardening) | 22 - | v0.5.1 | โœ“ | [Error Handling & Diagnostics](#error-handling--diagnostics) | 23 - | v0.5.2 | | | 24 - | v0.5.3 | | | 25 - | v0.5.4 | | | 26 - | v0.6.1 | | [Persistence & Offline](#persistence--offline) | 27 - | v0.6.2 | | | 28 - | v0.6.3 | | | 29 - | v0.6.4 | | [Background Requests & Reactive Polling](#background-requests--reactive-polling) | 30 - | v0.6.5 | | | 31 - | v0.6.6 | | | 32 - | v0.6.7 | | [Streaming & Patch Engine](#streaming--patch-engine) | 33 - | v0.6.8 | | | 34 - | v0.6.9 | | | 35 - | v0.6.10 | | | 36 - | v0.7.0 | | | 37 - | v0.8.0 | | Support `voltx-` & `vx-` attributes: recommend `vx-` | 38 - | | | Switch to `data-voltx` | 39 - | | | Update demo to be a multi page application with routing plugin | 40 - | v0.9.0 | | [Inspector & Developer Tools](#inspector--developer-tools) | 21 + | v0.5.1 | โœ“ | [Error Handling & Diagnostics](#error-handling--diagnostics) (partial) | 22 + | v0.6.0 | | [Error Handling & Diagnostics](#error-handling--diagnostics) | 23 + | v0.6.0 | | [Bundle Size Optimization](#bundle-size-optimization) | 24 + | v0.6.0 | | [IIFE Build Support](#iife-build-support) | 25 + | v0.7.0 | | [Testing & Benchmarking](#testing--benchmarking) | 26 + | v0.7.0 | | [CSP Compatibility](#csp-compatibility) | 27 + | v0.8.0 | | [DOM Morphing & Streaming](#dom-morphing--streaming) | 28 + | v0.9.0 | | [Scope Inheritance & State Management](#scope-inheritance--state-management) | 29 + | v0.10.0 | | [Background Requests & Reactive Polling](#background-requests--reactive-polling) | 30 + | v0.11.0 | | [Attribute Prefix Support](#attribute-prefix-support) | 31 + | v0.12.0 | | [Persistence & Offline](#persistence--offline) (advanced features) | 32 + | v0.13.0 | | [Inspector & Developer Tools](#inspector--developer-tools) | 41 33 | v1.0.0 | | [Stable Release](#stable-release) | 42 34 43 35 ## Completed ··· 112 104 113 105 ## To-Do 114 106 107 + **Focus:** Versions v0.5.5 through v0.9.x prioritize core framework capabilities and performance: 108 + 109 + - Bundle size reduction to <15KB gzipped (currently 19KB) 110 + - CSP compatibility (removing Function constructor dependency) 111 + - DOM morphing and SSE streaming support 112 + - Optional scope inheritance for improved ergonomics 113 + 114 + These milestones strengthen VoltX.js as a signals-based reactive framework with declarative-first design. 115 + 115 116 ### Error Handling & Diagnostics 116 117 117 118 **Goal**: Provide clear, actionable feedback when runtime or directive errors occur. 118 119 **Outcome**: VoltX.js surfaces developer-friendly diagnostics for expression evaluation, 119 120 directive parsing, and network operations, making it easier to debug apps without opaque stack traces. 120 121 **Deliverables**: 121 - - v0.5.1 122 - โœ“ Centralized error boundary system for directives and effects. 123 - โœ“ Sandbox error wrapping with contextual hints (directive name, expression, element). 124 - โœ“ `$volt.report(error, context)` API for plugin and app-level reporting. 125 - - v0.5.2 126 - - Visual in-DOM error overlays for development mode. 127 - - Enhanced console messages with source map trace and directive path. 128 - - Differentiated error levels: warn, error, fatal. 129 - - v0.5.3 130 - - Runtime health monitor tracking evaluation and subscription failures. 131 - - v0.5.4 132 - - Documentation: "Understanding VoltX Errors" guide. 133 - - Configurable global error policy (silent, overlay, throw). 134 - 135 - ### Streaming & Patch Engine 136 - 137 - **Goal:** Enable real-time updates via SSE/WebSocket streaming with intelligent DOM patching. 138 - **Outcome:** VoltX.js can receive and apply live updates from the server 139 - **Deliverables:** 140 - - v0.5.7 141 - - Server-Sent Events (SSE) integration 142 - - `data-volt-flow` attribute for SSE endpoints 143 - - v0.5.8 144 - - Signal patching from backend (`data-signals-*` merge system) 145 - - Backend action system with `$$spark()` syntax 146 - - v0.5.9 147 - - JSON Patch parser and DOM morphing engine 148 - - `data-volt-ignore-morph` for selective patch exclusion 149 - - v0.5.10 150 - - WebSocket as alternative to SSE 122 + - โœ“ v0.5.1: Centralized error boundary system for directives and effects 123 + - โœ“ v0.5.1: Sandbox error wrapping with contextual hints (directive name, expression, element) 124 + - โœ“ v0.5.1: `$volt.report(error, context)` API for plugin and app-level reporting 125 + - โœ“ v0.6.0: Enhanced console error messages with directive context 126 + - โœ“ v0.6.0: Differentiated error levels: warn, error, fatal 127 + - โœ“ v0.6.0: Documentation: "Understanding VoltX Errors" guide 128 + - v0.6.0: Add error handling examples to demo 129 + - v0.13.0: Visual in-DOM error overlays for development mode 130 + - v0.13.0: Runtime health monitor tracking failures 131 + - v0.13.0: Configurable global error policy 151 132 152 133 ### Persistence & Offline 153 134 ··· 156 137 **Deliverables:** 157 138 - โœ“ Persistent signals (localStorage, sessionStorage, indexedDb) 158 139 - โœ“ Storage plugin (`data-volt-persist`) 159 - - v0.5.1 160 - - Storage modifiers on signals: 161 - - `.local` modifier for localStorage persistence 162 - - `.session` modifier for sessionStorage persistence 163 - - `.ifmissing` modifier for conditional initialization 164 - - v0.5.2 165 - - Sync strategy API (merge, overwrite, patch) for conflict resolution 166 - - Cache invalidation strategies 167 - - v0.5.3 168 - - Offline queue for deferred stream events and HTTP requests 169 - - Service Worker integration for offline-first apps 170 - - Background sync for deferred requests 171 - - Cross-tab synchronization via `BroadcastChannel` 140 + - v0.12.0: Storage modifiers on signals (`.local`, `.session`, `.ifmissing`) 141 + - v0.12.0: Sync strategy API (merge, overwrite, patch) for conflict resolution 142 + - v0.12.0: Cache invalidation strategies 143 + - v0.12.0: Offline queue for deferred stream events and HTTP requests 144 + - v0.12.0: Service Worker integration for offline-first apps 145 + - v0.12.0: Background sync for deferred requests 146 + - v0.12.0: Cross-tab synchronization via `BroadcastChannel` 147 + 148 + ### Bundle Size Optimization 149 + 150 + **Goal:** Reduce bundle size to <15KB gzipped while maintaining full feature set. 151 + **Outcome:** Lightweight runtime footprint with comprehensive declarative capabilities. 152 + **Deliverables:** 153 + - v0.6.0: Audit and tree-shake unused code paths 154 + - v0.6.0: Optimize evaluator and binder implementations 155 + - v0.6.0: Minimize plugin footprint, ensure lazy loading 156 + - v0.6.0: Refactor expression compiler for smaller output 157 + - v0.6.0: Compress constant strings and reduce runtime helpers 158 + - v0.6.0: Optimize signal subscription management 159 + - v0.6.0: Production mode stripping (remove dev-only error messages) 160 + - v0.6.0: Aggressive minification pipeline tuning 161 + - v0.6.0: Target: <15KB gzipped sustained 162 + 163 + ### IIFE Build Support 164 + 165 + **Goal:** Provide an IIFE build target for VoltX.js to support direct `<script>` tag usage without module systems. 166 + **Outcome:** VoltX.js can be used via CDN without build tools or module bundlers. 167 + **Deliverables:** 168 + - v0.6.0: IIFE build output (voltx.iife.js) alongside ESM build 169 + - v0.6.0: Global `Volt` namespace for browser environments 170 + - v0.6.0: CDN-friendly distribution (unpkg, jsdelivr) 171 + - v0.6.0: Update build pipeline to generate IIFE bundle 172 + - v0.6.0: Document usage: `<script src="voltx.iife.min.js"></script>` 173 + - v0.6.0: Ensure plugins work with IIFE build 174 + - v0.6.0: Add IIFE examples to documentation 175 + 176 + ### Testing & Benchmarking 177 + 178 + **Goal:** Establish comprehensive testing infrastructure and performance benchmarking. 179 + **Outcome:** VoltX.js has rigorous end-to-end testing and quantifiable performance metrics against competing frameworks. 180 + **Deliverables:** 181 + - v0.7.0: Playwright-based integration test suite for real browser testing 182 + - v0.7.0: End-to-end tests for all core directives and plugins 183 + - v0.7.0: Cross-browser compatibility tests (Chrome, Firefox, Safari) 184 + - v0.7.0: Memory usage and leak detection benchmarks 185 + - v0.7.0: Bundle size tracking and regression detection 186 + - v0.7.0: Reactivity performance benchmarks (signal updates, computed chains, effect execution) 187 + - v0.7.0: DOM update performance benchmarks 188 + - v0.7.0: CI integration for automated benchmark runs and regression alerts 189 + 190 + ### CSP Compatibility 191 + 192 + **Goal:** Make VoltX.js Content Security Policy compliant without 'unsafe-eval'. 193 + **Outcome:** VoltX.js can run in strict CSP environments (no Function constructor). 194 + **Deliverables:** 195 + - v0.7.0: Research and design CSP-safe evaluator architecture 196 + - v0.7.0: Evaluate trade-offs: AST interpreter vs limited expression subset 197 + - v0.7.0: Implement CSP-safe expression evaluator (AST-based or restricted syntax) 198 + - v0.7.0: Maintain expression feature parity where possible 199 + - v0.7.0: Fallback mode detection for environments requiring CSP 200 + - v0.7.0: Full test coverage for CSP mode 201 + - v0.7.0: Documentation on CSP limitations and alternatives 202 + - v0.7.0: Bundle split: standard build vs CSP build 203 + 204 + ### DOM Morphing & Streaming 205 + 206 + **Goal:** Add intelligent DOM morphing and Server-Sent Events for real-time updates. 207 + **Outcome:** Built-in morphing and SSE streaming for seamless server-driven UI updates. 208 + **Deliverables:** 209 + - v0.8.0: Integrate Idiomorph or implement lightweight morphing algorithm 210 + - v0.8.0: `data-volt-morph` attribute for morphing-based swaps 211 + - v0.8.0: Preserve focus, scroll, and input state during morphs 212 + - v0.8.0: Server-Sent Events (SSE) integration 213 + - v0.8.0: `data-volt-stream` attribute for SSE endpoints 214 + - v0.8.0: Automatic reconnection with exponential backoff 215 + - v0.8.0: Signal patching from backend SSE events 216 + - v0.8.0: JSON Patch support for partial updates 217 + - v0.8.0: `data-volt-ignore-morph` for selective exclusion 218 + - v0.8.0: WebSocket as alternative to SSE 219 + - v0.8.0: Unified streaming API across SSE/WebSocket 220 + 221 + ### Scope Inheritance & State Management 222 + 223 + **Goal:** Improve data scoping with optional inheritance for ergonomic nested components. 224 + **Outcome:** Flexible scoping patterns for complex component hierarchies. 225 + **Deliverables:** 226 + - v0.9.0: Optional scope inheritance via `data-volt-scope="inherit"` 227 + - v0.9.0: Child scopes inherit parent signals with override capability 228 + - v0.9.0: $parent accessor for explicit parent scope access 229 + - v0.9.0: Scoped context providers for dependency injection 230 + - v0.9.0: Enhanced $store with namespacing and modules 231 + - v0.9.0: Cross-scope signal sharing patterns 172 232 173 233 ### Background Requests & Reactive Polling 174 234 175 - **Goal:** Enable declarative background data fetching and periodic updates within the VoltX.js runtime. 235 + **Goal:** Enable declarative background data fetching and periodic updates. 176 236 **Outcome:** VoltX.js elements can fetch or refresh data automatically based on time, visibility, or reactive conditions. 177 237 **Deliverables:** 178 - - v0.5.4 179 - - `data-volt-visible` for fetching when an element enters the viewport (`IntersectionObserver`) 180 - - v0.5.5 181 - - `data-volt-fetch` attribute for declarative background requests 182 - - Configurable polling intervals, delays, and signal-based triggers 183 - - Automatic cancellation of requests when elements are unmounted 184 - - Conditional execution tied to reactive signals 185 - - Integration hooks for loading and pending states 186 - - v0.5.6 187 - - Background task scheduler with priority management 238 + - v0.10.0: `data-volt-visible` for fetching when element enters viewport (IntersectionObserver) 239 + - v0.10.0: `data-volt-poll` attribute for periodic background requests 240 + - v0.10.0: Configurable intervals, delays, and signal-based triggers 241 + - v0.10.0: Automatic cancellation when elements unmount 242 + - v0.10.0: Conditional polling tied to reactive signals 243 + - v0.10.0: Background task scheduler with priority management 244 + 245 + ### Attribute Prefix Support 246 + 247 + **Goal:** Support multiple attribute prefix options for developer preference. 248 + **Outcome:** VoltX.js supports `voltx-`, `vx-`, and `data-volt-` prefixes. 249 + **Deliverables:** 250 + - v0.11.0: Add support for `voltx-*` and `vx-*` attribute prefixes 251 + - v0.11.0: Recommend `vx-*` as primary in documentation 252 + - v0.11.0: Maintain backward compatibility with `data-volt-*` 253 + - v0.11.0: Update demo to use recommended prefix 188 254 189 255 ### Inspector & Developer Tools 190 256 191 257 **Goal:** Improve developer experience and runtime introspection. 192 258 **Outcome:** First-class developer ergonomics; VoltX.js is enjoyable to debug and extend. 193 259 **Deliverables:** 194 - - v0.9.1 195 - - Developer overlay for inspecting signals, subscriptions, and effects 196 - - Time-travel debugging for signal history 197 - - v0.9.2 198 - - Signal dependency graph visualization (graph data structure implemented in [proxy](#proxy-based-reactivity-enhancements) milestone) 199 - - v0.9.3 200 - - Browser console integration (`window.$volt.inspect()`) 201 - - Dev logging toggle (`Volt.debug = true`) 202 - - v0.9.4 203 - - Request/response debugging (HTTP actions, SSE streams) 204 - - v0.9.5 205 - - Performance profiling tools 206 - - v0.9.6 to v0.9.10 207 - - Browser DevTools extension 260 + - v0.13.0: Visual in-DOM error overlays for development mode 261 + - v0.13.0: Runtime health monitor tracking failures 262 + - v0.13.0: Configurable global error policy (silent, overlay, throw) 263 + - v0.13.0: Developer overlay for inspecting signals, subscriptions, and effects 264 + - v0.13.0: Time-travel debugging for signal history 265 + - v0.13.0: Signal dependency graph visualization 266 + - v0.13.0: Performance profiling tools 267 + - v0.13.0: Browser console integration (`window.$volt.inspect()`) 268 + - v0.13.0: Dev logging toggle (`Volt.debug = true`) 269 + - v0.13.0: Request/response debugging (HTTP actions, SSE streams) 270 + - v0.13.0: Browser DevTools extension with full integration 208 271 209 272 ### Stable Release 210 273 ··· 219 282 - Announcement post and release notes 220 283 - Community contribution guide & governance doc 221 284 222 - ### Better Demo 223 - 224 - **Goal:** Transform the current programmatic demo into a declarative multi-page SPA showcasing all framework and CSS features. 225 - **Outcome:** Production-quality reference application demonstrating VoltX.js best practices and real-world patterns. 226 - **Deliverables:** 227 - - Convert demo from programmatic to declarative mode (charge() + data-volt attributes) 228 - - Implement multi-page routing using Navigation & History API plugin 229 - - Add tooltips to VoltX css using data attributes 230 - - Example: data-vx-tooltip="Right" data-placement="right" 231 - - Page: Home - Framework overview and feature highlights 232 - - Page: Getting Started - Installation and first examples 233 - - Page: Reactivity - Signals, computed, effects, bindings, conditional/list rendering 234 - - Page: HTTP - Backend integration with all methods, swap strategies, retry logic 235 - - Page: State - Global stores and scope helpers ($store, $scope, $pulse, $uid, $probe, $pins, $arc) 236 - - Page: Persistence - localStorage/sessionStorage/IndexedDB, persist plugin, URL sync 237 - - Page: Animations - Surge directive, shift plugin, View Transitions 238 - - Page: Forms - Model binding, validation, event modifiers, multi-step forms 239 - - Page: CSS - Complete Volt CSS showcase (typography, layout, Tufte sidenotes, tables) 240 - - Page: Patterns - Real-world components (tabs, accordion, modal, autocomplete) 241 - - View-source friendly code with clear examples 242 - - Copy-paste ready patterns for common use cases 243 - 244 285 ## Parking Lot 245 286 246 287 ### Evaluator & Binder Hardening 247 288 248 289 All expression evaluation now flows through a cached `new Function` compiler guarded by a hardened scope proxy, with the binder slimmed into a directive registry so plugins self-register while tests verify the sandboxed error surfaces. 249 - 250 - ### Naming 251 - 252 - ## Examples 253 - 254 - Many of these are ideas, not planned to be implemented 255 - 256 - ### Components 257 - 258 - - Modal Dialog - Conditional rendering, focus trapping, backdrop, keyboard escape 259 - - Tabs & Accordion - Conditional rendering, active state management, keyboard navigation 260 - - Form Validation - Model binding, computed validation, conditional messages, error states 261 - 262 - ### Client-Side (SPA/Static) 263 - 264 - - โœ“ Counter - Basic signals, computed, event handling 265 - - โœ“ TodoMVC - List rendering, persistence, filtering, CRUD operations 266 - - Search with Autocomplete - Async effects, debouncing, API integration, keyboard navigation 267 - - Calculator - Event handling, computed expressions, button grid, operation state 268 - - Image Gallery - For loops, filtering, lightbox, category selection 269 - 270 - - Multi-Step Wizard - Form state across steps, validation per step, progress tracking, navigation 271 - - Note-Taking App - Rich CRUD, categories/tags, search/filter, localStorage persistence, markdown preview 272 - - Expense Tracker - Date handling, categories, computed totals/charts, filtering by date range, CSV export 273 - - Kanban Board - Drag-and-drop (via events), column management, task editing, state persistence 274 - - Timer/Stopwatch - Async effects, intervals, lap times, pause/resume, localStorage for history 275 - 276 - - Real-time Chat - SSE for messages, typing indicators, user presence, message history 277 - - Live Dashboard - SSE for metrics, charts updating in real-time, WebSocket fallback 278 - - Collaborative Editor - Operational transforms, SSE for changes, conflict resolution, cursor positions 279 - - Infinite Scroll Feed - Polling for new items, intersection observer, virtualized rendering 280 - - Admin Panel/CMS - CRUD operations, data tables, filters, pagination, bulk actions 281 - 282 - ### Server-Side Rendered (SSR) 283 - 284 - These will live in an example repo. 285 - 286 - - Authentication Flows - Login, signup, password reset, email verification (Go, Python, Rust, Node) 287 - - File Upload with Progress - Chunked uploads, progress bars, validation (Go, Python, Rust, Node) 288 - - Search with Server-Side Filtering - Debounced search, paginated results (Go, Python, Rust, Node) 289 - 290 - ### Desktop Apps 291 - 292 - - Note Editor - Local file system, syntax highlighting, multi-tab, settings persistence 293 - - System Monitor - CPU/memory graphs, process list, real-time updates 294 - - Database Client - Table browser, query editor, result grid, export 295 - - Media Player - File browser, playlists, controls, metadata display
+58 -4
TODO.md
··· 1 1 # Better Demo Implementation TODO 2 2 3 - This document tracks the implementation of the Better Demo deliverables from ROADMAP.md. 4 - 5 3 ## Existing Issues 6 4 7 5 - [x] **FIXME** (lib/src/demo/sections/plugins.ts:68): Sidenotes need stylesheet constraints - RESOLVED ··· 172 170 173 171 ## Phase 5: Polish & Documentation 174 172 175 - ### 14. View-Source Friendly Code 173 + ### 14. Framework Capabilities Showcase 174 + 175 + **Note:** Showcase framework capabilities as features are completed from ROADMAP.md 176 + 177 + - [ ] Add bundle size widget/badge highlighting <15KB achievement (from Bundle Size Optimization milestone) 178 + - [ ] Demonstrate CSP-safe mode when available (from CSP Compatibility milestone) 179 + - [ ] Showcase DOM morphing features (from DOM Morphing & Streaming milestone) 180 + - [ ] Demonstrate SSE streaming (from DOM Morphing & Streaming milestone) 181 + - [ ] Show scope inheritance patterns (from Scope Inheritance & State Management milestone) 182 + - [ ] Display reactive polling examples (from Background Requests & Reactive Polling milestone) 183 + 184 + ### 15. View-Source Friendly Code 176 185 177 186 - [ ] Ensure all HTML is readable and well-commented 178 187 - [ ] Add explanatory comments to complex bindings 179 188 - [ ] Include inline documentation where helpful 180 189 - [ ] Make examples copy-paste ready 181 190 182 - ### 15. Copy-Paste Ready Patterns 191 + ### 16. Copy-Paste Ready Patterns 183 192 184 193 - [ ] Extract reusable patterns into clearly marked sections 185 194 - [ ] Provide minimal examples for each feature ··· 212 221 components.css # Add tooltip styles here 213 222 ... 214 223 ``` 224 + 225 + ## Example Ideas 226 + 227 + Many of these are ideas, not planned to be implemented 228 + 229 + ### Components 230 + 231 + - Modal Dialog - Conditional rendering, focus trapping, backdrop, keyboard escape 232 + - Tabs & Accordion - Conditional rendering, active state management, keyboard navigation 233 + - Form Validation - Model binding, computed validation, conditional messages, error states 234 + 235 + ### Client-Side (SPA/Static) 236 + 237 + - โœ“ Counter - Basic signals, computed, event handling 238 + - โœ“ TodoMVC - List rendering, persistence, filtering, CRUD operations 239 + - Search with Autocomplete - Async effects, debouncing, API integration, keyboard navigation 240 + - Calculator - Event handling, computed expressions, button grid, operation state 241 + - Image Gallery - For loops, filtering, lightbox, category selection 242 + 243 + - Multi-Step Wizard - Form state across steps, validation per step, progress tracking, navigation 244 + - Note-Taking App - Rich CRUD, categories/tags, search/filter, localStorage persistence, markdown preview 245 + - Expense Tracker - Date handling, categories, computed totals/charts, filtering by date range, CSV export 246 + - Kanban Board - Drag-and-drop (via events), column management, task editing, state persistence 247 + - Timer/Stopwatch - Async effects, intervals, lap times, pause/resume, localStorage for history 248 + 249 + - Real-time Chat - SSE for messages, typing indicators, user presence, message history 250 + - Live Dashboard - SSE for metrics, charts updating in real-time, WebSocket fallback 251 + - Collaborative Editor - Operational transforms, SSE for changes, conflict resolution, cursor positions 252 + - Infinite Scroll Feed - Polling for new items, intersection observer, virtualized rendering 253 + - Admin Panel/CMS - CRUD operations, data tables, filters, pagination, bulk actions 254 + 255 + ### Server-Side Rendered (SSR) 256 + 257 + These will live in an example repo. 258 + 259 + - Authentication Flows - Login, signup, password reset, email verification (Go, Python, Rust, Node) 260 + - File Upload with Progress - Chunked uploads, progress bars, validation (Go, Python, Rust, Node) 261 + - Search with Server-Side Filtering - Debounced search, paginated results (Go, Python, Rust, Node) 262 + 263 + ### Desktop Apps 264 + 265 + - Note Editor - Local file system, syntax highlighting, multi-tab, settings persistence 266 + - System Monitor - CPU/memory graphs, process list, real-time updates 267 + - Database Client - Table browser, query editor, result grid, export 268 + - Media Player - File browser, playlists, controls, metadata display
+83
cli/README.md
··· 1 + # create-voltx 2 + 3 + CLI for creating and managing VoltX.js applications. 4 + 5 + ## Usage 6 + 7 + ### Create a New Project 8 + 9 + ```bash 10 + # Using pnpm (recommended) 11 + pnpm create voltx my-app 12 + 13 + # Using npm 14 + npm create voltx@latest my-app 15 + 16 + # Using npx 17 + npx create-voltx my-app 18 + ``` 19 + 20 + ### Commands 21 + 22 + #### `init [project-name]` 23 + 24 + Create a new VoltX.js project with interactive template selection. 25 + 26 + ```bash 27 + voltx init my-app 28 + ``` 29 + 30 + **Templates:** 31 + 32 + - **Minimal** - Basic VoltX.js app with counter 33 + - **With Router** - Multi-page app with routing 34 + - **With Plugins** - All plugins demo 35 + - **Styles Only** - Just HTML + CSS, no framework 36 + 37 + #### `dev` 38 + 39 + Start Vite development server. 40 + 41 + ```bash 42 + voltx dev [--port 3000] [--open] 43 + ``` 44 + 45 + #### `build` 46 + 47 + Build project for production. 48 + 49 + ```bash 50 + voltx build [--out dist] 51 + ``` 52 + 53 + #### `download` 54 + 55 + Download VoltX.js assets from CDN. 56 + 57 + ```bash 58 + voltx download [--version latest] [--output .] 59 + ``` 60 + 61 + ## Documentation 62 + 63 + See the [CLI Guide](https://stormlightlabs.github.io/volt/cli) for complete documentation. 64 + 65 + ## Development 66 + 67 + ```bash 68 + # Install dependencies 69 + pnpm install 70 + 71 + # Build CLI 72 + pnpm build 73 + 74 + # Run tests 75 + pnpm test 76 + 77 + # Type check 78 + pnpm typecheck 79 + ``` 80 + 81 + ## License 82 + 83 + MIT
+25
cli/package.json
··· 1 + { 2 + "name": "create-voltx", 3 + "version": "0.1.0", 4 + "description": "CLI for creating and managing VoltX.js applications", 5 + "type": "module", 6 + "author": "Owais Jamil", 7 + "license": "MIT", 8 + "repository": { "type": "git", "url": "https://github.com/stormlightlabs/volt.git", "directory": "cli" }, 9 + "bin": { "create-voltx": "./dist/index.js", "voltx": "./dist/index.js" }, 10 + "files": ["dist", "templates"], 11 + "main": "./dist/index.js", 12 + "module": "./dist/index.js", 13 + "types": "./dist/index.d.ts", 14 + "exports": { ".": "./dist/index.js", "./package.json": "./package.json" }, 15 + "scripts": { 16 + "build": "tsdown", 17 + "dev": "tsdown --watch", 18 + "test": "vitest", 19 + "test:run": "vitest run", 20 + "typecheck": "tsc --noEmit" 21 + }, 22 + "devDependencies": { "tsdown": "^0.15.6", "@vitest/coverage-v8": "^3.2.4" }, 23 + "dependencies": { "chalk": "^5.6.2", "commander": "^14.0.1", "@inquirer/prompts": "^8.0.1" }, 24 + "keywords": ["voltx", "reactive", "framework", "cli", "scaffold", "create"] 25 + }
+39
cli/src/commands/build.ts
··· 1 + import { echo } from "$utils/echo.js"; 2 + import { spawn } from "node:child_process"; 3 + 4 + /** 5 + * Builds the VoltX.js project for production using Vite. 6 + */ 7 + export async function buildCommand(options: { outDir?: string } = {}): Promise<void> { 8 + const outDir = options.outDir || "dist"; 9 + 10 + echo.title("\nโšก Building VoltX.js project for production...\n"); 11 + 12 + try { 13 + const { existsSync } = await import("node:fs"); 14 + if (!existsSync("index.html")) { 15 + echo.warn("Warning: No index.html found in current directory"); 16 + echo.info("Are you in a VoltX.js project?\n"); 17 + } 18 + 19 + const viteArgs = ["vite", "build", "--outDir", outDir]; 20 + const viteProcess = spawn("npx", viteArgs, { stdio: "inherit", shell: true }); 21 + 22 + viteProcess.on("error", (error) => { 23 + echo.err("Failed to build project:", error); 24 + process.exit(1); 25 + }); 26 + 27 + viteProcess.on("exit", (code) => { 28 + if (code === 0) { 29 + echo.success(`\nโœ“ Build completed successfully!\n`); 30 + echo.info(`Output directory: ${outDir}\n`); 31 + } else if (code !== null) { 32 + process.exit(code); 33 + } 34 + }); 35 + } catch (error) { 36 + echo.err("Error building project:", error); 37 + process.exit(1); 38 + } 39 + }
+47
cli/src/commands/dev.ts
··· 1 + import { echo } from "$utils/echo.js"; 2 + import { spawn } from "node:child_process"; 3 + 4 + /** 5 + * Starts a Vite development server for the current project. 6 + */ 7 + export async function devCommand(options: { port?: number; open?: boolean } = {}): Promise<void> { 8 + const port = options.port || 3000; 9 + const shouldOpen = options.open || false; 10 + 11 + echo.title("\nโšก Starting VoltX.js development server...\n"); 12 + 13 + try { 14 + const { existsSync } = await import("node:fs"); 15 + if (!existsSync("index.html")) { 16 + echo.warn("Warning: No index.html found in current directory"); 17 + echo.info("Are you in a VoltX.js project?\n"); 18 + } 19 + 20 + const viteArgs = ["vite", "--port", port.toString(), "--host"]; 21 + 22 + if (shouldOpen) { 23 + viteArgs.push("--open"); 24 + } 25 + 26 + const viteProcess = spawn("npx", viteArgs, { stdio: "inherit", shell: true }); 27 + 28 + viteProcess.on("error", (error) => { 29 + echo.err("Failed to start dev server:", error); 30 + process.exit(1); 31 + }); 32 + 33 + viteProcess.on("exit", (code) => { 34 + if (code !== 0 && code !== null) { 35 + process.exit(code); 36 + } 37 + }); 38 + 39 + process.on("SIGINT", () => { 40 + viteProcess.kill("SIGINT"); 41 + process.exit(0); 42 + }); 43 + } catch (error) { 44 + echo.err("Error starting dev server:", error); 45 + process.exit(1); 46 + } 47 + }
+40
cli/src/commands/download.ts
··· 1 + import { downloadFile, getCDNUrls } from "$utils/download.js"; 2 + import { echo } from "$utils/echo.js"; 3 + import path from "node:path"; 4 + 5 + /** 6 + * Downloads VoltX.js assets (JS and/or CSS) from the CDN. 7 + */ 8 + export async function downloadCommand( 9 + options: { version?: string; js?: boolean; css?: boolean; output?: string } = {}, 10 + ): Promise<void> { 11 + const version = options.version || "latest"; 12 + const downloadJS = options.js !== false; 13 + const downloadCSS = options.css !== false; 14 + const outputDir = options.output || "."; 15 + 16 + echo.title("\nโšก Downloading VoltX.js assets...\n"); 17 + 18 + try { 19 + const urls = getCDNUrls(version); 20 + 21 + if (downloadJS) { 22 + const jsPath = path.join(outputDir, "voltx.min.js"); 23 + echo.info(`Downloading voltx.min.js (${version})...`); 24 + await downloadFile(urls.js, jsPath); 25 + echo.ok(`โœ“ Downloaded: ${jsPath}`); 26 + } 27 + 28 + if (downloadCSS) { 29 + const cssPath = path.join(outputDir, "voltx.min.css"); 30 + echo.info(`Downloading voltx.min.css (${version})...`); 31 + await downloadFile(urls.css, cssPath); 32 + echo.ok(`โœ“ Downloaded: ${cssPath}`); 33 + } 34 + 35 + echo.success("\nโœ“ Download completed successfully!\n"); 36 + } catch (error) { 37 + echo.err("Failed to download assets:", error); 38 + process.exit(1); 39 + } 40 + }
+156
cli/src/commands/init.ts
··· 1 + import { 2 + generateMinimalCSS, 3 + generateMinimalHTML, 4 + generateMinimalPackageJSON, 5 + generateMinimalREADME, 6 + } from "$templates/minimal.js"; 7 + import { 8 + generateStylesCSS, 9 + generateStylesHTML, 10 + generateStylesPackageJSON, 11 + generateStylesREADME, 12 + } from "$templates/styles.js"; 13 + import { 14 + generatePluginsCSS, 15 + generatePluginsHTML, 16 + generatePluginsPackageJSON, 17 + generatePluginsREADME, 18 + } from "$templates/with-plugins.js"; 19 + import { 20 + generateRouterCSS, 21 + generateRouterHTML, 22 + generateRouterPackageJSON, 23 + generateRouterREADME, 24 + } from "$templates/with-router.js"; 25 + import { downloadFile, getCDNUrls } from "$utils/download.js"; 26 + import { echo } from "$utils/echo.js"; 27 + import { createFile, isEmptyOrMissing } from "$utils/files.js"; 28 + import { input, select } from "@inquirer/prompts"; 29 + import path from "node:path"; 30 + 31 + type Template = "minimal" | "with-router" | "with-plugins" | "styles"; 32 + 33 + /** 34 + * Download VoltX.js assets to the project directory. 35 + */ 36 + async function downloadAssets(projectDir: string, template: Template): Promise<void> { 37 + const urls = getCDNUrls(); 38 + 39 + echo.info("Downloading VoltX.js assets..."); 40 + 41 + const cssPath = path.join(projectDir, "voltx.min.css"); 42 + await downloadFile(urls.css, cssPath); 43 + echo.ok(` Downloaded: voltx.min.css`); 44 + 45 + if (template !== "styles") { 46 + const jsPath = path.join(projectDir, "voltx.min.js"); 47 + await downloadFile(urls.js, jsPath); 48 + echo.ok(` Downloaded: voltx.min.js`); 49 + } 50 + } 51 + 52 + /** 53 + * Generate project files based on the selected template. 54 + */ 55 + async function generateProjectFiles(projectDir: string, projectName: string, template: Template): Promise<void> { 56 + echo.info("Generating project files..."); 57 + 58 + let htmlContent: string; 59 + let cssContent: string; 60 + let packageJsonContent: string; 61 + let readmeContent: string; 62 + 63 + switch (template) { 64 + case "minimal": 65 + htmlContent = generateMinimalHTML(projectName); 66 + cssContent = generateMinimalCSS(); 67 + packageJsonContent = generateMinimalPackageJSON(projectName); 68 + readmeContent = generateMinimalREADME(projectName); 69 + break; 70 + 71 + case "styles": 72 + htmlContent = generateStylesHTML(projectName); 73 + cssContent = generateStylesCSS(); 74 + packageJsonContent = generateStylesPackageJSON(projectName); 75 + readmeContent = generateStylesREADME(projectName); 76 + break; 77 + 78 + case "with-router": 79 + htmlContent = generateRouterHTML(projectName); 80 + cssContent = generateRouterCSS(); 81 + packageJsonContent = generateRouterPackageJSON(projectName); 82 + readmeContent = generateRouterREADME(projectName); 83 + break; 84 + 85 + case "with-plugins": 86 + htmlContent = generatePluginsHTML(projectName); 87 + cssContent = generatePluginsCSS(); 88 + packageJsonContent = generatePluginsPackageJSON(projectName); 89 + readmeContent = generatePluginsREADME(projectName); 90 + break; 91 + } 92 + 93 + await createFile(path.join(projectDir, "index.html"), htmlContent); 94 + echo.ok(` Created: index.html`); 95 + 96 + await createFile(path.join(projectDir, "styles.css"), cssContent); 97 + echo.ok(` Created: styles.css`); 98 + 99 + await createFile(path.join(projectDir, "package.json"), packageJsonContent); 100 + echo.ok(` Created: package.json`); 101 + 102 + await createFile(path.join(projectDir, "README.md"), readmeContent); 103 + echo.ok(` Created: README.md`); 104 + } 105 + 106 + /** 107 + * Init command implementation. 108 + * 109 + * Creates a new VoltX.js project with the selected template. 110 + */ 111 + export async function initCommand(projectName?: string): Promise<void> { 112 + echo.title("\nโšก Create VoltX.js App\n"); 113 + 114 + if (!projectName) { 115 + projectName = await input({ message: "Project name:", default: "my-voltx-app" }); 116 + 117 + if (!projectName) { 118 + echo.err("Project name is required"); 119 + process.exit(1); 120 + } 121 + } 122 + 123 + const projectDir = path.resolve(process.cwd(), projectName); 124 + 125 + if (!(await isEmptyOrMissing(projectDir))) { 126 + echo.err(`Directory ${projectName} already exists and is not empty`); 127 + process.exit(1); 128 + } 129 + 130 + const template = await select<Template>({ 131 + message: "Select a template:", 132 + choices: [ 133 + { name: "Minimal", value: "minimal", description: "Basic VoltX.js app with counter" }, 134 + { name: "With Router", value: "with-router", description: "Multi-page app with routing" }, 135 + { name: "With Plugins", value: "with-plugins", description: "All plugins demo" }, 136 + { name: "Styles Only", value: "styles", description: "Just HTML + CSS, no framework" }, 137 + ], 138 + }); 139 + 140 + try { 141 + echo.text(""); 142 + await generateProjectFiles(projectDir, projectName, template); 143 + 144 + echo.text(""); 145 + await downloadAssets(projectDir, template); 146 + 147 + echo.success(`\nโœ“ Project created successfully!\n`); 148 + echo.info(`Next steps:\n`); 149 + echo.text(` cd ${projectName}`); 150 + echo.text(` pnpm install`); 151 + echo.text(` pnpm dev\n`); 152 + } catch (error) { 153 + echo.err("Failed to create project:", error); 154 + process.exit(1); 155 + } 156 + }
+82
cli/src/index.ts
··· 1 + #!/usr/bin/env node 2 + /* eslint-disable unicorn/no-process-exit */ 3 + import { buildCommand } from "$commands/build.js"; 4 + import { devCommand } from "$commands/dev.js"; 5 + import { downloadCommand } from "$commands/download.js"; 6 + import { initCommand } from "$commands/init.js"; 7 + import { echo } from "$utils/echo.js"; 8 + import { Command } from "commander"; 9 + 10 + const program = new Command(); 11 + const isCreateMode = process.argv[1]?.includes("create-voltx"); 12 + 13 + program.name(isCreateMode ? "create-voltx" : "voltx").description("CLI for creating and managing VoltX.js applications") 14 + .version("0.1.0"); 15 + 16 + program.command("init [project-name]").description("Create a new VoltX.js project").action( 17 + async (projectName: string | undefined) => { 18 + try { 19 + await initCommand(projectName); 20 + } catch (error) { 21 + echo.err("Error creating project:", error); 22 + process.exit(1); 23 + } 24 + }, 25 + ); 26 + 27 + program.command("dev").description("Start development server").option( 28 + "-p, --port <port>", 29 + "Port to run the dev server on", 30 + "3000", 31 + ).option("-o, --open", "Open browser automatically", false).action( 32 + async (options: { port?: string; open?: boolean }) => { 33 + try { 34 + const port = options.port ? Number.parseInt(options.port, 10) : 3000; 35 + await devCommand({ port, open: options.open }); 36 + } catch (error) { 37 + echo.err("Error starting dev server:", error); 38 + process.exit(1); 39 + } 40 + }, 41 + ); 42 + 43 + program.command("build").description("Build project for production").option("--out <dir>", "Output directory", "dist") 44 + .action(async (options: { out?: string }) => { 45 + try { 46 + await buildCommand({ outDir: options.out }); 47 + } catch (error) { 48 + echo.err("Error building project:", error); 49 + process.exit(1); 50 + } 51 + }); 52 + 53 + program.command("download").description("Download VoltX.js assets (JS and CSS)").option( 54 + "--version <version>", 55 + "VoltX.js version to download", 56 + "latest", 57 + ).option("--no-js", "Skip downloading JavaScript file").option("--no-css", "Skip downloading CSS file").option( 58 + "-o, --output <dir>", 59 + "Output directory", 60 + ".", 61 + ).action(async (options: { version?: string; js?: boolean; css?: boolean; output?: string }) => { 62 + try { 63 + await downloadCommand(options); 64 + } catch (error) { 65 + echo.err("Error downloading assets:", error); 66 + process.exit(1); 67 + } 68 + }); 69 + 70 + if (isCreateMode && process.argv.length === 2) { 71 + initCommand().catch((error) => { 72 + echo.err("Error:", error); 73 + process.exit(1); 74 + }); 75 + } else if (isCreateMode && process.argv.length === 3 && !process.argv[2]?.startsWith("-")) { 76 + initCommand(process.argv[2]).catch((error) => { 77 + echo.err("Error:", error); 78 + process.exit(1); 79 + }); 80 + } else { 81 + program.parse(); 82 + }
+149
cli/src/templates/minimal.ts
··· 1 + /** 2 + * Generate a minimal VoltX.js project with declarative mode. 3 + */ 4 + export function generateMinimalHTML(projectName: string): string { 5 + return `<!DOCTYPE html> 6 + <html lang="en"> 7 + <head> 8 + <meta charset="UTF-8"> 9 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 10 + <title>${projectName}</title> 11 + <link rel="stylesheet" href="voltx.min.css"> 12 + <link rel="stylesheet" href="styles.css"> 13 + </head> 14 + <body> 15 + <div data-volt data-volt-state='{"count": 0, "message": "Hello VoltX!"}'> 16 + <div class="container"> 17 + <h1 data-volt-text="message"></h1> 18 + 19 + <div class="counter"> 20 + <button data-volt-on-click="count.set(count.get() - 1)">-</button> 21 + <span data-volt-text="count"></span> 22 + <button data-volt-on-click="count.set(count.get() + 1)">+</button> 23 + </div> 24 + 25 + <p class="hint">Edit this file to start building your app!</p> 26 + </div> 27 + </div> 28 + 29 + <script type="module"> 30 + import { charge, registerPlugin, persistPlugin } from './voltx.min.js'; 31 + 32 + registerPlugin('persist', persistPlugin); 33 + charge(); 34 + </script> 35 + </body> 36 + </html> 37 + `; 38 + } 39 + 40 + export function generateMinimalCSS(): string { 41 + return `/* Custom styles for your VoltX.js app */ 42 + 43 + body { 44 + margin: 0; 45 + font-family: system-ui, -apple-system, sans-serif; 46 + background: #f5f5f5; 47 + color: #333; 48 + } 49 + 50 + .container { 51 + max-width: 600px; 52 + margin: 4rem auto; 53 + padding: 2rem; 54 + background: white; 55 + border-radius: 8px; 56 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 57 + text-align: center; 58 + } 59 + 60 + h1 { 61 + margin: 0 0 2rem; 62 + color: #2563eb; 63 + } 64 + 65 + .counter { 66 + display: flex; 67 + gap: 1rem; 68 + justify-content: center; 69 + align-items: center; 70 + margin: 2rem 0; 71 + } 72 + 73 + .counter button { 74 + width: 48px; 75 + height: 48px; 76 + font-size: 1.5rem; 77 + border: 2px solid #2563eb; 78 + background: white; 79 + color: #2563eb; 80 + border-radius: 8px; 81 + cursor: pointer; 82 + transition: all 0.2s; 83 + } 84 + 85 + .counter button:hover { 86 + background: #2563eb; 87 + color: white; 88 + } 89 + 90 + .counter span { 91 + font-size: 2rem; 92 + font-weight: bold; 93 + min-width: 60px; 94 + } 95 + 96 + .hint { 97 + margin-top: 2rem; 98 + color: #666; 99 + font-size: 0.9rem; 100 + } 101 + `; 102 + } 103 + 104 + export function generateMinimalPackageJSON(projectName: string): string { 105 + return JSON.stringify( 106 + { 107 + name: projectName, 108 + version: "0.1.0", 109 + type: "module", 110 + scripts: { dev: "voltx dev", build: "voltx build" }, 111 + devDependencies: { "create-voltx": "^0.1.0" }, 112 + }, 113 + null, 114 + 2, 115 + ); 116 + } 117 + 118 + export function generateMinimalREADME(projectName: string): string { 119 + return `# ${projectName} 120 + 121 + A minimal VoltX.js application. 122 + 123 + ## Getting Started 124 + 125 + 1. Install dependencies: 126 + \`\`\`bash 127 + pnpm install 128 + \`\`\` 129 + 130 + 2. Start the development server: 131 + \`\`\`bash 132 + pnpm dev 133 + \`\`\` 134 + 135 + 3. Open your browser to the URL shown in the terminal. 136 + 137 + ## Project Structure 138 + 139 + - \`index.html\` - Main HTML file with declarative VoltX.js markup 140 + - \`styles.css\` - Custom styles 141 + - \`voltx.min.js\` - VoltX.js framework (ES module) 142 + - \`voltx.min.css\` - VoltX.js base styles 143 + 144 + ## Learn More 145 + 146 + - [VoltX.js Documentation](https://stormlightlabs.github.io/volt) 147 + - [API Reference](https://stormlightlabs.github.io/volt/api) 148 + `; 149 + }
+194
cli/src/templates/styles.ts
··· 1 + /** 2 + * Generate a styles-only project with VoltX.js CSS but no JavaScript framework. 3 + */ 4 + export function generateStylesHTML(projectName: string): string { 5 + return `<!DOCTYPE html> 6 + <html lang="en"> 7 + <head> 8 + <meta charset="UTF-8"> 9 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 10 + <title>${projectName}</title> 11 + <link rel="stylesheet" href="voltx.min.css"> 12 + <link rel="stylesheet" href="styles.css"> 13 + </head> 14 + <body> 15 + <div class="container"> 16 + <h1>Welcome to ${projectName}</h1> 17 + 18 + <div class="card"> 19 + <h2>VoltX.js CSS</h2> 20 + <p>This project uses VoltX.js CSS utility classes without the reactive framework.</p> 21 + <p>Style your HTML with utility classes and semantic CSS variables.</p> 22 + </div> 23 + 24 + <div class="button-group"> 25 + <button class="btn btn-primary">Primary</button> 26 + <button class="btn btn-secondary">Secondary</button> 27 + <button class="btn btn-danger">Danger</button> 28 + </div> 29 + 30 + <div class="grid"> 31 + <div class="card"> 32 + <h3>Card 1</h3> 33 + <p>Build with semantic CSS.</p> 34 + </div> 35 + <div class="card"> 36 + <h3>Card 2</h3> 37 + <p>No JavaScript required.</p> 38 + </div> 39 + <div class="card"> 40 + <h3>Card 3</h3> 41 + <p>Just HTML and CSS.</p> 42 + </div> 43 + </div> 44 + </div> 45 + </body> 46 + </html> 47 + `; 48 + } 49 + 50 + export function generateStylesCSS(): string { 51 + return `/* Custom styles for your project */ 52 + 53 + body { 54 + margin: 0; 55 + font-family: system-ui, -apple-system, sans-serif; 56 + background: #f5f5f5; 57 + color: #333; 58 + } 59 + 60 + .container { 61 + max-width: 900px; 62 + margin: 0 auto; 63 + padding: 2rem; 64 + } 65 + 66 + h1 { 67 + text-align: center; 68 + margin-bottom: 3rem; 69 + color: #2563eb; 70 + } 71 + 72 + .card { 73 + background: white; 74 + padding: 1.5rem; 75 + border-radius: 8px; 76 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 77 + margin-bottom: 2rem; 78 + } 79 + 80 + .card h2, 81 + .card h3 { 82 + margin-top: 0; 83 + color: #1e40af; 84 + } 85 + 86 + .button-group { 87 + display: flex; 88 + gap: 1rem; 89 + justify-content: center; 90 + margin: 2rem 0; 91 + } 92 + 93 + .btn { 94 + padding: 0.75rem 1.5rem; 95 + border: none; 96 + border-radius: 6px; 97 + font-size: 1rem; 98 + font-weight: 500; 99 + cursor: pointer; 100 + transition: all 0.2s; 101 + } 102 + 103 + .btn-primary { 104 + background: #2563eb; 105 + color: white; 106 + } 107 + 108 + .btn-primary:hover { 109 + background: #1e40af; 110 + } 111 + 112 + .btn-secondary { 113 + background: #64748b; 114 + color: white; 115 + } 116 + 117 + .btn-secondary:hover { 118 + background: #475569; 119 + } 120 + 121 + .btn-danger { 122 + background: #dc2626; 123 + color: white; 124 + } 125 + 126 + .btn-danger:hover { 127 + background: #b91c1c; 128 + } 129 + 130 + .grid { 131 + display: grid; 132 + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 133 + gap: 1.5rem; 134 + margin-top: 2rem; 135 + } 136 + 137 + .grid .card { 138 + margin-bottom: 0; 139 + } 140 + `; 141 + } 142 + 143 + export function generateStylesPackageJSON(projectName: string): string { 144 + return JSON.stringify( 145 + { 146 + name: projectName, 147 + version: "0.1.0", 148 + scripts: { dev: "voltx dev", build: "voltx build" }, 149 + devDependencies: { "create-voltx": "^0.1.0" }, 150 + }, 151 + null, 152 + 2, 153 + ); 154 + } 155 + 156 + export function generateStylesREADME(projectName: string): string { 157 + return `# ${projectName} 158 + 159 + A styles-only project using VoltX.js CSS utilities. 160 + 161 + ## Getting Started 162 + 163 + 1. Install dependencies: 164 + \`\`\`bash 165 + pnpm install 166 + \`\`\` 167 + 168 + 2. Start the development server: 169 + \`\`\`bash 170 + pnpm dev 171 + \`\`\` 172 + 173 + 3. Open your browser to the URL shown in the terminal. 174 + 175 + ## What's Included 176 + 177 + This project uses VoltX.js CSS for styling without the reactive framework: 178 + 179 + - Utility classes for common patterns 180 + - CSS custom properties for theming 181 + - Semantic HTML with clean CSS 182 + 183 + ## Adding VoltX.js Reactivity 184 + 185 + To add reactivity to this project later: 186 + 187 + \`\`\`bash 188 + # Add data-volt attributes to your HTML 189 + # Import and initialize VoltX.js in a script tag 190 + \`\`\` 191 + 192 + See the [VoltX.js Documentation](https://stormlightlabs.github.io/volt) for more information. 193 + `; 194 + }
+278
cli/src/templates/with-plugins.ts
··· 1 + /** 2 + * Generate a VoltX.js project with all plugins pre-registered. 3 + */ 4 + export function generatePluginsHTML(projectName: string): string { 5 + return `<!DOCTYPE html> 6 + <html lang="en"> 7 + <head> 8 + <meta charset="UTF-8"> 9 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 10 + <title>${projectName}</title> 11 + <link rel="stylesheet" href="voltx.min.css"> 12 + <link rel="stylesheet" href="styles.css"> 13 + </head> 14 + <body> 15 + <div data-volt data-volt-state='{"count": 0, "message": "Hello VoltX!"}'> 16 + <div class="container"> 17 + <h1 data-volt-text="message"></h1> 18 + 19 + <!-- Persist plugin demo --> 20 + <div class="card"> 21 + <h2>Persist Plugin</h2> 22 + <p>This counter persists to localStorage:</p> 23 + <div class="counter"> 24 + <button data-volt-on-click="count.set(count.get() - 1)">-</button> 25 + <span data-volt-text="count" data-volt-persist="count"></span> 26 + <button data-volt-on-click="count.set(count.get() + 1)">+</button> 27 + </div> 28 + <p class="hint">Refresh the page - your count is saved!</p> 29 + </div> 30 + 31 + <!-- Scroll plugin demo --> 32 + <div class="card"> 33 + <h2>Scroll Plugin</h2> 34 + <p>Smooth scroll to sections:</p> 35 + <button data-volt-scroll-to="#section-1">Scroll to Section 1</button> 36 + <button data-volt-scroll-to="#section-2">Scroll to Section 2</button> 37 + </div> 38 + 39 + <!-- URL plugin demo --> 40 + <div class="card"> 41 + <h2>URL Plugin</h2> 42 + <p>State synced with URL params:</p> 43 + <input type="text" data-volt-model="message" data-volt-url-param="msg"> 44 + <p class="hint">Your message is in the URL!</p> 45 + </div> 46 + 47 + <!-- Surge plugin demo --> 48 + <div class="card"> 49 + <h2>Surge Plugin (Animations)</h2> 50 + <button data-volt-on-click="$scope.toggle('showBox', !$scope.get('showBox'))"> 51 + Toggle Box 52 + </button> 53 + <div 54 + data-volt-if="$scope.get('showBox')" 55 + data-volt-surge="fade" 56 + class="animated-box" 57 + > 58 + I fade in and out! 59 + </div> 60 + </div> 61 + 62 + <div id="section-1" class="section"> 63 + <h3>Section 1</h3> 64 + <p>This is a scrollable section.</p> 65 + </div> 66 + 67 + <div id="section-2" class="section"> 68 + <h3>Section 2</h3> 69 + <p>Scroll here with smooth animations!</p> 70 + </div> 71 + </div> 72 + </div> 73 + 74 + <script type="module"> 75 + import { 76 + charge, 77 + registerPlugin, 78 + persistPlugin, 79 + scrollPlugin, 80 + urlPlugin, 81 + surgePlugin, 82 + navigatePlugin 83 + } from './voltx.min.js'; 84 + 85 + // Register all plugins 86 + registerPlugin('persist', persistPlugin); 87 + registerPlugin('scroll', scrollPlugin); 88 + registerPlugin('url', urlPlugin); 89 + registerPlugin('surge', surgePlugin); 90 + registerPlugin('navigate', navigatePlugin); 91 + 92 + charge(); 93 + </script> 94 + </body> 95 + </html> 96 + `; 97 + } 98 + 99 + export function generatePluginsCSS(): string { 100 + return `/* Custom styles for your VoltX.js app */ 101 + 102 + body { 103 + margin: 0; 104 + font-family: system-ui, -apple-system, sans-serif; 105 + background: #f5f5f5; 106 + color: #333; 107 + } 108 + 109 + .container { 110 + max-width: 900px; 111 + margin: 2rem auto; 112 + padding: 2rem; 113 + } 114 + 115 + h1 { 116 + text-align: center; 117 + margin-bottom: 3rem; 118 + color: #2563eb; 119 + } 120 + 121 + .card { 122 + background: white; 123 + padding: 2rem; 124 + border-radius: 8px; 125 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 126 + margin-bottom: 2rem; 127 + } 128 + 129 + .card h2 { 130 + margin-top: 0; 131 + color: #1e40af; 132 + } 133 + 134 + .counter { 135 + display: flex; 136 + gap: 1rem; 137 + justify-content: center; 138 + align-items: center; 139 + margin: 1.5rem 0; 140 + } 141 + 142 + .counter button { 143 + width: 48px; 144 + height: 48px; 145 + font-size: 1.5rem; 146 + border: 2px solid #2563eb; 147 + background: white; 148 + color: #2563eb; 149 + border-radius: 8px; 150 + cursor: pointer; 151 + transition: all 0.2s; 152 + } 153 + 154 + .counter button:hover { 155 + background: #2563eb; 156 + color: white; 157 + } 158 + 159 + .counter span { 160 + font-size: 2rem; 161 + font-weight: bold; 162 + min-width: 60px; 163 + text-align: center; 164 + } 165 + 166 + button:not(.counter button) { 167 + padding: 0.75rem 1.5rem; 168 + background: #2563eb; 169 + color: white; 170 + border: none; 171 + border-radius: 6px; 172 + font-size: 1rem; 173 + font-weight: 500; 174 + cursor: pointer; 175 + transition: all 0.2s; 176 + margin-right: 0.5rem; 177 + } 178 + 179 + button:not(.counter button):hover { 180 + background: #1e40af; 181 + } 182 + 183 + input[type="text"] { 184 + width: 100%; 185 + padding: 0.75rem; 186 + border: 1px solid #ddd; 187 + border-radius: 4px; 188 + font-size: 1rem; 189 + margin: 1rem 0; 190 + } 191 + 192 + input[type="text"]:focus { 193 + outline: none; 194 + border-color: #2563eb; 195 + } 196 + 197 + .hint { 198 + color: #666; 199 + font-size: 0.9rem; 200 + margin-top: 1rem; 201 + } 202 + 203 + .animated-box { 204 + margin-top: 1.5rem; 205 + padding: 2rem; 206 + background: #dbeafe; 207 + border: 2px solid #2563eb; 208 + border-radius: 8px; 209 + text-align: center; 210 + font-weight: 500; 211 + color: #1e40af; 212 + } 213 + 214 + .section { 215 + margin-top: 3rem; 216 + padding: 3rem; 217 + background: white; 218 + border-radius: 8px; 219 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 220 + min-height: 300px; 221 + } 222 + 223 + .section h3 { 224 + margin-top: 0; 225 + color: #2563eb; 226 + } 227 + `; 228 + } 229 + 230 + export function generatePluginsPackageJSON(projectName: string): string { 231 + return JSON.stringify( 232 + { 233 + name: projectName, 234 + version: "0.1.0", 235 + type: "module", 236 + scripts: { dev: "voltx dev", build: "voltx build" }, 237 + devDependencies: { "create-voltx": "^0.1.0" }, 238 + }, 239 + null, 240 + 2, 241 + ); 242 + } 243 + 244 + export function generatePluginsREADME(projectName: string): string { 245 + return `# ${projectName} 246 + 247 + A VoltX.js application with all plugins pre-registered. 248 + 249 + ## Getting Started 250 + 251 + 1. Install dependencies: 252 + \`\`\`bash 253 + pnpm install 254 + \`\`\` 255 + 256 + 2. Start the development server: 257 + \`\`\`bash 258 + pnpm dev 259 + \`\`\` 260 + 261 + 3. Open your browser to the URL shown in the terminal. 262 + 263 + ## Included Plugins 264 + 265 + This project includes all VoltX.js plugins: 266 + 267 + - **Persist Plugin**: Save state to localStorage/sessionStorage 268 + - **Scroll Plugin**: Smooth scrolling and scroll restoration 269 + - **URL Plugin**: Sync state with URL parameters 270 + - **Surge Plugin**: Declarative animations (fade, slide, scale) 271 + - **Navigate Plugin**: Client-side routing 272 + 273 + ## Learn More 274 + 275 + - [VoltX.js Documentation](https://stormlightlabs.github.io/volt) 276 + - [Plugin Reference](https://stormlightlabs.github.io/volt/plugins) 277 + `; 278 + }
+235
cli/src/templates/with-router.ts
··· 1 + /** 2 + * Generate a VoltX.js project with router plugin. 3 + */ 4 + export function generateRouterHTML(projectName: string): string { 5 + return `<!DOCTYPE html> 6 + <html lang="en"> 7 + <head> 8 + <meta charset="UTF-8"> 9 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 10 + <title>${projectName}</title> 11 + <link rel="stylesheet" href="voltx.min.css"> 12 + <link rel="stylesheet" href="styles.css"> 13 + </head> 14 + <body> 15 + <div data-volt> 16 + <nav class="nav"> 17 + <a href="/" data-volt-navigate>Home</a> 18 + <a href="/about" data-volt-navigate>About</a> 19 + <a href="/contact" data-volt-navigate>Contact</a> 20 + </nav> 21 + 22 + <main class="container"> 23 + <!-- Home page --> 24 + <div data-volt-url="/" data-volt-url-exact> 25 + <h1>Home</h1> 26 + <p>Welcome to ${projectName}!</p> 27 + <p>This is a VoltX.js app with client-side routing.</p> 28 + </div> 29 + 30 + <!-- About page --> 31 + <div data-volt-url="/about"> 32 + <h1>About</h1> 33 + <p>This project demonstrates VoltX.js routing capabilities.</p> 34 + <ul> 35 + <li>Client-side navigation</li> 36 + <li>Clean URLs with History API</li> 37 + <li>Declarative route matching</li> 38 + </ul> 39 + </div> 40 + 41 + <!-- Contact page --> 42 + <div data-volt-url="/contact"> 43 + <h1>Contact</h1> 44 + <p>Get in touch with us!</p> 45 + <form> 46 + <label> 47 + Name: 48 + <input type="text" placeholder="Your name"> 49 + </label> 50 + <label> 51 + Email: 52 + <input type="email" placeholder="your@email.com"> 53 + </label> 54 + <label> 55 + Message: 56 + <textarea rows="4" placeholder="Your message"></textarea> 57 + </label> 58 + <button type="submit">Send</button> 59 + </form> 60 + </div> 61 + 62 + <!-- 404 page --> 63 + <div data-volt-url-fallback> 64 + <h1>404 - Page Not Found</h1> 65 + <p>The page you're looking for doesn't exist.</p> 66 + <a href="/" data-volt-navigate>Go back home</a> 67 + </div> 68 + </main> 69 + </div> 70 + 71 + <script type="module"> 72 + import { charge, registerPlugin, navigatePlugin, urlPlugin } from './voltx.min.js'; 73 + 74 + registerPlugin('navigate', navigatePlugin); 75 + registerPlugin('url', urlPlugin); 76 + charge(); 77 + </script> 78 + </body> 79 + </html> 80 + `; 81 + } 82 + 83 + export function generateRouterCSS(): string { 84 + return `/* Custom styles for your VoltX.js app */ 85 + 86 + body { 87 + margin: 0; 88 + font-family: system-ui, -apple-system, sans-serif; 89 + background: #f5f5f5; 90 + color: #333; 91 + } 92 + 93 + .nav { 94 + background: #2563eb; 95 + padding: 1rem 2rem; 96 + display: flex; 97 + gap: 2rem; 98 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 99 + } 100 + 101 + .nav a { 102 + color: white; 103 + text-decoration: none; 104 + font-weight: 500; 105 + padding: 0.5rem 1rem; 106 + border-radius: 4px; 107 + transition: background 0.2s; 108 + } 109 + 110 + .nav a:hover { 111 + background: rgba(255, 255, 255, 0.1); 112 + } 113 + 114 + .nav a[aria-current="page"] { 115 + background: rgba(255, 255, 255, 0.2); 116 + } 117 + 118 + .container { 119 + max-width: 800px; 120 + margin: 2rem auto; 121 + padding: 2rem; 122 + background: white; 123 + border-radius: 8px; 124 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 125 + } 126 + 127 + h1 { 128 + margin-top: 0; 129 + color: #2563eb; 130 + } 131 + 132 + form { 133 + display: flex; 134 + flex-direction: column; 135 + gap: 1rem; 136 + margin-top: 2rem; 137 + } 138 + 139 + label { 140 + display: flex; 141 + flex-direction: column; 142 + gap: 0.5rem; 143 + font-weight: 500; 144 + } 145 + 146 + input, 147 + textarea { 148 + padding: 0.75rem; 149 + border: 1px solid #ddd; 150 + border-radius: 4px; 151 + font-size: 1rem; 152 + font-family: inherit; 153 + } 154 + 155 + input:focus, 156 + textarea:focus { 157 + outline: none; 158 + border-color: #2563eb; 159 + } 160 + 161 + button[type="submit"] { 162 + padding: 0.75rem 1.5rem; 163 + background: #2563eb; 164 + color: white; 165 + border: none; 166 + border-radius: 4px; 167 + font-size: 1rem; 168 + font-weight: 500; 169 + cursor: pointer; 170 + transition: background 0.2s; 171 + align-self: flex-start; 172 + } 173 + 174 + button[type="submit"]:hover { 175 + background: #1e40af; 176 + } 177 + `; 178 + } 179 + 180 + export function generateRouterPackageJSON(projectName: string): string { 181 + return JSON.stringify( 182 + { 183 + name: projectName, 184 + version: "0.1.0", 185 + type: "module", 186 + scripts: { dev: "voltx dev", build: "voltx build" }, 187 + devDependencies: { "create-voltx": "^0.1.0" }, 188 + }, 189 + null, 190 + 2, 191 + ); 192 + } 193 + 194 + export function generateRouterREADME(projectName: string): string { 195 + return `# ${projectName} 196 + 197 + A VoltX.js application with client-side routing. 198 + 199 + ## Getting Started 200 + 201 + 1. Install dependencies: 202 + \`\`\`bash 203 + pnpm install 204 + \`\`\` 205 + 206 + 2. Start the development server: 207 + \`\`\`bash 208 + pnpm dev 209 + \`\`\` 210 + 211 + 3. Open your browser to the URL shown in the terminal. 212 + 213 + ## Features 214 + 215 + This project demonstrates: 216 + 217 + - Client-side routing with the History API 218 + - Declarative route matching with \`data-volt-url\` 219 + - Navigation with \`data-volt-navigate\` 220 + - 404 fallback pages 221 + - Clean URLs without hash routing 222 + 223 + ## Project Structure 224 + 225 + - \`index.html\` - Main HTML file with routes 226 + - \`styles.css\` - Custom styles 227 + - \`voltx.min.js\` - VoltX.js framework with router 228 + - \`voltx.min.css\` - VoltX.js base styles 229 + 230 + ## Learn More 231 + 232 + - [VoltX.js Documentation](https://stormlightlabs.github.io/volt) 233 + - [Routing Guide](https://stormlightlabs.github.io/volt/guides/routing) 234 + `; 235 + }
+158
cli/src/tests/commands.test.ts
··· 1 + import { downloadCommand } from "$commands/download.js"; 2 + import { initCommand } from "$commands/init.js"; 3 + import * as downloadUtils from "$utils/download.js"; 4 + import * as filesUtils from "$utils/files.js"; 5 + import { mkdtemp, rm } from "node:fs/promises"; 6 + import { tmpdir } from "node:os"; 7 + import path from "node:path"; 8 + import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 9 + 10 + // Mock inquirer prompts 11 + vi.mock("@inquirer/prompts", () => ({ input: vi.fn(), select: vi.fn() })); 12 + 13 + describe("CLI commands", () => { 14 + let tempDir: string; 15 + 16 + beforeEach(async () => { 17 + tempDir = await mkdtemp(path.join(tmpdir(), "voltx-cmd-test-")); 18 + vi.spyOn(downloadUtils, "downloadFile").mockResolvedValue(); 19 + vi.spyOn(filesUtils, "createFile").mockResolvedValue(); 20 + }); 21 + 22 + afterEach(async () => { 23 + await rm(tempDir, { recursive: true, force: true }).catch(() => {}); 24 + vi.clearAllMocks(); 25 + vi.restoreAllMocks(); 26 + }); 27 + 28 + describe("downloadCommand", () => { 29 + it("should download JS and CSS by default", async () => { 30 + await downloadCommand({ output: tempDir }); 31 + 32 + const downloadFileSpy = vi.mocked(downloadUtils.downloadFile); 33 + expect(downloadFileSpy).toHaveBeenCalledTimes(2); 34 + expect(downloadFileSpy).toHaveBeenCalledWith( 35 + "https://cdn.jsdelivr.net/npm/voltx.js@latest/dist/voltx.min.js", 36 + expect.stringContaining("voltx.min.js"), 37 + ); 38 + expect(downloadFileSpy).toHaveBeenCalledWith( 39 + "https://cdn.jsdelivr.net/npm/voltx.js@latest/dist/voltx.min.css", 40 + expect.stringContaining("voltx.min.css"), 41 + ); 42 + }); 43 + 44 + it("should download only JS when css is disabled", async () => { 45 + await downloadCommand({ output: tempDir, css: false }); 46 + 47 + const downloadFileSpy = vi.mocked(downloadUtils.downloadFile); 48 + expect(downloadFileSpy).toHaveBeenCalledTimes(1); 49 + expect(downloadFileSpy).toHaveBeenCalledWith( 50 + "https://cdn.jsdelivr.net/npm/voltx.js@latest/dist/voltx.min.js", 51 + expect.stringContaining("voltx.min.js"), 52 + ); 53 + }); 54 + 55 + it("should download only CSS when js is disabled", async () => { 56 + await downloadCommand({ output: tempDir, js: false }); 57 + 58 + const downloadFileSpy = vi.mocked(downloadUtils.downloadFile); 59 + expect(downloadFileSpy).toHaveBeenCalledTimes(1); 60 + expect(downloadFileSpy).toHaveBeenCalledWith( 61 + "https://cdn.jsdelivr.net/npm/voltx.js@latest/dist/voltx.min.css", 62 + expect.stringContaining("voltx.min.css"), 63 + ); 64 + }); 65 + 66 + it("should download specific version when specified", async () => { 67 + await downloadCommand({ output: tempDir, version: "0.5.0" }); 68 + 69 + const downloadFileSpy = vi.mocked(downloadUtils.downloadFile); 70 + expect(downloadFileSpy).toHaveBeenCalledWith( 71 + "https://cdn.jsdelivr.net/npm/voltx.js@0.5.0/dist/voltx.min.js", 72 + expect.stringContaining("voltx.min.js"), 73 + ); 74 + expect(downloadFileSpy).toHaveBeenCalledWith( 75 + "https://cdn.jsdelivr.net/npm/voltx.js@0.5.0/dist/voltx.min.css", 76 + expect.stringContaining("voltx.min.css"), 77 + ); 78 + }); 79 + 80 + it("should handle download errors and exit with code 1", async () => { 81 + const downloadFileSpy = vi.spyOn(downloadUtils, "downloadFile").mockRejectedValue(new Error("Network error")); 82 + const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as any); 83 + 84 + await downloadCommand({ output: tempDir }); 85 + 86 + expect(downloadFileSpy).toHaveBeenCalled(); 87 + expect(exitSpy).toHaveBeenCalledWith(1); 88 + 89 + exitSpy.mockRestore(); 90 + }); 91 + }); 92 + 93 + describe("initCommand", () => { 94 + it("should check for existing non-empty directory", async () => { 95 + const { input, select } = await import("@inquirer/prompts"); 96 + vi.mocked(input).mockResolvedValue("test-project"); 97 + vi.mocked(select).mockResolvedValue("minimal" as any); 98 + 99 + const isEmptyOrMissingSpy = vi.spyOn(filesUtils, "isEmptyOrMissing").mockResolvedValue(false); 100 + const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as any); 101 + 102 + await initCommand(); 103 + 104 + expect(isEmptyOrMissingSpy).toHaveBeenCalled(); 105 + expect(exitSpy).toHaveBeenCalledWith(1); 106 + 107 + exitSpy.mockRestore(); 108 + }); 109 + 110 + it("should create minimal template", async () => { 111 + const { select } = await import("@inquirer/prompts"); 112 + vi.mocked(select).mockResolvedValue("minimal" as any); 113 + 114 + vi.spyOn(filesUtils, "isEmptyOrMissing").mockResolvedValue(true); 115 + 116 + await initCommand("minimal-app"); 117 + 118 + expect(vi.mocked(filesUtils.createFile)).toHaveBeenCalled(); 119 + expect(vi.mocked(downloadUtils.downloadFile)).toHaveBeenCalled(); 120 + }); 121 + 122 + it("should create styles template without JS", async () => { 123 + const { select } = await import("@inquirer/prompts"); 124 + vi.mocked(select).mockResolvedValue("styles" as any); 125 + 126 + vi.spyOn(filesUtils, "isEmptyOrMissing").mockResolvedValue(true); 127 + 128 + await initCommand("styles-app"); 129 + 130 + const downloadSpy = vi.mocked(downloadUtils.downloadFile); 131 + const calls = downloadSpy.mock.calls; 132 + expect(calls.some((call) => call[0].includes("voltx.min.css"))).toBe(true); 133 + expect(calls.some((call) => call[0].includes("voltx.min.js"))).toBe(false); 134 + }); 135 + 136 + it("should create with-router template", async () => { 137 + const { select } = await import("@inquirer/prompts"); 138 + vi.mocked(select).mockResolvedValue("with-router" as any); 139 + 140 + vi.spyOn(filesUtils, "isEmptyOrMissing").mockResolvedValue(true); 141 + 142 + await initCommand("router-app"); 143 + 144 + expect(vi.mocked(filesUtils.createFile)).toHaveBeenCalled(); 145 + }); 146 + 147 + it("should create with-plugins template", async () => { 148 + const { select } = await import("@inquirer/prompts"); 149 + vi.mocked(select).mockResolvedValue("with-plugins" as any); 150 + 151 + vi.spyOn(filesUtils, "isEmptyOrMissing").mockResolvedValue(true); 152 + 153 + await initCommand("plugins-app"); 154 + 155 + expect(vi.mocked(filesUtils.createFile)).toHaveBeenCalled(); 156 + }); 157 + }); 158 + });
+27
cli/src/tests/download.test.ts
··· 1 + import { getCDNUrls } from "$utils/download.js"; 2 + import { describe, expect, it } from "vitest"; 3 + 4 + describe("download utilities", () => { 5 + describe("getCDNUrls", () => { 6 + it("should return latest URLs when no version specified", () => { 7 + const urls = getCDNUrls(); 8 + 9 + expect(urls.js).toBe("https://cdn.jsdelivr.net/npm/voltx.js@latest/dist/voltx.min.js"); 10 + expect(urls.css).toBe("https://cdn.jsdelivr.net/npm/voltx.js@latest/dist/voltx.min.css"); 11 + }); 12 + 13 + it("should return latest URLs when 'latest' is specified", () => { 14 + const urls = getCDNUrls("latest"); 15 + 16 + expect(urls.js).toBe("https://cdn.jsdelivr.net/npm/voltx.js@latest/dist/voltx.min.js"); 17 + expect(urls.css).toBe("https://cdn.jsdelivr.net/npm/voltx.js@latest/dist/voltx.min.css"); 18 + }); 19 + 20 + it("should return versioned URLs when version is specified", () => { 21 + const urls = getCDNUrls("1.0.0"); 22 + 23 + expect(urls.js).toBe("https://cdn.jsdelivr.net/npm/voltx.js@1.0.0/dist/voltx.min.js"); 24 + expect(urls.css).toBe("https://cdn.jsdelivr.net/npm/voltx.js@1.0.0/dist/voltx.min.css"); 25 + }); 26 + }); 27 + });
+87
cli/src/tests/echo.test.ts
··· 1 + import { echo } from "$utils/echo.js"; 2 + import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 3 + 4 + describe("echo utility", () => { 5 + let consoleLogSpy: ReturnType<typeof vi.spyOn>; 6 + let consoleErrorSpy: ReturnType<typeof vi.spyOn>; 7 + let consoleWarnSpy: ReturnType<typeof vi.spyOn>; 8 + 9 + beforeEach(() => { 10 + consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); 11 + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); 12 + consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); 13 + }); 14 + 15 + afterEach(() => { 16 + consoleLogSpy.mockRestore(); 17 + consoleErrorSpy.mockRestore(); 18 + consoleWarnSpy.mockRestore(); 19 + }); 20 + 21 + describe("err", () => { 22 + it("should log to stderr", () => { 23 + echo.err("Error message"); 24 + expect(consoleErrorSpy).toHaveBeenCalledWith(expect.any(String)); 25 + }); 26 + 27 + it("should accept additional parameters", () => { 28 + echo.err("Error:", "details", 123); 29 + expect(consoleErrorSpy).toHaveBeenCalledWith(expect.any(String), "details", 123); 30 + }); 31 + }); 32 + 33 + describe("danger", () => { 34 + it("should log to stdout", () => { 35 + echo.danger("Danger message"); 36 + expect(consoleLogSpy).toHaveBeenCalledWith(expect.any(String)); 37 + }); 38 + }); 39 + 40 + describe("ok", () => { 41 + it("should log success message", () => { 42 + echo.ok("Success message"); 43 + expect(consoleLogSpy).toHaveBeenCalledWith(expect.any(String)); 44 + }); 45 + }); 46 + 47 + describe("success", () => { 48 + it("should log bold success message", () => { 49 + echo.success("Success!"); 50 + expect(consoleLogSpy).toHaveBeenCalledWith(expect.any(String)); 51 + }); 52 + }); 53 + 54 + describe("info", () => { 55 + it("should log info message", () => { 56 + echo.info("Info message"); 57 + expect(consoleLogSpy).toHaveBeenCalledWith(expect.any(String)); 58 + }); 59 + }); 60 + 61 + describe("label", () => { 62 + it("should log label message", () => { 63 + echo.label("Label"); 64 + expect(consoleLogSpy).toHaveBeenCalledWith(expect.any(String)); 65 + }); 66 + }); 67 + 68 + describe("title", () => { 69 + it("should log bold title", () => { 70 + echo.title("Title"); 71 + expect(consoleLogSpy).toHaveBeenCalledWith(expect.any(String)); 72 + }); 73 + }); 74 + 75 + describe("warn", () => { 76 + it("should log warning", () => { 77 + echo.warn("Warning message"); 78 + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.any(String)); 79 + }); 80 + }); 81 + 82 + describe("text", () => { 83 + it("should be a reference to console.log", () => { 84 + expect(typeof echo.text).toBe("function"); 85 + }); 86 + }); 87 + });
+64
cli/src/tests/files.test.ts
··· 1 + import { createFile, isEmptyOrMissing } from "$utils/files.js"; 2 + import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises"; 3 + import { tmpdir } from "node:os"; 4 + import path from "node:path"; 5 + import { afterEach, beforeEach, describe, expect, it } from "vitest"; 6 + 7 + describe("files utilities", () => { 8 + let tempDir: string; 9 + 10 + beforeEach(async () => { 11 + tempDir = await mkdtemp(path.join(tmpdir(), "voltx-test-")); 12 + }); 13 + 14 + afterEach(async () => { 15 + await rm(tempDir, { recursive: true, force: true }); 16 + }); 17 + 18 + describe("createFile", () => { 19 + it("should create a file with content", async () => { 20 + const filePath = path.join(tempDir, "test.txt"); 21 + const content = "Hello VoltX!"; 22 + 23 + await createFile(filePath, content); 24 + 25 + const fileContent = await readFile(filePath, "utf8"); 26 + expect(fileContent).toBe(content); 27 + }); 28 + 29 + it("should create parent directories if they don't exist", async () => { 30 + const filePath = path.join(tempDir, "nested", "deep", "test.txt"); 31 + const content = "Nested file"; 32 + 33 + await createFile(filePath, content); 34 + 35 + const fileContent = await readFile(filePath, "utf8"); 36 + expect(fileContent).toBe(content); 37 + }); 38 + }); 39 + 40 + describe("isEmptyOrMissing", () => { 41 + it("should return true for non-existent directory", async () => { 42 + const nonExistentDir = path.join(tempDir, "does-not-exist"); 43 + const result = await isEmptyOrMissing(nonExistentDir); 44 + expect(result).toBe(true); 45 + }); 46 + 47 + it("should return true for empty directory", async () => { 48 + const emptyDir = path.join(tempDir, "empty"); 49 + await mkdir(emptyDir); 50 + 51 + const result = await isEmptyOrMissing(emptyDir); 52 + expect(result).toBe(true); 53 + }); 54 + 55 + it("should return false for directory with files", async () => { 56 + const dirWithFiles = path.join(tempDir, "with-files"); 57 + await mkdir(dirWithFiles); 58 + await createFile(path.join(dirWithFiles, "test.txt"), "content"); 59 + 60 + const result = await isEmptyOrMissing(dirWithFiles); 61 + expect(result).toBe(false); 62 + }); 63 + }); 64 + });
+146
cli/src/tests/templates.test.ts
··· 1 + import { describe, it, expect } from "vitest"; 2 + import { 3 + generateMinimalHTML, 4 + generateMinimalCSS, 5 + generateMinimalPackageJSON, 6 + generateMinimalREADME, 7 + } from "$templates/minimal.js"; 8 + import { 9 + generateStylesHTML, 10 + generateStylesCSS, 11 + generateStylesPackageJSON, 12 + generateStylesREADME, 13 + } from "$templates/styles.js"; 14 + import { 15 + generateRouterHTML, 16 + generateRouterCSS, 17 + generateRouterPackageJSON, 18 + generateRouterREADME, 19 + } from "$templates/with-router.js"; 20 + import { 21 + generatePluginsHTML, 22 + generatePluginsCSS, 23 + generatePluginsPackageJSON, 24 + generatePluginsREADME, 25 + } from "$templates/with-plugins.js"; 26 + 27 + describe("template generators", () => { 28 + const projectName = "test-project"; 29 + 30 + describe("minimal template", () => { 31 + it("should generate HTML with project name", () => { 32 + const html = generateMinimalHTML(projectName); 33 + expect(html).toContain(projectName); 34 + expect(html).toContain("data-volt"); 35 + expect(html).toContain("voltx.min.js"); 36 + }); 37 + 38 + it("should generate CSS", () => { 39 + const css = generateMinimalCSS(); 40 + expect(css).toContain(".container"); 41 + expect(css).toContain(".counter"); 42 + }); 43 + 44 + it("should generate valid package.json", () => { 45 + const packageJson = generateMinimalPackageJSON(projectName); 46 + const parsed = JSON.parse(packageJson); 47 + expect(parsed.name).toBe(projectName); 48 + expect(parsed.scripts.dev).toBe("voltx dev"); 49 + expect(parsed.scripts.build).toBe("voltx build"); 50 + }); 51 + 52 + it("should generate README with project name", () => { 53 + const readme = generateMinimalREADME(projectName); 54 + expect(readme).toContain(projectName); 55 + expect(readme).toContain("pnpm dev"); 56 + }); 57 + }); 58 + 59 + describe("styles template", () => { 60 + it("should generate HTML without VoltX.js framework", () => { 61 + const html = generateStylesHTML(projectName); 62 + expect(html).toContain(projectName); 63 + expect(html).toContain("voltx.min.css"); 64 + expect(html).not.toContain("voltx.min.js"); 65 + expect(html).not.toContain("data-volt"); 66 + }); 67 + 68 + it("should generate CSS", () => { 69 + const css = generateStylesCSS(); 70 + expect(css).toContain(".container"); 71 + expect(css).toContain(".card"); 72 + }); 73 + 74 + it("should generate valid package.json", () => { 75 + const packageJson = generateStylesPackageJSON(projectName); 76 + const parsed = JSON.parse(packageJson); 77 + expect(parsed.name).toBe(projectName); 78 + }); 79 + 80 + it("should generate README explaining styles-only approach", () => { 81 + const readme = generateStylesREADME(projectName); 82 + expect(readme).toContain(projectName); 83 + expect(readme).toContain("styles-only"); 84 + }); 85 + }); 86 + 87 + describe("router template", () => { 88 + it("should generate HTML with routing", () => { 89 + const html = generateRouterHTML(projectName); 90 + expect(html).toContain(projectName); 91 + expect(html).toContain("data-volt-navigate"); 92 + expect(html).toContain("data-volt-url"); 93 + expect(html).toContain("navigatePlugin"); 94 + }); 95 + 96 + it("should generate CSS with navigation styles", () => { 97 + const css = generateRouterCSS(); 98 + expect(css).toContain(".nav"); 99 + }); 100 + 101 + it("should generate valid package.json", () => { 102 + const packageJson = generateRouterPackageJSON(projectName); 103 + const parsed = JSON.parse(packageJson); 104 + expect(parsed.name).toBe(projectName); 105 + }); 106 + 107 + it("should generate README explaining routing", () => { 108 + const readme = generateRouterREADME(projectName); 109 + expect(readme).toContain(projectName); 110 + expect(readme).toContain("routing"); 111 + }); 112 + }); 113 + 114 + describe("plugins template", () => { 115 + it("should generate HTML with all plugins", () => { 116 + const html = generatePluginsHTML(projectName); 117 + expect(html).toContain(projectName); 118 + expect(html).toContain("persistPlugin"); 119 + expect(html).toContain("scrollPlugin"); 120 + expect(html).toContain("urlPlugin"); 121 + expect(html).toContain("surgePlugin"); 122 + expect(html).toContain("navigatePlugin"); 123 + }); 124 + 125 + it("should generate CSS for plugin demos", () => { 126 + const css = generatePluginsCSS(); 127 + expect(css).toContain(".card"); 128 + expect(css).toContain(".counter"); 129 + expect(css).toContain(".animated-box"); 130 + }); 131 + 132 + it("should generate valid package.json", () => { 133 + const packageJson = generatePluginsPackageJSON(projectName); 134 + const parsed = JSON.parse(packageJson); 135 + expect(parsed.name).toBe(projectName); 136 + }); 137 + 138 + it("should generate README listing all plugins", () => { 139 + const readme = generatePluginsREADME(projectName); 140 + expect(readme).toContain(projectName); 141 + expect(readme).toContain("Persist Plugin"); 142 + expect(readme).toContain("Scroll Plugin"); 143 + expect(readme).toContain("URL Plugin"); 144 + }); 145 + }); 146 + });
+56
cli/src/utils/download.ts
··· 1 + import { mkdir, writeFile } from "node:fs/promises"; 2 + import https from "node:https"; 3 + import path from "node:path"; 4 + 5 + /** 6 + * Download a file from a URL and save it to the specified path. 7 + */ 8 + export async function downloadFile(url: string, outputPath: string): Promise<void> { 9 + const dir = path.dirname(outputPath); 10 + await mkdir(dir, { recursive: true }); 11 + 12 + return new Promise((resolve, reject) => { 13 + https.get(url, (response) => { 14 + if (response.statusCode === 302 || response.statusCode === 301) { 15 + if (response.headers.location) { 16 + downloadFile(response.headers.location, outputPath).then(resolve).catch(reject); 17 + return; 18 + } 19 + } 20 + 21 + if (response.statusCode !== 200) { 22 + reject(new Error(`Failed to download: ${response.statusCode} ${response.statusMessage}`)); 23 + return; 24 + } 25 + 26 + const chunks: Buffer[] = []; 27 + 28 + response.on("data", (chunk) => { 29 + chunks.push(chunk); 30 + }); 31 + 32 + response.on("end", async () => { 33 + try { 34 + const buffer = Buffer.concat(chunks); 35 + await writeFile(outputPath, buffer); 36 + resolve(); 37 + } catch (error) { 38 + reject(error); 39 + } 40 + }); 41 + 42 + response.on("error", reject); 43 + }).on("error", reject); 44 + }); 45 + } 46 + 47 + /** 48 + * Get the CDN URLs for VoltX.js assets. 49 + */ 50 + export function getCDNUrls(version: string = "latest"): { js: string; css: string } { 51 + const baseUrl = version === "latest" 52 + ? "https://cdn.jsdelivr.net/npm/voltx.js@latest/dist" 53 + : `https://cdn.jsdelivr.net/npm/voltx.js@${version}/dist`; 54 + 55 + return { js: `${baseUrl}/voltx.min.js`, css: `${baseUrl}/voltx.min.css` }; 56 + }
+40
cli/src/utils/echo.ts
··· 1 + import chalk from "chalk"; 2 + 3 + type Echo = Record< 4 + "info" | "success" | "ok" | "warn" | "text" | "err" | "danger" | "label" | "title", 5 + (message?: any, ...optionalParams: any[]) => void 6 + >; 7 + 8 + export const echo: Echo = { 9 + /** 10 + * Red text to stderr 11 + */ 12 + err(message, ...optionalParams) { 13 + console.error(chalk.red(message), ...optionalParams); 14 + }, 15 + /** 16 + * Red text for recoverable errors (to stdout) 17 + */ 18 + danger(message, ...optionalParams) { 19 + console.log(chalk.red(message), ...optionalParams); 20 + }, 21 + ok(message, ...optionalParams) { 22 + console.log(chalk.green(message), ...optionalParams); 23 + }, 24 + success(message, ...optionalParams) { 25 + console.log(chalk.green.bold(message), ...optionalParams); 26 + }, 27 + info(message, ...optionalParams) { 28 + console.log(chalk.cyan(message), ...optionalParams); 29 + }, 30 + label(message, ...optionalParams) { 31 + console.log(chalk.blue(message), ...optionalParams); 32 + }, 33 + title(message, ...optionalParams) { 34 + console.log(chalk.blue.bold(message), ...optionalParams); 35 + }, 36 + warn(message, ...optionalParams) { 37 + console.warn(chalk.yellow(message), ...optionalParams); 38 + }, 39 + text: console.log, 40 + };
+28
cli/src/utils/files.ts
··· 1 + import { mkdir, writeFile } from "node:fs/promises"; 2 + import path from "node:path"; 3 + 4 + /** 5 + * Create a file with the given content at the specified path. 6 + * 7 + * Creates parent directories if they don't exist. 8 + */ 9 + export async function createFile(filePath: string, content: string): Promise<void> { 10 + const dir = path.dirname(filePath); 11 + await mkdir(dir, { recursive: true }); 12 + await writeFile(filePath, content, "utf8"); 13 + } 14 + 15 + /** 16 + * Check if a directory is empty or doesn't exist. 17 + */ 18 + export async function isEmptyOrMissing(dirPath: string): Promise<boolean> { 19 + const { existsSync } = await import("node:fs"); 20 + const { readdir } = await import("node:fs/promises"); 21 + 22 + if (!existsSync(dirPath)) { 23 + return true; 24 + } 25 + 26 + const files = await readdir(dirPath); 27 + return files.length === 0; 28 + }
+25
cli/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "lib": ["ES2022"], 6 + "moduleResolution": "bundler", 7 + "resolveJsonModule": true, 8 + "allowJs": true, 9 + "strict": true, 10 + "esModuleInterop": true, 11 + "skipLibCheck": true, 12 + "forceConsistentCasingInFileNames": true, 13 + "declaration": true, 14 + "declarationMap": true, 15 + "outDir": "./dist", 16 + "rootDir": "./src", 17 + "paths": { 18 + "$commands/*": ["./src/commands/*"], 19 + "$templates/*": ["./src/templates/*"], 20 + "$utils/*": ["./src/utils/*"] 21 + } 22 + }, 23 + "include": ["src/**/*"], 24 + "exclude": ["node_modules", "dist", "tests"] 25 + }
+12
cli/tsdown.config.ts
··· 1 + import { defineConfig } from "tsdown"; 2 + 3 + export default defineConfig({ 4 + entry: ["src/index.ts"], 5 + format: ["esm"], 6 + clean: true, 7 + dts: true, 8 + shims: true, 9 + platform: "node", 10 + target: "node18", 11 + tsconfig: "./tsconfig.json", 12 + });
+24
cli/vitest.config.ts
··· 1 + import path from "node:path"; 2 + import { defineConfig } from "vitest/config"; 3 + 4 + export default defineConfig({ 5 + test: { 6 + globals: true, 7 + environment: "node", 8 + coverage: { 9 + provider: "v8", 10 + reporter: ["text", "html", "lcov"], 11 + include: ["src/**/*.ts"], 12 + exclude: ["src/tests/**", "src/**/*.test.ts", "src/index.ts", "src/commands/dev.ts", "src/commands/build.ts"], 13 + all: true, 14 + thresholds: { lines: 95, functions: 95, branches: 80, statements: 95 }, 15 + }, 16 + }, 17 + resolve: { 18 + alias: { 19 + "$commands": path.resolve(__dirname, "./src/commands"), 20 + "$templates": path.resolve(__dirname, "./src/templates"), 21 + "$utils": path.resolve(__dirname, "./src/utils"), 22 + }, 23 + }, 24 + });
+3 -3
docs/.vitepress/config.ts
··· 30 30 { 31 31 text: "Getting Started", 32 32 items: 33 - ([{ text: "Overview", link: "/overview" }, { 34 - text: "Installation", 35 - link: "/installation", 33 + ([{ text: "Overview", link: "/overview" }, { text: "Installation", link: "/installation" }, { 34 + text: "CLI", 35 + link: "/cli", 36 36 }] as DefaultTheme.SidebarItem[]).concat(...u.scanDir("usage", "/usage")), 37 37 }, 38 38 {
+1 -1
docs/.vitepress/theme/index.ts
··· 2 2 import type { Theme } from "vitepress"; 3 3 import DefaultTheme from "vitepress/theme"; 4 4 import { h } from "vue"; 5 - import "./style.css"; 5 + import "@catppuccin/vitepress/theme/mocha/green.css"; 6 6 7 7 export default { 8 8 extends: DefaultTheme,
+2
docs/.vitepress/theme/style.css
··· 1 1 /** 2 2 * Customize default theme styling by overriding CSS variables: 3 3 * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css 4 + * 5 + * TODO: prune 4 6 */ 5 7 6 8 /**
+293
docs/cli.md
··· 1 + # CLI 2 + 3 + โš ๏ธ This CLI is unreleased as of writing. The documentation may change before the CLI is published in v0.6.0. 4 + 5 + The VoltX.js CLI provides tools for creating and managing VoltX.js applications. Use it to scaffold new projects, run development servers, build for production, and download framework assets. 6 + 7 + ## Installation 8 + 9 + The CLI is available as `create-voltx` on npm: 10 + 11 + ```bash 12 + # Use with pnpm 13 + pnpm create voltx my-app 14 + 15 + # Use with npm 16 + npm create voltx@latest my-app 17 + 18 + # Use with npx 19 + npx create-voltx my-app 20 + ``` 21 + 22 + For ongoing development commands, install the CLI in your project: 23 + 24 + ```bash 25 + pnpm add -D create-voltx 26 + ``` 27 + 28 + ## Commands 29 + 30 + ### `init` 31 + 32 + Create a new VoltX.js project with an interactive template selector. 33 + 34 + ```bash 35 + # Interactive mode - prompts for project name and template 36 + pnpm create voltx 37 + 38 + # Specify project name 39 + pnpm create voltx my-app 40 + 41 + # Using the voltx command 42 + voltx init my-app 43 + ``` 44 + 45 + **Templates:** 46 + 47 + - **Minimal** - Basic VoltX.js app with a counter demo 48 + - **With Router** - Multi-page app with client-side routing 49 + - **With Plugins** - Demonstration of all VoltX.js plugins 50 + - **Styles Only** - HTML + CSS using VoltX.js styles without the framework 51 + 52 + The init command: 53 + 54 + - Creates project directory 55 + - Generates HTML, CSS, package.json, and README 56 + - Downloads latest VoltX.js assets from CDN 57 + - Sets up development scripts 58 + 59 + **Generated Structure:** 60 + 61 + ```sh 62 + my-app/ 63 + โ”œโ”€โ”€ index.html # Main HTML file 64 + โ”œโ”€โ”€ styles.css # Custom styles 65 + โ”œโ”€โ”€ package.json # Project configuration 66 + โ”œโ”€โ”€ README.md # Getting started guide 67 + โ”œโ”€โ”€ voltx.min.js # VoltX.js framework 68 + โ””โ”€โ”€ voltx.min.css # VoltX.js base styles 69 + ``` 70 + 71 + ### `dev` 72 + 73 + Start a Vite development server for your VoltX.js project. 74 + 75 + ```bash 76 + voltx dev 77 + ``` 78 + 79 + **Options:** 80 + 81 + - `-p, --port <port>` - Port to run the dev server on (default: 3000) 82 + - `-o, --open` - Open browser automatically 83 + 84 + **Examples:** 85 + 86 + ```bash 87 + # Start dev server on default port (3000) 88 + voltx dev 89 + 90 + # Use custom port 91 + voltx dev --port 8080 92 + 93 + # Open browser automatically 94 + voltx dev --open 95 + ``` 96 + 97 + The dev server provides: 98 + 99 + - Hot module replacement (HMR) 100 + - Fast refresh for development 101 + - Automatic browser reload 102 + - HTTPS support (via Vite) 103 + 104 + ### `build` 105 + 106 + Build your VoltX.js project for production. 107 + 108 + ```bash 109 + voltx build 110 + ``` 111 + 112 + **Options:** 113 + 114 + - `--out <dir>` - Output directory (default: dist) 115 + 116 + **Examples:** 117 + 118 + ```bash 119 + # Build to default dist/ directory 120 + voltx build 121 + 122 + # Build to custom directory 123 + voltx build --out public 124 + ``` 125 + 126 + The build command: 127 + 128 + - Minifies HTML, CSS, and JavaScript 129 + - Optimizes assets for production 130 + - Generates source maps 131 + - Creates optimized bundle 132 + 133 + ### `download` 134 + 135 + Download VoltX.js assets (JS and CSS) from the CDN. 136 + 137 + ```bash 138 + voltx download 139 + ``` 140 + 141 + **Options:** 142 + 143 + - `--version <version>` - VoltX.js version to download (default: latest) 144 + - `--no-js` - Skip downloading JavaScript file 145 + - `--no-css` - Skip downloading CSS file 146 + - `-o, --output <dir>` - Output directory (default: current directory) 147 + 148 + **Examples:** 149 + 150 + ```bash 151 + # Download latest JS and CSS 152 + voltx download 153 + 154 + # Download specific version 155 + voltx download --version 0.5.0 156 + 157 + # Download only CSS 158 + voltx download --no-js 159 + 160 + # Download to custom directory 161 + voltx download --output assets 162 + ``` 163 + 164 + This command downloads minified assets from the jsDelivr CDN. 165 + 166 + ## Workflows 167 + 168 + ### Creating a New Project 169 + 170 + ```bash 171 + # Create new project 172 + pnpm create voltx my-app 173 + 174 + # Navigate to project 175 + cd my-app 176 + 177 + # Install dependencies 178 + pnpm install 179 + 180 + # Start dev server 181 + pnpm dev 182 + ``` 183 + 184 + ### Development 185 + 186 + ```bash 187 + # Start dev server (watches for changes) 188 + pnpm dev 189 + 190 + # In another terminal, run tests if available 191 + pnpm test 192 + ``` 193 + 194 + ### Production Build 195 + 196 + ```bash 197 + # Build for production 198 + pnpm build 199 + 200 + # Preview production build locally 201 + npx vite preview --outDir dist 202 + ``` 203 + 204 + ### Updating VoltX.js Assets 205 + 206 + ```bash 207 + # Download latest version 208 + voltx download 209 + 210 + # Or download specific version 211 + voltx download --version 0.5.1 212 + ``` 213 + 214 + ## Project Scripts 215 + 216 + All generated projects include these npm scripts: 217 + 218 + ```json 219 + { 220 + "scripts": { 221 + "dev": "voltx dev", 222 + "build": "voltx build" 223 + } 224 + } 225 + ``` 226 + 227 + Run them with your package manager: 228 + 229 + ```bash 230 + pnpm dev 231 + pnpm build 232 + ``` 233 + 234 + ## Templates 235 + 236 + ### Minimal 237 + 238 + A basic VoltX.js application demonstrating: 239 + 240 + - Declarative state with `data-volt-state` 241 + - Reactive bindings 242 + - Event handlers 243 + - Counter example 244 + 245 + ### With Router 246 + 247 + A multi-page application featuring: 248 + 249 + - Client-side routing with History API 250 + - Multiple routes (home, about, contact, 404) 251 + - Navigation with `data-volt-navigate` 252 + - Route matching with `data-volt-url` 253 + 254 + ### With Plugins 255 + 256 + A comprehensive demo showcasing: 257 + 258 + - Persist plugin (localStorage sync) 259 + - Scroll plugin (smooth scrolling) 260 + - URL plugin (URL parameter sync) 261 + - Surge plugin (animations) 262 + - Navigate plugin (routing) 263 + 264 + Best for: Learning all VoltX.js features, reference implementation. 265 + 266 + ### Styles Only 267 + 268 + HTML and CSS using VoltX.js styles without the reactive framework: 269 + 270 + - VoltX.js CSS utilities 271 + - Semantic HTML 272 + - No JavaScript required 273 + 274 + ## Configuration 275 + 276 + ### Vite Configuration 277 + 278 + The CLI uses Vite as the dev server and build tool. To customize Vite, create a `vite.config.js` in your project root: 279 + 280 + ```js 281 + import { defineConfig } from 'vite'; 282 + 283 + export default defineConfig({ 284 + server: { 285 + port: 3000, 286 + }, 287 + build: { 288 + outDir: 'dist', 289 + }, 290 + }); 291 + ``` 292 + 293 + See the [Vite documentation](https://vitejs.dev/config/) for all available options.
+13
docs/installation.md
··· 26 26 </script> 27 27 ``` 28 28 29 + ## CLI (โš ๏ธ Unreleased as of v0.5.0) 30 + 31 + The fastest way to create a new VoltX.js project is with the CLI: 32 + 33 + ```bash 34 + pnpm create voltx my-app 35 + cd my-app 36 + pnpm install 37 + pnpm dev 38 + ``` 39 + 40 + This scaffolds a complete project with development server, build tools, and framework assets. See the [CLI Guide](./cli) for all available commands and templates. 41 + 29 42 ## Package Manager 30 43 31 44 For applications using node based tools, install VoltX.js via npm or JSR:
+24
docs/overview.md
··· 41 41 - TypeScript source 42 42 - Every feature tested 43 43 44 + ## Concepts 45 + 46 + | Concept | Description | 47 + | -------- | --------------------------------------------------------------------------------------------------------- | 48 + | Signals | Reactive primitives that automatically update DOM bindings when changed. | 49 + | Bindings | `data-volt-text`, `data-volt-html`, `data-volt-class` connect attributes or text to expressions. | 50 + | Actions | `data-volt-on-click`, `data-volt-on-input`, etc. attach event handlers declaratively. | 51 + | Streams | `data-volt-stream="/events"` listens for SSE or WebSocket updates and applies JSON patches. | 52 + | Plugins | Modular extensions (`data-volt-persist`, `data-volt-surge`, `data-volt-shift`, etc.) to enhance the core. | 53 + 54 + ## VoltX.css 55 + 56 + VoltX ships with an optional classless CSS framework inspired by Pico CSS and Tufte CSS. It provides beautiful, semantic styling for HTML elements without requiring any CSS classesโ€”just write semantic markup and it looks great out of the box. 57 + 58 + Features include typography with modular scale, Tufte-style sidenotes, styled form elements, dialogs, accordions, tooltips, tables, and more. 59 + 60 + Here are some highlights 61 + 62 + ![VoltX Typography](./images/voltx-css_typography.png) 63 + 64 + ![VoltX Structured Content](./images/voltx-css_structured-content.png) 65 + 66 + ![VoltX Components](./images/voltx-css_components.png) 67 + 44 68 ## Browser Support 45 69 46 70 Modern browsers (Chrome 90+, Firefox 88+, Safari 14+) with support for:
+3 -2
docs/package.json
··· 1 1 { 2 2 "name": "@voltx/docs", 3 - "version": "0.4.0", 3 + "version": "0.5.1", 4 4 "private": true, 5 5 "type": "module", 6 6 "scripts": { "dev": "vitepress dev", "build": "vitepress build", "preview": "vitepress preview" }, 7 - "devDependencies": { "esbuild": "^0.25.11", "vitepress": "^1.6.4", "vue": "^3.5.22" } 7 + "devDependencies": { "esbuild": "^0.25.11", "vitepress": "^1.6.4", "vue": "^3.5.22" }, 8 + "dependencies": { "@catppuccin/vitepress": "^0.1.2" } 8 9 }
+201
docs/usage/error-handling.md
··· 1 + # Error Handling 2 + 3 + โš ๏ธ Named error classes and enhanced error handling are unreleased as of writing. This documentation describes features planned for v0.6.0. 4 + 5 + VoltX categorizes all errors by source and severity, wrapping them in named error classes with rich debugging context. This system enables precise error identification, flexible handling strategies, and seamless integration with logging services. 6 + 7 + ## Error Types 8 + 9 + Each error source maps to a specific error class: 10 + 11 + | Identifier | Source | Cause | 12 + | ---------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------- | 13 + | `EvaluatorError` | `evaluator` | Expression evaluation fails in directives (`data-volt-text`, `data-volt-if`, etc.) or computed values | 14 + | `BindingError` | `binding` | Directive setup or execution fails (`data-volt-model` with missing signal, invalid `data-volt-for` syntax, missing parent elements) | 15 + | `EffectError` | `effect` | Effect callbacks, computed signals, or async effects fail during execution or cleanup | 16 + | `HttpError` | `http` | HTTP directives encounter network errors, invalid swap strategies, missing target elements, or parsing failures | 17 + | `PluginError` | `plugin` | Custom plugin handlers fail during initialization or execution | 18 + | `LifecycleError` | `lifecycle` | Lifecycle hooks (`beforeMount`, `afterMount`, `onMount`, etc.) fail during execution | 19 + | `ChargeError` | `charge` | `charge()` encounters invalid `data-volt-state` JSON, malformed configuration, or initialization errors | 20 + | `UserError` | `user` | User code explicitly reports errors via `report()` | 21 + 22 + All error classes extend `VoltError` and set their `name` property accordingly (e.g., `error.name === "HttpError"`). 23 + 24 + ## Severity Levels 25 + 26 + Errors have three severity levels that control console output and execution flow: 27 + 28 + **warn** โ€” Non-critical issues logged via `console.warn`. Execution continues. Use for deprecations, missing optional features, or recoverable configuration issues. 29 + 30 + **error** (default) โ€” Recoverable errors logged via `console.error`. Execution continues but the specific operation fails. Most runtime errors use this level. 31 + 32 + **fatal** โ€” Unrecoverable errors logged via `console.error` and then thrown, halting execution. Reserve for critical initialization failures or corrupted state. 33 + 34 + ## Error Context 35 + 36 + Every VoltX error includes contextual metadata for debugging: 37 + 38 + ```ts 39 + interface VoltError { 40 + name: string; // Error class name 41 + source: ErrorSource; // Error category 42 + level: ErrorLevel; // Severity level 43 + directive?: string; // Failed directive (e.g., "data-volt-text") 44 + expression?: string; // Failed expression 45 + element?: HTMLElement; // DOM element where error occurred 46 + cause: Error; // Original wrapped error 47 + timestamp: number; // Unix timestamp (ms) 48 + context: ErrorContext; // Full context including custom properties 49 + 50 + // HTTP errors only 51 + httpMethod?: string; 52 + httpUrl?: string; 53 + httpStatus?: number; 54 + 55 + // Plugin errors only 56 + pluginName?: string; 57 + 58 + // Lifecycle errors only 59 + hookName?: string; 60 + } 61 + ``` 62 + 63 + ## Handling Errors 64 + 65 + ### Registration 66 + 67 + Register global error handlers with `onError()`. Handlers execute in registration order and receive all errors: 68 + 69 + ```ts 70 + import { onError } from "voltx.js"; 71 + 72 + const cleanup = onError((error) => { 73 + analytics.track("error", error.toJSON()); 74 + 75 + if (error instanceof HttpError) { 76 + showToast(`Request failed: ${error.cause.message}`); 77 + } 78 + }); 79 + 80 + // Cleanup when done 81 + cleanup(); 82 + ``` 83 + 84 + ### Propagation Control 85 + 86 + Call `error.stopPropagation()` to prevent subsequent handlers from running: 87 + 88 + ```ts 89 + onError((error) => { 90 + if (error.source === "http") { 91 + handleHttpError(error); 92 + error.stopPropagation(); 93 + } 94 + }); 95 + 96 + // This handler won't run for HTTP errors 97 + onError((error) => logToConsole(error)); 98 + ``` 99 + 100 + ### Cleanup 101 + 102 + Remove handlers individually via their cleanup function or clear all handlers with `clearErrorHandlers()`. 103 + 104 + ## Console Fallback 105 + 106 + When no handlers are registered, errors log to console based on severity (`console.warn` for warn, `console.error` for error/fatal) with formatted context: 107 + 108 + ```text 109 + [ERROR] [evaluator] Cannot read property 'foo' of undefined | Directive: data-volt-text | Expression: user.foo | Element: <div#app> 110 + Caused by: TypeError: Cannot read property 'foo' of undefined 111 + Element: <div id="app">...</div> 112 + ``` 113 + 114 + Fatal errors throw after logging. 115 + 116 + ## Reporting Errors 117 + 118 + Report errors from user code using `report(error, context)`: 119 + 120 + ```ts 121 + import { report } from "voltx.js"; 122 + 123 + try { 124 + processForm(formElement); 125 + } catch (error) { 126 + report(error as Error, { 127 + source: "user", 128 + level: "warn", 129 + element: formElement as HTMLElement, 130 + formId: formElement.id, 131 + }); 132 + } 133 + ``` 134 + 135 + The `context` object accepts any custom properties beyond the standard fields. 136 + 137 + ## Serialization 138 + 139 + All VoltX errors implement `toJSON()` for serialization to logging services or error tracking systems. 140 + 141 + ### Schema 142 + 143 + ```ts 144 + interface SerializedVoltError { 145 + /** Error class name (e.g., "EvaluatorError", "HttpError") */ 146 + name: string; 147 + /** Full formatted error message with context */ 148 + message: string; 149 + /** Error source category */ 150 + source: 151 + | "evaluator" 152 + | "binding" 153 + | "effect" 154 + | "http" 155 + | "plugin" 156 + | "lifecycle" 157 + | "charge" 158 + | "user"; 159 + 160 + /** Severity level */ 161 + level: "warn" | "error" | "fatal"; 162 + /** Directive name (e.g., "data-volt-text") */ 163 + directive?: string; 164 + /** Expression that failed */ 165 + expression?: string; 166 + /** Unix timestamp in milliseconds */ 167 + timestamp: number; 168 + /** Full error context including custom properties */ 169 + context: { 170 + source: string; 171 + level?: string; 172 + element?: HTMLElement; 173 + directive?: string; 174 + expression?: string; 175 + pluginName?: string; 176 + httpMethod?: string; 177 + httpUrl?: string; 178 + httpStatus?: number; 179 + hookName?: string; 180 + [key: string]: unknown; 181 + }; 182 + /** Original error that was wrapped */ 183 + cause: { name: string; message: string; stack?: string; }; 184 + /** VoltX error stack trace */ 185 + stack?: string; 186 + } 187 + ``` 188 + 189 + ### Usage 190 + 191 + ```ts 192 + import { onError } from "voltx.js"; 193 + 194 + onError((error) => { 195 + fetch("/api/errors", { 196 + method: "POST", 197 + headers: { "Content-Type": "application/json" }, 198 + body: JSON.stringify(error.toJSON()), 199 + }); 200 + }); 201 + ```
+1 -1
lib/deno.json
··· 1 1 { 2 2 "name": "@voltx/core", 3 - "version": "0.4.0", 3 + "version": "0.5.1", 4 4 "license": "MIT", 5 5 "exports": { ".": "./src/index.ts", "./debug": "./src/debug.ts" }, 6 6 "imports": {
+1 -1
lib/jsr.json
··· 1 1 { 2 2 "name": "@voltx/core", 3 - "version": "0.4.0", 3 + "version": "0.5.1", 4 4 "license": "MIT", 5 5 "exports": { ".": "./src/index.ts", "./debug": "./src/debug.ts" }, 6 6 "publish": { "include": ["src", "README.md", "LICENSE"] }
+1 -1
lib/package.json
··· 1 1 { 2 2 "name": "voltx.js", 3 - "version": "0.4.0", 3 + "version": "0.5.1", 4 4 "description": "A lightweight reactive framework for declarative UIs", 5 5 "type": "module", 6 6 "author": "Owais Jamil",
+9 -1
lib/src/core/charge.ts
··· 88 88 if (typeof stateData !== "object" || isNil(stateData) || Array.isArray(stateData)) { 89 89 report(new Error(`data-volt-state must be a JSON object, got ${typeof stateData}`), { 90 90 source: "charge", 91 + level: "fatal", 91 92 element: el as HTMLElement, 92 93 directive: "data-volt-state", 93 94 expression: stateAttr, ··· 100 101 } catch (error) { 101 102 report(error as Error, { 102 103 source: "charge", 104 + level: "fatal", 103 105 element: el as HTMLElement, 104 106 directive: "data-volt-state", 105 107 expression: stateAttr, ··· 143 145 if (typeof data !== "object" || isNil(data) || Array.isArray(data)) { 144 146 report(new Error(`data-volt-store script must contain a JSON object, got: ${typeof data}`), { 145 147 source: "charge", 148 + level: "fatal", 146 149 element: script as HTMLElement, 147 150 directive: "data-volt-store", 148 151 }); ··· 151 154 152 155 registerStore(data); 153 156 } catch (error) { 154 - report(error as Error, { source: "charge", element: script as HTMLElement, directive: "data-volt-store" }); 157 + report(error as Error, { 158 + source: "charge", 159 + level: "fatal", 160 + element: script as HTMLElement, 161 + directive: "data-volt-store", 162 + }); 155 163 } 156 164 } 157 165 }
+183 -32
lib/src/core/error.ts
··· 6 6 * 7 7 * @module core/error 8 8 */ 9 - import type { ErrorContext, ErrorHandler, ErrorSource } from "$types/volt"; 9 + import type { ErrorContext, ErrorHandler, ErrorLevel, ErrorSource } from "$types/volt"; 10 10 11 11 /** 12 - * Enhanced error class with VoltX context 12 + * Base error class with VoltX context 13 13 * 14 - * Wraps original errors with rich debugging information including 15 - * source, element, directive, and expression details. 14 + * Wraps original errors with rich debugging information including ource, element, directive, and expression details. 16 15 */ 17 16 export class VoltError extends Error { 18 17 /** Error source category */ 19 18 public readonly source: ErrorSource; 19 + /** Error severity level */ 20 + public readonly level: ErrorLevel; 20 21 /** DOM element where error occurred */ 21 22 public readonly element?: HTMLElement; 22 23 /** Directive name */ ··· 38 39 this.name = "VoltError"; 39 40 this.cause = cause; 40 41 this.source = context.source; 42 + this.level = context.level ?? "error"; 41 43 this.element = context.element; 42 44 this.directive = context.directive; 43 45 this.expression = context.expression; ··· 47 49 // V8-specific feature 48 50 // See: https://github.com/microsoft/TypeScript/issues/3926 49 51 if ((Error as any).captureStackTrace) { 50 - (Error as any).captureStackTrace(this, VoltError); 52 + (Error as any).captureStackTrace(this, this.constructor); 51 53 } 52 54 } 53 55 ··· 67 69 68 70 private static buildMessage(cause: Error, context: ErrorContext): string { 69 71 const parts: string[] = []; 72 + const level = context.level ?? "error"; 70 73 71 - parts.push(`[${context.source}] ${cause.message}`); 74 + parts.push(`[${level.toUpperCase()}] [${context.source}] ${cause.message}`); 72 75 73 76 if (context.directive) { 74 77 parts.push(`Directive: ${context.directive}`); ··· 112 115 name: this.name, 113 116 message: this.message, 114 117 source: this.source, 118 + level: this.level, 115 119 directive: this.directive, 116 120 expression: this.expression, 117 121 timestamp: this.timestamp, ··· 123 127 } 124 128 125 129 /** 130 + * Error during expression evaluation 131 + * 132 + * Thrown when evaluating expressions in directives like data-volt-text, data-volt-if, or any other binding that uses the expression evaluator. 133 + */ 134 + export class EvaluatorError extends VoltError { 135 + constructor(cause: Error, context: ErrorContext) { 136 + super(cause, { ...context, source: "evaluator" }); 137 + this.name = "EvaluatorError"; 138 + } 139 + } 140 + 141 + /** 142 + * Error during directive binding 143 + * 144 + * Thrown when setting up or executing DOM bindings like data-volt-text, data-volt-class, data-volt-model, etc. 145 + */ 146 + export class BindingError extends VoltError { 147 + constructor(cause: Error, context: ErrorContext) { 148 + super(cause, { ...context, source: "binding" }); 149 + this.name = "BindingError"; 150 + } 151 + } 152 + 153 + /** 154 + * Error during effect execution 155 + * 156 + * Thrown when effects, computed signals, or async effects fail during execution or cleanup. 157 + */ 158 + export class EffectError extends VoltError { 159 + constructor(cause: Error, context: ErrorContext) { 160 + super(cause, { ...context, source: "effect" }); 161 + this.name = "EffectError"; 162 + } 163 + } 164 + 165 + /** 166 + * Error during HTTP operations 167 + * 168 + * Thrown when HTTP directives (data-volt-get, data-volt-post, etc.) encounter network errors, parsing failures, or swap strategy issues. 169 + */ 170 + export class HttpError extends VoltError { 171 + constructor(cause: Error, context: ErrorContext) { 172 + super(cause, { ...context, source: "http" }); 173 + this.name = "HttpError"; 174 + } 175 + } 176 + 177 + /** 178 + * Error in plugin execution 179 + * 180 + * Thrown when custom plugins registered via registerPlugin fail during initialization or execution. 181 + */ 182 + export class PluginError extends VoltError { 183 + constructor(cause: Error, context: ErrorContext) { 184 + super(cause, { ...context, source: "plugin" }); 185 + this.name = "PluginError"; 186 + } 187 + } 188 + 189 + /** 190 + * Error in lifecycle hooks 191 + * 192 + * Thrown when lifecycle hooks (beforeMount, afterMount, onMount, etc.) fail during execution. 193 + */ 194 + export class LifecycleError extends VoltError { 195 + constructor(cause: Error, context: ErrorContext) { 196 + super(cause, { ...context, source: "lifecycle" }); 197 + this.name = "LifecycleError"; 198 + } 199 + } 200 + 201 + /** 202 + * Error during charge/initialization 203 + * 204 + * Thrown when charge() encounters errors during auto-discovery and mounting of [data-volt] elements, or when parsing data-volt-state. 205 + */ 206 + export class ChargeError extends VoltError { 207 + constructor(cause: Error, context: ErrorContext) { 208 + super(cause, { ...context, source: "charge" }); 209 + this.name = "ChargeError"; 210 + } 211 + } 212 + 213 + /** 214 + * User-triggered error 215 + * 216 + * Errors explicitly reported by user code via the report() function 217 + * with source: "user". 218 + */ 219 + export class UserError extends VoltError { 220 + constructor(cause: Error, context: ErrorContext) { 221 + super(cause, { ...context, source: "user" }); 222 + this.name = "UserError"; 223 + } 224 + } 225 + 226 + /** 126 227 * Global error handler registry 127 228 */ 128 229 let errorHandlers: ErrorHandler[] = []; ··· 131 232 * Register an error handler 132 233 * 133 234 * Multiple handlers can be registered and will be called in registration order. 134 - * Handlers can call `error.stopPropagation()` to prevent subsequent handlers 135 - * from being called. 235 + * Handlers can call `error.stopPropagation()` to prevent subsequent handlers from being called. 136 236 * 137 237 * @param handler - Error handler function 138 238 * @returns Cleanup function to unregister the handler ··· 183 283 * If no error handlers are registered, errors are logged to console as fallback. 184 284 * Once handlers are registered, console logging is disabled. 185 285 * 286 + * Error levels determine console output and behavior: 287 + * - warn: Non-critical issues logged with console.warn 288 + * - error: Recoverable errors logged with console.error (default) 289 + * - fatal: Unrecoverable errors logged with console.error and thrown to halt execution 290 + * 186 291 * @param error - Error to report (can be Error, unknown, or string) 187 292 * @param context - Error context with source and additional details 188 293 * 189 294 * @example 190 295 * ```ts 191 - * // Internal usage (by VoltX) 192 - * try { 193 - * evaluate(expression, scope); 194 - * } catch (err) { 195 - * report(err, { 196 - * source: ErrorSource.Evaluator, 197 - * element: ctx.element, 198 - * directive: 'data-volt-text', 199 - * expression: expression 200 - * }); 201 - * } 296 + * // Warning for non-critical issues 297 + * report(err, { 298 + * source: "binding", 299 + * level: "warn", 300 + * directive: "data-volt-deprecated" 301 + * }); 202 302 * 203 - * // External usage (by plugins/apps) 204 - * try { 205 - * myCustomLogic(); 206 - * } catch (err) { 207 - * report(err, { 208 - * source: ErrorSource.User, 209 - * customContext: 'My feature failed' 210 - * }); 211 - * } 303 + * // Error for recoverable issues (default) 304 + * report(err, { 305 + * source: "evaluator", 306 + * level: "error", 307 + * directive: "data-volt-text", 308 + * expression: expression 309 + * }); 310 + * 311 + * // Fatal error that halts execution 312 + * report(err, { 313 + * source: "charge", 314 + * level: "fatal", 315 + * directive: "data-volt-state" 316 + * }); 212 317 * ``` 213 318 */ 214 319 export function report(error: unknown, context: ErrorContext): void { 215 320 const errorObj = error instanceof Error ? error : new Error(String(error)); 216 - const voltError = new VoltError(errorObj, context); 321 + 322 + const voltError = createErrorBySource(errorObj, context); 217 323 218 324 if (errorHandlers.length === 0) { 219 - console.error(voltError.message); 220 - console.error("Caused by:", voltError.cause); 325 + const logFn = voltError.level === "warn" ? console.warn : console.error; 326 + 327 + logFn(voltError.message); 328 + logFn("Caused by:", voltError.cause); 221 329 if (voltError.element) { 222 - console.error("Element:", voltError.element); 330 + logFn("Element:", voltError.element); 331 + } 332 + 333 + if (voltError.level === "fatal") { 334 + throw voltError; 223 335 } 224 336 return; 225 337 } ··· 232 344 } 233 345 } catch (handlerError) { 234 346 console.error("Error in error handler:", handlerError); 347 + } 348 + } 349 + 350 + if (voltError.level === "fatal") { 351 + throw voltError; 352 + } 353 + } 354 + 355 + /** 356 + * Create the appropriate error type based on the source 357 + */ 358 + function createErrorBySource(cause: Error, context: ErrorContext): VoltError { 359 + switch (context.source) { 360 + case "evaluator": { 361 + return new EvaluatorError(cause, context); 362 + } 363 + case "binding": { 364 + return new BindingError(cause, context); 365 + } 366 + case "effect": { 367 + return new EffectError(cause, context); 368 + } 369 + case "http": { 370 + return new HttpError(cause, context); 371 + } 372 + case "plugin": { 373 + return new PluginError(cause, context); 374 + } 375 + case "lifecycle": { 376 + return new LifecycleError(cause, context); 377 + } 378 + case "charge": { 379 + return new ChargeError(cause, context); 380 + } 381 + case "user": { 382 + return new UserError(cause, context); 383 + } 384 + default: { 385 + return new VoltError(cause, context); 235 386 } 236 387 } 237 388 }
+2
lib/src/core/http.ts
··· 231 231 default: { 232 232 report(new Error(`Unknown swap strategy: ${strategy as string}`), { 233 233 source: "http", 234 + level: "warn", 234 235 element: target as HTMLElement, 235 236 }); 236 237 } ··· 510 511 if (!target) { 511 512 report(new Error(`Target element not found: ${targetConf}`), { 512 513 source: "http", 514 + level: "warn", 513 515 element: defaultEl as HTMLElement, 514 516 directive: "data-volt-target", 515 517 });
+15 -2
lib/src/index.ts
··· 7 7 export { asyncEffect } from "$core/async-effect"; 8 8 export { mount } from "$core/binder"; 9 9 export { charge } from "$core/charge"; 10 - export { clearErrorHandlers, onError, report } from "$core/error"; 11 - export type { VoltError } from "$core/error"; 10 + export { 11 + BindingError, 12 + ChargeError, 13 + clearErrorHandlers, 14 + EffectError, 15 + EvaluatorError, 16 + HttpError, 17 + LifecycleError, 18 + onError, 19 + PluginError, 20 + report, 21 + UserError, 22 + VoltError, 23 + } from "$core/error"; 12 24 export { parseHttpConfig, request, serializeForm, serializeFormToJSON, swap } from "$core/http"; 13 25 export { 14 26 clearAllGlobalHooks, ··· 86 98 ComputedSignal, 87 99 ErrorContext, 88 100 ErrorHandler, 101 + ErrorLevel, 89 102 ErrorSource, 90 103 GlobalHookName, 91 104 GlobalStore,
+11
lib/src/types/volt.d.ts
··· 559 559 export type ErrorSource = "evaluator" | "binding" | "effect" | "http" | "plugin" | "lifecycle" | "charge" | "user"; 560 560 561 561 /** 562 + * Error severity level 563 + * 564 + * - `warn`: Non-critical issues that don't prevent operation (e.g., deprecated usage, missing optional features) 565 + * - `error`: Recoverable errors that prevent specific operations (e.g., failed evaluations, missing elements) 566 + * - `fatal`: Unrecoverable errors that should halt execution (e.g., critical initialization failures) 567 + */ 568 + export type ErrorLevel = "warn" | "error" | "fatal"; 569 + 570 + /** 562 571 * Context information for error reporting 563 572 */ 564 573 export type ErrorContext = { 565 574 /** Error source category */ 566 575 source: ErrorSource; 576 + /** Error severity level (defaults to "error") */ 577 + level?: ErrorLevel; 567 578 /** DOM element where error occurred */ 568 579 element?: HTMLElement; 569 580 /** Directive name (e.g., "data-volt-text", "data-volt-on-click") */
+186 -2
lib/test/core/error.test.ts
··· 1 - import { clearErrorHandlers, getErrorHandlerCount, onError, report, VoltError } from "$core/error"; 2 - import type { ErrorContext, ErrorSource } from "$types/volt"; 1 + import { 2 + BindingError, 3 + ChargeError, 4 + clearErrorHandlers, 5 + EffectError, 6 + EvaluatorError, 7 + getErrorHandlerCount, 8 + HttpError, 9 + LifecycleError, 10 + onError, 11 + PluginError, 12 + report, 13 + UserError, 14 + VoltError, 15 + } from "$core/error"; 16 + import type { ErrorContext, ErrorLevel, ErrorSource } from "$types/volt"; 3 17 import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 4 18 5 19 describe("VoltError", () => { ··· 295 309 for (const [i, source] of sources.entries()) { 296 310 const voltError: VoltError = handler.mock.calls[i][0]; 297 311 expect(voltError.source).toBe(source); 312 + } 313 + }); 314 + 315 + it("creates correct error types based on source", () => { 316 + const handler = vi.fn(); 317 + onError(handler); 318 + 319 + const testCases: Array<{ source: ErrorSource; errorType: typeof VoltError; name: string }> = [ 320 + { source: "evaluator", errorType: EvaluatorError, name: "EvaluatorError" }, 321 + { source: "binding", errorType: BindingError, name: "BindingError" }, 322 + { source: "effect", errorType: EffectError, name: "EffectError" }, 323 + { source: "http", errorType: HttpError, name: "HttpError" }, 324 + { source: "plugin", errorType: PluginError, name: "PluginError" }, 325 + { source: "lifecycle", errorType: LifecycleError, name: "LifecycleError" }, 326 + { source: "charge", errorType: ChargeError, name: "ChargeError" }, 327 + { source: "user", errorType: UserError, name: "UserError" }, 328 + ]; 329 + 330 + for (const { source } of testCases) { 331 + report(new Error(`Test ${source}`), { source }); 332 + } 333 + 334 + expect(handler).toHaveBeenCalledTimes(testCases.length); 335 + 336 + for (const [i, { errorType, name }] of testCases.entries()) { 337 + const voltError = handler.mock.calls[i][0]; 338 + expect(voltError).toBeInstanceOf(errorType); 339 + expect(voltError).toBeInstanceOf(VoltError); 340 + expect(voltError.name).toBe(name); 341 + } 342 + }); 343 + }); 344 + 345 + describe("Error Levels", () => { 346 + beforeEach(() => { 347 + clearErrorHandlers(); 348 + vi.spyOn(console, "error").mockImplementation(() => {}); 349 + vi.spyOn(console, "warn").mockImplementation(() => {}); 350 + }); 351 + 352 + afterEach(() => { 353 + clearErrorHandlers(); 354 + vi.restoreAllMocks(); 355 + }); 356 + 357 + it("defaults to error level when not specified", () => { 358 + const cause = new Error("Test error"); 359 + const context: ErrorContext = { source: "binding" }; 360 + 361 + const voltError = new VoltError(cause, context); 362 + 363 + expect(voltError.level).toBe("error"); 364 + }); 365 + 366 + it("includes error level in VoltError", () => { 367 + const levels: Array<ErrorLevel> = ["warn", "error", "fatal"]; 368 + 369 + for (const level of levels) { 370 + const cause = new Error(`Test ${level}`); 371 + const context: ErrorContext = { source: "binding", level }; 372 + 373 + const voltError = new VoltError(cause, context); 374 + 375 + expect(voltError.level).toBe(level); 376 + } 377 + }); 378 + 379 + it("includes error level in message", () => { 380 + const levels: Array<ErrorLevel> = ["warn", "error", "fatal"]; 381 + 382 + for (const level of levels) { 383 + const cause = new Error(`Test ${level}`); 384 + const context: ErrorContext = { source: "binding", level }; 385 + 386 + const voltError = new VoltError(cause, context); 387 + 388 + expect(voltError.message).toContain(`[${level.toUpperCase()}]`); 389 + } 390 + }); 391 + 392 + it("includes error level in JSON serialization", () => { 393 + const cause = new Error("Test error"); 394 + const context: ErrorContext = { source: "binding", level: "warn" }; 395 + 396 + const voltError = new VoltError(cause, context); 397 + const json = voltError.toJSON(); 398 + 399 + expect(json.level).toBe("warn"); 400 + }); 401 + 402 + it("uses console.warn for warn level without handlers", () => { 403 + const error = new Error("Warning message"); 404 + const context: ErrorContext = { source: "binding", level: "warn" }; 405 + 406 + report(error, context); 407 + 408 + expect(console.warn).toHaveBeenCalledTimes(2); 409 + expect(console.warn).toHaveBeenCalledWith(expect.stringContaining("[WARN]")); 410 + expect(console.warn).toHaveBeenCalledWith("Caused by:", error); 411 + expect(console.error).not.toHaveBeenCalled(); 412 + }); 413 + 414 + it("uses console.error for error level without handlers", () => { 415 + const error = new Error("Error message"); 416 + const context: ErrorContext = { source: "binding", level: "error" }; 417 + 418 + report(error, context); 419 + 420 + expect(console.error).toHaveBeenCalledTimes(2); 421 + expect(console.error).toHaveBeenCalledWith(expect.stringContaining("[ERROR]")); 422 + expect(console.error).toHaveBeenCalledWith("Caused by:", error); 423 + expect(console.warn).not.toHaveBeenCalled(); 424 + }); 425 + 426 + it("uses console.error for fatal level without handlers", () => { 427 + const error = new Error("Fatal error"); 428 + const context: ErrorContext = { source: "charge", level: "fatal" }; 429 + 430 + expect(() => report(error, context)).toThrow(VoltError); 431 + 432 + expect(console.error).toHaveBeenCalledTimes(2); 433 + expect(console.error).toHaveBeenCalledWith(expect.stringContaining("[FATAL]")); 434 + expect(console.error).toHaveBeenCalledWith("Caused by:", error); 435 + }); 436 + 437 + it("throws error for fatal level after handlers", () => { 438 + const handler = vi.fn(); 439 + onError(handler); 440 + 441 + const error = new Error("Fatal error"); 442 + const context: ErrorContext = { source: "charge", level: "fatal" }; 443 + 444 + expect(() => report(error, context)).toThrow(VoltError); 445 + 446 + expect(handler).toHaveBeenCalledTimes(1); 447 + const voltError = handler.mock.calls[0][0]; 448 + expect(voltError.level).toBe("fatal"); 449 + }); 450 + 451 + it("does not throw for warn level", () => { 452 + const error = new Error("Warning"); 453 + const context: ErrorContext = { source: "http", level: "warn" }; 454 + 455 + expect(() => report(error, context)).not.toThrow(); 456 + }); 457 + 458 + it("does not throw for error level", () => { 459 + const error = new Error("Error"); 460 + const context: ErrorContext = { source: "binding", level: "error" }; 461 + 462 + expect(() => report(error, context)).not.toThrow(); 463 + }); 464 + 465 + it("passes error level to handlers", () => { 466 + const handler = vi.fn(); 467 + onError(handler); 468 + 469 + const levels: Array<ErrorLevel> = ["warn", "error", "fatal"]; 470 + 471 + for (const level of levels) { 472 + try { 473 + report(new Error(`Test ${level}`), { source: "binding", level }); 474 + } catch { /* No-op */ } 475 + } 476 + 477 + expect(handler).toHaveBeenCalledTimes(3); 478 + 479 + for (const [i, level] of levels.entries()) { 480 + const voltError: VoltError = handler.mock.calls[i][0]; 481 + expect(voltError.level).toBe(level); 298 482 } 299 483 }); 300 484 });
+2
package.json
··· 14 14 "docs:dev": "pnpm --filter @voltx/docs dev", 15 15 "docs:build": "pnpm --filter @voltx/docs build", 16 16 "docs:preview": "pnpm --filter @voltx/docs preview", 17 + "cli:dev": "pnpm --filter create-voltx dev", 18 + "cli:build": "pnpm --filter create-voltx build", 17 19 "typecheck": "pnpm -r typecheck" 18 20 }, 19 21 "devDependencies": {
+414 -73
pnpm-lock.yaml
··· 39 39 specifier: ^3.2.4 40 40 version: 3.2.4(@types/node@24.8.1)(esbuild@0.25.11)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(terser@5.44.0)(yaml@2.8.1) 41 41 42 + cli: 43 + dependencies: 44 + '@inquirer/prompts': 45 + specifier: ^8.0.1 46 + version: 8.0.1(@types/node@24.8.1) 47 + chalk: 48 + specifier: ^5.6.2 49 + version: 5.6.2 50 + commander: 51 + specifier: ^14.0.1 52 + version: 14.0.1 53 + devDependencies: 54 + '@vitest/coverage-v8': 55 + specifier: ^3.2.4 56 + version: 3.2.4(vitest@3.2.4(@types/node@24.8.1)(esbuild@0.25.11)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(terser@5.44.0)(yaml@2.8.1)) 57 + tsdown: 58 + specifier: ^0.15.6 59 + version: 0.15.8(typescript@5.9.3) 60 + 42 61 dev: 43 62 dependencies: 44 63 chalk: ··· 62 81 version: 0.15.8(typescript@5.9.3) 63 82 64 83 docs: 84 + dependencies: 85 + '@catppuccin/vitepress': 86 + specifier: ^0.1.2 87 + version: 0.1.2(typescript@5.9.3) 65 88 devDependencies: 66 89 esbuild: 67 90 specifier: ^0.25.11 ··· 238 261 '@bcoe/v8-coverage@1.0.2': 239 262 resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} 240 263 engines: {node: '>=18'} 264 + 265 + '@catppuccin/vitepress@0.1.2': 266 + resolution: {integrity: sha512-dqhgo6U6GWbgh3McAgwemUC8Y2Aj48rRcQx/9iuPzBPAgo7NA3yi7ZcR0wolAENMmoOMAHBV+rz/5DfiGxtZLA==} 267 + peerDependencies: 268 + typescript: ^5.0.0 241 269 242 270 '@csstools/color-helpers@5.1.0': 243 271 resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} ··· 583 611 '@iconify/types@2.0.0': 584 612 resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} 585 613 614 + '@inquirer/ansi@2.0.1': 615 + resolution: {integrity: sha512-QAZUk6BBncv/XmSEZTscd8qazzjV3E0leUMrEPjxCd51QBgCKmprUGLex5DTsNtURm7LMzv+CLcd6S86xvBfYg==} 616 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 617 + 618 + '@inquirer/checkbox@5.0.1': 619 + resolution: {integrity: sha512-5VPFBK8jKdsjMK3DTFOlbR0+Kkd4q0AWB7VhWQn6ppv44dr3b7PU8wSJQTC5oA0f/aGW7v/ZozQJAY9zx6PKig==} 620 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 621 + peerDependencies: 622 + '@types/node': '>=18' 623 + peerDependenciesMeta: 624 + '@types/node': 625 + optional: true 626 + 627 + '@inquirer/confirm@6.0.1': 628 + resolution: {integrity: sha512-wD+pM7IxLn1TdcQN12Q6wcFe5VpyCuh/I2sSmqO5KjWH2R4v+GkUToHb+PsDGobOe1MtAlXMwGNkZUPc2+L6NA==} 629 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 630 + peerDependencies: 631 + '@types/node': '>=18' 632 + peerDependenciesMeta: 633 + '@types/node': 634 + optional: true 635 + 636 + '@inquirer/core@11.0.1': 637 + resolution: {integrity: sha512-Tpf49h50e4KYffVUCXzkx4gWMafUi3aDQDwfVAAGBNnVcXiwJIj4m2bKlZ7Kgyf6wjt1eyXH1wDGXcAokm4Ssw==} 638 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 639 + peerDependencies: 640 + '@types/node': '>=18' 641 + peerDependenciesMeta: 642 + '@types/node': 643 + optional: true 644 + 645 + '@inquirer/editor@5.0.1': 646 + resolution: {integrity: sha512-zDKobHI7Ry++4noiV9Z5VfYgSVpPZoMApviIuGwLOMciQaP+dGzCO+1fcwI441riklRiZg4yURWyEoX0Zy2zZw==} 647 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 648 + peerDependencies: 649 + '@types/node': '>=18' 650 + peerDependenciesMeta: 651 + '@types/node': 652 + optional: true 653 + 654 + '@inquirer/expand@5.0.1': 655 + resolution: {integrity: sha512-TBrTpAB6uZNnGQHtSEkbvJZIQ3dXZOrwqQSO9uUbwct3G2LitwBCE5YZj98MbQ5nzihzs5pRjY1K9RRLH4WgoA==} 656 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 657 + peerDependencies: 658 + '@types/node': '>=18' 659 + peerDependenciesMeta: 660 + '@types/node': 661 + optional: true 662 + 663 + '@inquirer/external-editor@2.0.1': 664 + resolution: {integrity: sha512-BPYWJXCAK9w6R+pb2s3WyxUz9ts9SP/LDOUwA9fu7LeuyYgojz83i0DSRwezu736BgMwz14G63Xwj70hSzHohQ==} 665 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 666 + peerDependencies: 667 + '@types/node': '>=18' 668 + peerDependenciesMeta: 669 + '@types/node': 670 + optional: true 671 + 672 + '@inquirer/figures@2.0.1': 673 + resolution: {integrity: sha512-KtMxyjLCuDFqAWHmCY9qMtsZ09HnjMsm8H3OvpSIpfhHdfw3/AiGWHNrfRwbyvHPtOJpumm8wGn5fkhtvkWRsg==} 674 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 675 + 676 + '@inquirer/input@5.0.1': 677 + resolution: {integrity: sha512-cEhEUohCpE2BCuLKtFFZGp4Ief05SEcqeAOq9NxzN5ThOQP8Rl5N/Nt9VEDORK1bRb2Sk/zoOyQYfysPQwyQtA==} 678 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 679 + peerDependencies: 680 + '@types/node': '>=18' 681 + peerDependenciesMeta: 682 + '@types/node': 683 + optional: true 684 + 685 + '@inquirer/number@4.0.1': 686 + resolution: {integrity: sha512-4//zgBGHe8Q/FfCoUXZUrUHyK/q5dyqiwsePz3oSSPSmw1Ijo35ZkjaftnxroygcUlLYfXqm+0q08lnB5hd49A==} 687 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 688 + peerDependencies: 689 + '@types/node': '>=18' 690 + peerDependenciesMeta: 691 + '@types/node': 692 + optional: true 693 + 694 + '@inquirer/password@5.0.1': 695 + resolution: {integrity: sha512-UJudHpd7Ia30Q+x+ctYqI9Nh6SyEkaBscpa7J6Ts38oc1CNSws0I1hJEdxbQBlxQd65z5GEJPM4EtNf6tzfWaQ==} 696 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 697 + peerDependencies: 698 + '@types/node': '>=18' 699 + peerDependenciesMeta: 700 + '@types/node': 701 + optional: true 702 + 703 + '@inquirer/prompts@8.0.1': 704 + resolution: {integrity: sha512-MURRu/cyvLm9vchDDaVZ9u4p+ADnY0Mz3LQr0KTgihrrvuKZlqcWwlBC4lkOMvd0KKX4Wz7Ww9+uA7qEpQaqjg==} 705 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 706 + peerDependencies: 707 + '@types/node': '>=18' 708 + peerDependenciesMeta: 709 + '@types/node': 710 + optional: true 711 + 712 + '@inquirer/rawlist@5.0.1': 713 + resolution: {integrity: sha512-vVfVHKUgH6rZmMlyd0jOuGZo0Fw1jfcOqZF96lMwlgavx7g0x7MICe316bV01EEoI+c68vMdbkTTawuw3O+Fgw==} 714 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 715 + peerDependencies: 716 + '@types/node': '>=18' 717 + peerDependenciesMeta: 718 + '@types/node': 719 + optional: true 720 + 721 + '@inquirer/search@4.0.1': 722 + resolution: {integrity: sha512-XwiaK5xBvr31STX6Ji8iS3HCRysBXfL/jUbTzufdWTS6LTGtvDQA50oVETt1BJgjKyQBp9vt0VU6AmU/AnOaGA==} 723 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 724 + peerDependencies: 725 + '@types/node': '>=18' 726 + peerDependenciesMeta: 727 + '@types/node': 728 + optional: true 729 + 730 + '@inquirer/select@5.0.1': 731 + resolution: {integrity: sha512-gPByrgYoezGyKMq5KjV7Tuy1JU2ArIy6/sI8sprw0OpXope3VGQwP5FK1KD4eFFqEhKu470Dwe6/AyDPmGRA0Q==} 732 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 733 + peerDependencies: 734 + '@types/node': '>=18' 735 + peerDependenciesMeta: 736 + '@types/node': 737 + optional: true 738 + 739 + '@inquirer/type@4.0.1': 740 + resolution: {integrity: sha512-odO8YwoQAw/eVu/PSPsDDVPmqO77r/Mq7zcoF5VduVqIu2wSRWUgmYb5K9WH1no0SjLnOe8MDKtDL++z6mfo2g==} 741 + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} 742 + peerDependencies: 743 + '@types/node': '>=18' 744 + peerDependenciesMeta: 745 + '@types/node': 746 + optional: true 747 + 586 748 '@isaacs/cliui@8.0.2': 587 749 resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 588 750 engines: {node: '>=12'} ··· 629 791 '@oxc-project/types@0.93.0': 630 792 resolution: {integrity: sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==} 631 793 632 - '@oxc-project/types@0.95.0': 633 - resolution: {integrity: sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==} 794 + '@oxc-project/types@0.98.0': 795 + resolution: {integrity: sha512-Vzmd6FsqVuz5HQVcRC/hrx7Ujo3WEVeQP7C2UNP5uy1hUY4SQvMB+93jxkI1KRHz9a/6cni3glPOtvteN+zpsw==} 634 796 635 797 '@pkgjs/parseargs@0.11.0': 636 798 resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} ··· 645 807 cpu: [arm64] 646 808 os: [android] 647 809 648 - '@rolldown/binding-android-arm64@1.0.0-beta.44': 649 - resolution: {integrity: sha512-g9ejDOehJFhxC1DIXQuZQ9bKv4lRDioOTL42cJjFjqKPl1L7DVb9QQQE1FxokGEIMr6FezLipxwnzOXWe7DNPg==} 810 + '@rolldown/binding-android-arm64@1.0.0-beta.51': 811 + resolution: {integrity: sha512-Ctn8FUXKWWQI9pWC61P1yumS9WjQtelNS9riHwV7oCkknPGaAry4o7eFx2KgoLMnI2BgFJYpW7Im8/zX3BuONg==} 650 812 engines: {node: ^20.19.0 || >=22.12.0} 651 813 cpu: [arm64] 652 814 os: [android] ··· 657 819 cpu: [arm64] 658 820 os: [darwin] 659 821 660 - '@rolldown/binding-darwin-arm64@1.0.0-beta.44': 661 - resolution: {integrity: sha512-PxAW1PXLPmCzfhfKIS53kwpjLGTUdIfX4Ht+l9mj05C3lYCGaGowcNsYi2rdxWH24vSTmeK+ajDNRmmmrK0M7g==} 822 + '@rolldown/binding-darwin-arm64@1.0.0-beta.51': 823 + resolution: {integrity: sha512-EL1aRW2Oq15ShUEkBPsDtLMO8GTqfb/ktM/dFaVzXKQiEE96Ss6nexMgfgQrg8dGnNpndFyffVDb5IdSibsu1g==} 662 824 engines: {node: ^20.19.0 || >=22.12.0} 663 825 cpu: [arm64] 664 826 os: [darwin] ··· 669 831 cpu: [x64] 670 832 os: [darwin] 671 833 672 - '@rolldown/binding-darwin-x64@1.0.0-beta.44': 673 - resolution: {integrity: sha512-/CtQqs1oO9uSb5Ju60rZvsdjE7Pzn8EK2ISAdl2jedjMzeD/4neNyCbwyJOAPzU+GIQTZVyrFZJX+t7HXR1R/g==} 834 + '@rolldown/binding-darwin-x64@1.0.0-beta.51': 835 + resolution: {integrity: sha512-uGtYKlFen9pMIPvkHPWZVDtmYhMQi5g5Ddsndg1gf3atScKYKYgs5aDP4DhHeTwGXQglhfBG7lEaOIZ4UAIWww==} 674 836 engines: {node: ^20.19.0 || >=22.12.0} 675 837 cpu: [x64] 676 838 os: [darwin] ··· 681 843 cpu: [x64] 682 844 os: [freebsd] 683 845 684 - '@rolldown/binding-freebsd-x64@1.0.0-beta.44': 685 - resolution: {integrity: sha512-V5Q5W9c4+2GJ4QabmjmVV6alY97zhC/MZBaLkDtHwGy3qwzbM4DYgXUbun/0a8AH5hGhuU27tUIlYz6ZBlvgOA==} 846 + '@rolldown/binding-freebsd-x64@1.0.0-beta.51': 847 + resolution: {integrity: sha512-JRoVTQtHYbZj1P07JLiuTuXjiBtIa7ag7/qgKA6CIIXnAcdl4LrOf7nfDuHPJcuRKaP5dzecMgY99itvWfmUFQ==} 686 848 engines: {node: ^20.19.0 || >=22.12.0} 687 849 cpu: [x64] 688 850 os: [freebsd] ··· 693 855 cpu: [arm] 694 856 os: [linux] 695 857 696 - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.44': 697 - resolution: {integrity: sha512-X6adjkHeFqKsTU0FXdNN9HY4LDozPqIfHcnXovE5RkYLWIjMWuc489mIZ6iyhrMbCqMUla9IOsh5dvXSGT9o9A==} 858 + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.51': 859 + resolution: {integrity: sha512-BKATVnpPZ0TYBW9XfDwyd4kPGgvf964HiotIwUgpMrFOFYWqpZ+9ONNzMV4UFAYC7Hb5C2qgYQk/qj2OnAd4RQ==} 698 860 engines: {node: ^20.19.0 || >=22.12.0} 699 861 cpu: [arm] 700 862 os: [linux] ··· 705 867 cpu: [arm64] 706 868 os: [linux] 707 869 708 - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.44': 709 - resolution: {integrity: sha512-kRRKGZI4DXWa6ANFr3dLA85aSVkwPdgXaRjfanwY84tfc3LncDiIjyWCb042e3ckPzYhHSZ3LmisO+cdOIYL6Q==} 870 + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.51': 871 + resolution: {integrity: sha512-xLd7da5jkfbVsBCm1buIRdWtuXY8+hU3+6ESXY/Tk5X5DPHaifrUblhYDgmA34dQt6WyNC2kfXGgrduPEvDI6Q==} 710 872 engines: {node: ^20.19.0 || >=22.12.0} 711 873 cpu: [arm64] 712 874 os: [linux] ··· 717 879 cpu: [arm64] 718 880 os: [linux] 719 881 720 - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.44': 721 - resolution: {integrity: sha512-hMtiN9xX1NhxXBa2U3Up4XkVcsVp2h73yYtMDY59z9CDLEZLrik9RVLhBL5QtoX4zZKJ8HZKJtWuGYvtmkCbIQ==} 882 + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.51': 883 + resolution: {integrity: sha512-EQFXTgHxxTzv3t5EmjUP/DfxzFYx9sMndfLsYaAY4DWF6KsK1fXGYsiupif6qPTViPC9eVmRm78q0pZU/kuIPg==} 722 884 engines: {node: ^20.19.0 || >=22.12.0} 723 885 cpu: [arm64] 724 886 os: [linux] ··· 729 891 cpu: [x64] 730 892 os: [linux] 731 893 732 - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.44': 733 - resolution: {integrity: sha512-rd1LzbpXQuR8MTG43JB9VyXDjG7ogSJbIkBpZEHJ8oMKzL6j47kQT5BpIXrg3b5UVygW9QCI2fpFdMocT5Kudg==} 894 + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.51': 895 + resolution: {integrity: sha512-p5P6Xpa68w3yFaAdSzIZJbj+AfuDnMDqNSeglBXM7UlJT14Q4zwK+rV+8Mhp9MiUb4XFISZtbI/seBprhkQbiQ==} 734 896 engines: {node: ^20.19.0 || >=22.12.0} 735 897 cpu: [x64] 736 898 os: [linux] ··· 741 903 cpu: [x64] 742 904 os: [linux] 743 905 744 - '@rolldown/binding-linux-x64-musl@1.0.0-beta.44': 745 - resolution: {integrity: sha512-qI2IiPqmPRW25exXkuQr3TlweCDc05YvvbSDRPCuPsWkwb70dTiSoXn8iFxT4PWqTi71wWHg1Wyta9PlVhX5VA==} 906 + '@rolldown/binding-linux-x64-musl@1.0.0-beta.51': 907 + resolution: {integrity: sha512-sNVVyLa8HB8wkFipdfz1s6i0YWinwpbMWk5hO5S+XAYH2UH67YzUT13gs6wZTKg2x/3gtgXzYnHyF5wMIqoDAw==} 746 908 engines: {node: ^20.19.0 || >=22.12.0} 747 909 cpu: [x64] 748 910 os: [linux] ··· 753 915 cpu: [arm64] 754 916 os: [openharmony] 755 917 756 - '@rolldown/binding-openharmony-arm64@1.0.0-beta.44': 757 - resolution: {integrity: sha512-+vHvEc1pL5iJRFlldLC8mjm6P4Qciyfh2bh5ZI6yxDQKbYhCHRKNURaKz1mFcwxhVL5YMYsLyaqM3qizVif9MQ==} 918 + '@rolldown/binding-openharmony-arm64@1.0.0-beta.51': 919 + resolution: {integrity: sha512-e/JMTz9Q8+T3g/deEi8DK44sFWZWGKr9AOCW5e8C8SCVWzAXqYXAG7FXBWBNzWEZK0Rcwo9TQHTQ9Q0gXgdCaA==} 758 920 engines: {node: ^20.19.0 || >=22.12.0} 759 921 cpu: [arm64] 760 922 os: [openharmony] ··· 764 926 engines: {node: '>=14.0.0'} 765 927 cpu: [wasm32] 766 928 767 - '@rolldown/binding-wasm32-wasi@1.0.0-beta.44': 768 - resolution: {integrity: sha512-XSgLxRrtFj6RpTeMYmmQDAwHjKseYGKUn5LPiIdW4Cq+f5SBSStL2ToBDxkbdxKPEbCZptnLPQ/nfKcAxrC8Xg==} 929 + '@rolldown/binding-wasm32-wasi@1.0.0-beta.51': 930 + resolution: {integrity: sha512-We3LWqSu6J9s5Y0MK+N7fUiiu37aBGPG3Pc347EoaROuAwkCS2u9xJ5dpIyLW4B49CIbS3KaPmn4kTgPb3EyPw==} 769 931 engines: {node: '>=14.0.0'} 770 932 cpu: [wasm32] 771 933 ··· 775 937 cpu: [arm64] 776 938 os: [win32] 777 939 778 - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.44': 779 - resolution: {integrity: sha512-cF1LJdDIX02cJrFrX3wwQ6IzFM7I74BYeKFkzdcIA4QZ0+2WA7/NsKIgjvrunupepWb1Y6PFWdRlHSaz5AW1Wg==} 940 + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.51': 941 + resolution: {integrity: sha512-fj56buHRuMM+r/cb6ZYfNjNvO/0xeFybI6cTkTROJatdP4fvmQ1NS8D/Lm10FCSDEOkqIz8hK3TGpbAThbPHsA==} 780 942 engines: {node: ^20.19.0 || >=22.12.0} 781 943 cpu: [arm64] 782 944 os: [win32] ··· 787 949 cpu: [ia32] 788 950 os: [win32] 789 951 790 - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.44': 791 - resolution: {integrity: sha512-5uaJonDafhHiMn+iEh7qUp3QQ4Gihv3lEOxKfN8Vwadpy0e+5o28DWI42DpJ9YBYMrVy4JOWJ/3etB/sptpUwA==} 952 + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.51': 953 + resolution: {integrity: sha512-fkqEqaeEx8AySXiDm54b/RdINb3C0VovzJA3osMhZsbn6FoD73H0AOIiaVAtGr6x63hefruVKTX8irAm4Jkt2w==} 792 954 engines: {node: ^20.19.0 || >=22.12.0} 793 955 cpu: [ia32] 794 956 os: [win32] ··· 799 961 cpu: [x64] 800 962 os: [win32] 801 963 802 - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.44': 803 - resolution: {integrity: sha512-vsqhWAFJkkmgfBN/lkLCWTXF1PuPhMjfnAyru48KvF7mVh2+K7WkKYHezF3Fjz4X/mPScOcIv+g6cf6wnI6eWg==} 964 + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.51': 965 + resolution: {integrity: sha512-CWuLG/HMtrVcjKGa0C4GnuxONrku89g0+CsH8nT0SNhOtREXuzwgjIXNJImpE/A/DMf9JF+1Xkrq/YRr+F/rCg==} 804 966 engines: {node: ^20.19.0 || >=22.12.0} 805 967 cpu: [x64] 806 968 os: [win32] ··· 808 970 '@rolldown/pluginutils@1.0.0-beta.41': 809 971 resolution: {integrity: sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw==} 810 972 811 - '@rolldown/pluginutils@1.0.0-beta.44': 812 - resolution: {integrity: sha512-g6eW7Zwnr2c5RADIoqziHoVs6b3W5QTQ4+qbpfjbkMJ9x+8Og211VW/oot2dj9dVwaK/UyC6Yo+02gV+wWQVNg==} 973 + '@rolldown/pluginutils@1.0.0-beta.51': 974 + resolution: {integrity: sha512-51/8cNXMrqWqX3o8DZidhwz1uYq0BhHDDSfVygAND1Skx5s1TDw3APSSxCMcFFedwgqGcx34gRouwY+m404BBQ==} 813 975 814 976 '@shikijs/core@2.5.0': 815 977 resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} ··· 951 1113 resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} 952 1114 engines: {node: ^18.0.0 || >=20.0.0} 953 1115 peerDependencies: 954 - vite: npm:rolldown-vite@7.1.14 1116 + vite: ^5.0.0 || ^6.0.0 955 1117 vue: ^3.2.25 956 1118 957 1119 '@vitest/coverage-v8@3.2.4': ··· 970 1132 resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} 971 1133 peerDependencies: 972 1134 msw: ^2.4.9 973 - vite: npm:rolldown-vite@7.1.14 1135 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 974 1136 peerDependenciesMeta: 975 1137 msw: 976 1138 optional: true ··· 1246 1408 character-entities-legacy@3.0.0: 1247 1409 resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} 1248 1410 1411 + chardet@2.1.1: 1412 + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} 1413 + 1249 1414 check-error@2.1.1: 1250 1415 resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 1251 1416 engines: {node: '>= 16'} ··· 1268 1433 clean-regexp@1.0.0: 1269 1434 resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} 1270 1435 engines: {node: '>=4'} 1436 + 1437 + cli-width@4.1.0: 1438 + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} 1439 + engines: {node: '>= 12'} 1271 1440 1272 1441 cliui@8.0.1: 1273 1442 resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} ··· 1469 1638 emoji-regex-xs@1.0.0: 1470 1639 resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} 1471 1640 1641 + emoji-regex@10.6.0: 1642 + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} 1643 + 1472 1644 emoji-regex@8.0.0: 1473 1645 resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 1474 1646 ··· 1638 1810 get-caller-file@2.0.5: 1639 1811 resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 1640 1812 engines: {node: 6.* || 8.* || >= 10.*} 1813 + 1814 + get-east-asian-width@1.4.0: 1815 + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} 1816 + engines: {node: '>=18'} 1641 1817 1642 1818 get-tsconfig@4.12.0: 1643 1819 resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} ··· 1711 1887 resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} 1712 1888 engines: {node: '>=0.10.0'} 1713 1889 1890 + iconv-lite@0.7.0: 1891 + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} 1892 + engines: {node: '>=0.10.0'} 1893 + 1714 1894 ignore@5.3.2: 1715 1895 resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 1716 1896 engines: {node: '>= 4'} ··· 2016 2196 2017 2197 ms@2.1.3: 2018 2198 resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 2199 + 2200 + mute-stream@3.0.0: 2201 + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} 2202 + engines: {node: ^20.17.0 || >=22.9.0} 2019 2203 2020 2204 nanoid@3.3.11: 2021 2205 resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} ··· 2492 2676 engines: {node: ^20.19.0 || >=22.12.0} 2493 2677 hasBin: true 2494 2678 2495 - rolldown@1.0.0-beta.44: 2496 - resolution: {integrity: sha512-gcqgyCi3g93Fhr49PKvymE8PoaGS0sf6ajQrsYaQ8o5de6aUEbD6rJZiJbhOfpcqOnycgsAsUNPYri1h25NgsQ==} 2679 + rolldown@1.0.0-beta.51: 2680 + resolution: {integrity: sha512-ZRLgPlS91l4JztLYEZnmMcd3Umcla1hkXJgiEiR4HloRJBBoeaX8qogTu5Jfu36rRMVLndzqYv0h+M5gJAkUfg==} 2497 2681 engines: {node: ^20.19.0 || >=22.12.0} 2498 2682 hasBin: true 2499 2683 ··· 2574 2758 string-width@5.1.2: 2575 2759 resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 2576 2760 engines: {node: '>=12'} 2761 + 2762 + string-width@7.2.0: 2763 + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} 2764 + engines: {node: '>=18'} 2577 2765 2578 2766 stringify-entities@4.0.4: 2579 2767 resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} ··· 2878 3066 resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 2879 3067 engines: {node: '>=12'} 2880 3068 3069 + wrap-ansi@9.0.2: 3070 + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} 3071 + engines: {node: '>=18'} 3072 + 2881 3073 ws@8.18.3: 2882 3074 resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} 2883 3075 engines: {node: '>=10.0.0'} ··· 3090 3282 '@babel/helper-validator-identifier': 7.27.1 3091 3283 3092 3284 '@bcoe/v8-coverage@1.0.2': {} 3285 + 3286 + '@catppuccin/vitepress@0.1.2(typescript@5.9.3)': 3287 + dependencies: 3288 + typescript: 5.9.3 3093 3289 3094 3290 '@csstools/color-helpers@5.1.0': {} 3095 3291 ··· 3338 3534 3339 3535 '@iconify/types@2.0.0': {} 3340 3536 3537 + '@inquirer/ansi@2.0.1': {} 3538 + 3539 + '@inquirer/checkbox@5.0.1(@types/node@24.8.1)': 3540 + dependencies: 3541 + '@inquirer/ansi': 2.0.1 3542 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3543 + '@inquirer/figures': 2.0.1 3544 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3545 + optionalDependencies: 3546 + '@types/node': 24.8.1 3547 + 3548 + '@inquirer/confirm@6.0.1(@types/node@24.8.1)': 3549 + dependencies: 3550 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3551 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3552 + optionalDependencies: 3553 + '@types/node': 24.8.1 3554 + 3555 + '@inquirer/core@11.0.1(@types/node@24.8.1)': 3556 + dependencies: 3557 + '@inquirer/ansi': 2.0.1 3558 + '@inquirer/figures': 2.0.1 3559 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3560 + cli-width: 4.1.0 3561 + mute-stream: 3.0.0 3562 + signal-exit: 4.1.0 3563 + wrap-ansi: 9.0.2 3564 + optionalDependencies: 3565 + '@types/node': 24.8.1 3566 + 3567 + '@inquirer/editor@5.0.1(@types/node@24.8.1)': 3568 + dependencies: 3569 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3570 + '@inquirer/external-editor': 2.0.1(@types/node@24.8.1) 3571 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3572 + optionalDependencies: 3573 + '@types/node': 24.8.1 3574 + 3575 + '@inquirer/expand@5.0.1(@types/node@24.8.1)': 3576 + dependencies: 3577 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3578 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3579 + optionalDependencies: 3580 + '@types/node': 24.8.1 3581 + 3582 + '@inquirer/external-editor@2.0.1(@types/node@24.8.1)': 3583 + dependencies: 3584 + chardet: 2.1.1 3585 + iconv-lite: 0.7.0 3586 + optionalDependencies: 3587 + '@types/node': 24.8.1 3588 + 3589 + '@inquirer/figures@2.0.1': {} 3590 + 3591 + '@inquirer/input@5.0.1(@types/node@24.8.1)': 3592 + dependencies: 3593 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3594 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3595 + optionalDependencies: 3596 + '@types/node': 24.8.1 3597 + 3598 + '@inquirer/number@4.0.1(@types/node@24.8.1)': 3599 + dependencies: 3600 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3601 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3602 + optionalDependencies: 3603 + '@types/node': 24.8.1 3604 + 3605 + '@inquirer/password@5.0.1(@types/node@24.8.1)': 3606 + dependencies: 3607 + '@inquirer/ansi': 2.0.1 3608 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3609 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3610 + optionalDependencies: 3611 + '@types/node': 24.8.1 3612 + 3613 + '@inquirer/prompts@8.0.1(@types/node@24.8.1)': 3614 + dependencies: 3615 + '@inquirer/checkbox': 5.0.1(@types/node@24.8.1) 3616 + '@inquirer/confirm': 6.0.1(@types/node@24.8.1) 3617 + '@inquirer/editor': 5.0.1(@types/node@24.8.1) 3618 + '@inquirer/expand': 5.0.1(@types/node@24.8.1) 3619 + '@inquirer/input': 5.0.1(@types/node@24.8.1) 3620 + '@inquirer/number': 4.0.1(@types/node@24.8.1) 3621 + '@inquirer/password': 5.0.1(@types/node@24.8.1) 3622 + '@inquirer/rawlist': 5.0.1(@types/node@24.8.1) 3623 + '@inquirer/search': 4.0.1(@types/node@24.8.1) 3624 + '@inquirer/select': 5.0.1(@types/node@24.8.1) 3625 + optionalDependencies: 3626 + '@types/node': 24.8.1 3627 + 3628 + '@inquirer/rawlist@5.0.1(@types/node@24.8.1)': 3629 + dependencies: 3630 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3631 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3632 + optionalDependencies: 3633 + '@types/node': 24.8.1 3634 + 3635 + '@inquirer/search@4.0.1(@types/node@24.8.1)': 3636 + dependencies: 3637 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3638 + '@inquirer/figures': 2.0.1 3639 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3640 + optionalDependencies: 3641 + '@types/node': 24.8.1 3642 + 3643 + '@inquirer/select@5.0.1(@types/node@24.8.1)': 3644 + dependencies: 3645 + '@inquirer/ansi': 2.0.1 3646 + '@inquirer/core': 11.0.1(@types/node@24.8.1) 3647 + '@inquirer/figures': 2.0.1 3648 + '@inquirer/type': 4.0.1(@types/node@24.8.1) 3649 + optionalDependencies: 3650 + '@types/node': 24.8.1 3651 + 3652 + '@inquirer/type@4.0.1(@types/node@24.8.1)': 3653 + optionalDependencies: 3654 + '@types/node': 24.8.1 3655 + 3341 3656 '@isaacs/cliui@8.0.2': 3342 3657 dependencies: 3343 3658 string-width: 5.1.2 ··· 3391 3706 3392 3707 '@oxc-project/types@0.93.0': {} 3393 3708 3394 - '@oxc-project/types@0.95.0': {} 3709 + '@oxc-project/types@0.98.0': {} 3395 3710 3396 3711 '@pkgjs/parseargs@0.11.0': 3397 3712 optional: true ··· 3403 3718 '@rolldown/binding-android-arm64@1.0.0-beta.41': 3404 3719 optional: true 3405 3720 3406 - '@rolldown/binding-android-arm64@1.0.0-beta.44': 3721 + '@rolldown/binding-android-arm64@1.0.0-beta.51': 3407 3722 optional: true 3408 3723 3409 3724 '@rolldown/binding-darwin-arm64@1.0.0-beta.41': 3410 3725 optional: true 3411 3726 3412 - '@rolldown/binding-darwin-arm64@1.0.0-beta.44': 3727 + '@rolldown/binding-darwin-arm64@1.0.0-beta.51': 3413 3728 optional: true 3414 3729 3415 3730 '@rolldown/binding-darwin-x64@1.0.0-beta.41': 3416 3731 optional: true 3417 3732 3418 - '@rolldown/binding-darwin-x64@1.0.0-beta.44': 3733 + '@rolldown/binding-darwin-x64@1.0.0-beta.51': 3419 3734 optional: true 3420 3735 3421 3736 '@rolldown/binding-freebsd-x64@1.0.0-beta.41': 3422 3737 optional: true 3423 3738 3424 - '@rolldown/binding-freebsd-x64@1.0.0-beta.44': 3739 + '@rolldown/binding-freebsd-x64@1.0.0-beta.51': 3425 3740 optional: true 3426 3741 3427 3742 '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41': 3428 3743 optional: true 3429 3744 3430 - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.44': 3745 + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.51': 3431 3746 optional: true 3432 3747 3433 3748 '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41': 3434 3749 optional: true 3435 3750 3436 - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.44': 3751 + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.51': 3437 3752 optional: true 3438 3753 3439 3754 '@rolldown/binding-linux-arm64-musl@1.0.0-beta.41': 3440 3755 optional: true 3441 3756 3442 - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.44': 3757 + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.51': 3443 3758 optional: true 3444 3759 3445 3760 '@rolldown/binding-linux-x64-gnu@1.0.0-beta.41': 3446 3761 optional: true 3447 3762 3448 - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.44': 3763 + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.51': 3449 3764 optional: true 3450 3765 3451 3766 '@rolldown/binding-linux-x64-musl@1.0.0-beta.41': 3452 3767 optional: true 3453 3768 3454 - '@rolldown/binding-linux-x64-musl@1.0.0-beta.44': 3769 + '@rolldown/binding-linux-x64-musl@1.0.0-beta.51': 3455 3770 optional: true 3456 3771 3457 3772 '@rolldown/binding-openharmony-arm64@1.0.0-beta.41': 3458 3773 optional: true 3459 3774 3460 - '@rolldown/binding-openharmony-arm64@1.0.0-beta.44': 3775 + '@rolldown/binding-openharmony-arm64@1.0.0-beta.51': 3461 3776 optional: true 3462 3777 3463 3778 '@rolldown/binding-wasm32-wasi@1.0.0-beta.41': ··· 3465 3780 '@napi-rs/wasm-runtime': 1.0.7 3466 3781 optional: true 3467 3782 3468 - '@rolldown/binding-wasm32-wasi@1.0.0-beta.44': 3783 + '@rolldown/binding-wasm32-wasi@1.0.0-beta.51': 3469 3784 dependencies: 3470 3785 '@napi-rs/wasm-runtime': 1.0.7 3471 3786 optional: true ··· 3473 3788 '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41': 3474 3789 optional: true 3475 3790 3476 - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.44': 3791 + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.51': 3477 3792 optional: true 3478 3793 3479 3794 '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41': 3480 3795 optional: true 3481 3796 3482 - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.44': 3797 + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.51': 3483 3798 optional: true 3484 3799 3485 3800 '@rolldown/binding-win32-x64-msvc@1.0.0-beta.41': 3486 3801 optional: true 3487 3802 3488 - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.44': 3803 + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.51': 3489 3804 optional: true 3490 3805 3491 3806 '@rolldown/pluginutils@1.0.0-beta.41': {} 3492 3807 3493 - '@rolldown/pluginutils@1.0.0-beta.44': {} 3808 + '@rolldown/pluginutils@1.0.0-beta.51': {} 3494 3809 3495 3810 '@shikijs/core@2.5.0': 3496 3811 dependencies: ··· 4037 4352 4038 4353 character-entities-legacy@3.0.0: {} 4039 4354 4355 + chardet@2.1.1: {} 4356 + 4040 4357 check-error@2.1.1: {} 4041 4358 4042 4359 chokidar@3.6.0: ··· 4064 4381 clean-regexp@1.0.0: 4065 4382 dependencies: 4066 4383 escape-string-regexp: 1.0.5 4384 + 4385 + cli-width@4.1.0: {} 4067 4386 4068 4387 cliui@8.0.1: 4069 4388 dependencies: ··· 4268 4587 4269 4588 emoji-regex-xs@1.0.0: {} 4270 4589 4590 + emoji-regex@10.6.0: {} 4591 + 4271 4592 emoji-regex@8.0.0: {} 4272 4593 4273 4594 emoji-regex@9.2.2: {} ··· 4480 4801 function-bind@1.1.2: {} 4481 4802 4482 4803 get-caller-file@2.0.5: {} 4804 + 4805 + get-east-asian-width@1.4.0: {} 4483 4806 4484 4807 get-tsconfig@4.12.0: 4485 4808 dependencies: ··· 4571 4894 dependencies: 4572 4895 safer-buffer: 2.1.2 4573 4896 4897 + iconv-lite@0.7.0: 4898 + dependencies: 4899 + safer-buffer: 2.1.2 4900 + 4574 4901 ignore@5.3.2: {} 4575 4902 4576 4903 ignore@7.0.5: {} ··· 4848 5175 mitt@3.0.1: {} 4849 5176 4850 5177 ms@2.1.3: {} 5178 + 5179 + mute-stream@3.0.0: {} 4851 5180 4852 5181 nanoid@3.3.11: {} 4853 5182 ··· 5222 5551 5223 5552 rfdc@1.4.1: {} 5224 5553 5225 - rolldown-plugin-dts@0.16.12(rolldown@1.0.0-beta.44)(typescript@5.9.3): 5554 + rolldown-plugin-dts@0.16.12(rolldown@1.0.0-beta.51)(typescript@5.9.3): 5226 5555 dependencies: 5227 5556 '@babel/generator': 7.28.3 5228 5557 '@babel/parser': 7.28.4 ··· 5233 5562 dts-resolver: 2.1.2 5234 5563 get-tsconfig: 4.12.0 5235 5564 magic-string: 0.30.19 5236 - rolldown: 1.0.0-beta.44 5565 + rolldown: 1.0.0-beta.51 5237 5566 optionalDependencies: 5238 5567 typescript: 5.9.3 5239 5568 transitivePeerDependencies: ··· 5278 5607 '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.41 5279 5608 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.41 5280 5609 5281 - rolldown@1.0.0-beta.44: 5610 + rolldown@1.0.0-beta.51: 5282 5611 dependencies: 5283 - '@oxc-project/types': 0.95.0 5284 - '@rolldown/pluginutils': 1.0.0-beta.44 5612 + '@oxc-project/types': 0.98.0 5613 + '@rolldown/pluginutils': 1.0.0-beta.51 5285 5614 optionalDependencies: 5286 - '@rolldown/binding-android-arm64': 1.0.0-beta.44 5287 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.44 5288 - '@rolldown/binding-darwin-x64': 1.0.0-beta.44 5289 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.44 5290 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.44 5291 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.44 5292 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.44 5293 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.44 5294 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.44 5295 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.44 5296 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.44 5297 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.44 5298 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.44 5299 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.44 5615 + '@rolldown/binding-android-arm64': 1.0.0-beta.51 5616 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.51 5617 + '@rolldown/binding-darwin-x64': 1.0.0-beta.51 5618 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.51 5619 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.51 5620 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.51 5621 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.51 5622 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.51 5623 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.51 5624 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.51 5625 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.51 5626 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.51 5627 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.51 5628 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.51 5300 5629 5301 5630 rrweb-cssom@0.8.0: {} 5302 5631 ··· 5366 5695 dependencies: 5367 5696 eastasianwidth: 0.2.0 5368 5697 emoji-regex: 9.2.2 5698 + strip-ansi: 7.1.2 5699 + 5700 + string-width@7.2.0: 5701 + dependencies: 5702 + emoji-regex: 10.6.0 5703 + get-east-asian-width: 1.4.0 5369 5704 strip-ansi: 7.1.2 5370 5705 5371 5706 stringify-entities@4.0.4: ··· 5490 5825 diff: 8.0.2 5491 5826 empathic: 2.0.0 5492 5827 hookable: 5.5.3 5493 - rolldown: 1.0.0-beta.44 5494 - rolldown-plugin-dts: 0.16.12(rolldown@1.0.0-beta.44)(typescript@5.9.3) 5828 + rolldown: 1.0.0-beta.51 5829 + rolldown-plugin-dts: 0.16.12(rolldown@1.0.0-beta.51)(typescript@5.9.3) 5495 5830 semver: 7.7.3 5496 5831 tinyexec: 1.0.1 5497 5832 tinyglobby: 0.2.15 ··· 5745 6080 dependencies: 5746 6081 ansi-styles: 6.2.3 5747 6082 string-width: 5.1.2 6083 + strip-ansi: 7.1.2 6084 + 6085 + wrap-ansi@9.0.2: 6086 + dependencies: 6087 + ansi-styles: 6.2.3 6088 + string-width: 7.2.0 5748 6089 strip-ansi: 7.1.2 5749 6090 5750 6091 ws@8.18.3: {}
+1
pnpm-workspace.yaml
··· 2 2 - 'lib' 3 3 - 'dev' 4 4 - 'docs' 5 + - 'cli'