+126
.github/ISSUE_TEMPLATE/bug_report.yml
+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
+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
+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
+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
+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
-

51
-
52
-

53
-
54
-

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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+12
cli/tsdown.config.ts
+24
cli/vitest.config.ts
+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
+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
+1
-1
docs/.vitepress/theme/index.ts
+2
docs/.vitepress/theme/style.css
+2
docs/.vitepress/theme/style.css
+293
docs/cli.md
+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
+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
+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
+

63
+
64
+

65
+
66
+

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
+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
+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
lib/deno.json
+1
-1
lib/jsr.json
+1
-1
lib/jsr.json
+1
-1
lib/package.json
+1
-1
lib/package.json
+9
-1
lib/src/core/charge.ts
+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
+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
+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
+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
+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
+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
+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
+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: {}