veslx 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -55
- package/bin/lib/build.ts +65 -13
- package/bin/lib/import-config.ts +10 -9
- package/bin/lib/init.ts +21 -22
- package/bin/lib/serve.ts +66 -12
- package/bin/veslx.ts +2 -2
- package/dist/client/App.js +3 -9
- package/dist/client/App.js.map +1 -1
- package/dist/client/components/front-matter.js +11 -25
- package/dist/client/components/front-matter.js.map +1 -1
- package/dist/client/components/gallery/components/figure-caption.js +6 -4
- package/dist/client/components/gallery/components/figure-caption.js.map +1 -1
- package/dist/client/components/gallery/components/figure-header.js +3 -3
- package/dist/client/components/gallery/components/figure-header.js.map +1 -1
- package/dist/client/components/gallery/components/lightbox.js +13 -13
- package/dist/client/components/gallery/components/lightbox.js.map +1 -1
- package/dist/client/components/gallery/components/loading-image.js +11 -10
- package/dist/client/components/gallery/components/loading-image.js.map +1 -1
- package/dist/client/components/gallery/hooks/use-gallery-images.js +31 -7
- package/dist/client/components/gallery/hooks/use-gallery-images.js.map +1 -1
- package/dist/client/components/gallery/index.js +22 -15
- package/dist/client/components/gallery/index.js.map +1 -1
- package/dist/client/components/header.js +5 -3
- package/dist/client/components/header.js.map +1 -1
- package/dist/client/components/mdx-components.js +42 -8
- package/dist/client/components/mdx-components.js.map +1 -1
- package/dist/client/components/post-list.js +97 -90
- package/dist/client/components/post-list.js.map +1 -1
- package/dist/client/components/running-bar.js +1 -1
- package/dist/client/components/running-bar.js.map +1 -1
- package/dist/client/components/slide.js +18 -0
- package/dist/client/components/slide.js.map +1 -0
- package/dist/client/components/slides-renderer.js +7 -71
- package/dist/client/components/slides-renderer.js.map +1 -1
- package/dist/client/hooks/use-mdx-content.js +55 -9
- package/dist/client/hooks/use-mdx-content.js.map +1 -1
- package/dist/client/main.js +1 -0
- package/dist/client/main.js.map +1 -1
- package/dist/client/pages/content-router.js +19 -0
- package/dist/client/pages/content-router.js.map +1 -0
- package/dist/client/pages/home.js +11 -7
- package/dist/client/pages/home.js.map +1 -1
- package/dist/client/pages/post.js +8 -20
- package/dist/client/pages/post.js.map +1 -1
- package/dist/client/pages/slides.js +62 -86
- package/dist/client/pages/slides.js.map +1 -1
- package/dist/client/plugin/src/client.js +58 -96
- package/dist/client/plugin/src/client.js.map +1 -1
- package/dist/client/plugin/src/directory-tree.js +111 -0
- package/dist/client/plugin/src/directory-tree.js.map +1 -0
- package/index.html +1 -1
- package/package.json +27 -15
- package/plugin/src/client.tsx +64 -116
- package/plugin/src/directory-tree.ts +171 -0
- package/plugin/src/lib.ts +6 -249
- package/plugin/src/plugin.ts +93 -50
- package/plugin/src/remark-slides.ts +100 -0
- package/plugin/src/types.ts +22 -0
- package/src/App.tsx +3 -6
- package/src/components/front-matter.tsx +14 -29
- package/src/components/gallery/components/figure-caption.tsx +15 -7
- package/src/components/gallery/components/figure-header.tsx +3 -3
- package/src/components/gallery/components/lightbox.tsx +15 -13
- package/src/components/gallery/components/loading-image.tsx +15 -12
- package/src/components/gallery/hooks/use-gallery-images.ts +51 -10
- package/src/components/gallery/index.tsx +32 -26
- package/src/components/header.tsx +14 -9
- package/src/components/mdx-components.tsx +61 -8
- package/src/components/post-list.tsx +149 -115
- package/src/components/running-bar.tsx +1 -1
- package/src/components/slide.tsx +22 -5
- package/src/components/slides-renderer.tsx +7 -115
- package/src/components/welcome.tsx +11 -14
- package/src/hooks/use-mdx-content.ts +94 -9
- package/src/index.css +159 -0
- package/src/main.tsx +1 -0
- package/src/pages/content-router.tsx +27 -0
- package/src/pages/home.tsx +16 -2
- package/src/pages/post.tsx +10 -13
- package/src/pages/slides.tsx +75 -88
- package/src/vite-env.d.ts +7 -17
- package/vite.config.ts +25 -6
package/README.md
CHANGED
|
@@ -1,130 +1,337 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<pre align="center">
|
|
3
|
+
▗▖ ▗▖▗▄▄▄▖ ▗▄▄▖▗▖ ▗▖ ▗▖
|
|
4
|
+
▐▌ ▐▌▐▌ ▐▌ ▐▌ ▝▚▞▘
|
|
5
|
+
▐▌ ▐▌▐▛▀▀▘ ▝▀▚▖▐▌ ▐▌
|
|
6
|
+
▝▚▞▘ ▐▙▄▄▖▗▄▄▞▘▐▙▄▄▖▗▞▘▝▚▖
|
|
7
|
+
</pre>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<strong>Turn markdown directories into beautiful documentation sites and presentations</strong>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="#quick-start">Quick Start</a> •
|
|
16
|
+
<a href="#features">Features</a> •
|
|
17
|
+
<a href="#components">Components</a> •
|
|
18
|
+
<a href="#slides">Slides</a> •
|
|
19
|
+
<a href="#configuration">Config</a>
|
|
20
|
+
</p>
|
|
2
21
|
|
|
3
|
-
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Why veslx?
|
|
4
25
|
|
|
5
|
-
|
|
26
|
+
**veslx** is a zero-config CLI that transforms your markdown files into a polished documentation site. Write in MDX, import React components, render LaTeX equations, display image galleries, and create slide and pdf presentations—all from simple markdown files.
|
|
27
|
+
|
|
28
|
+
Built on Vite + React + Tailwind. Fast builds. Instant hot reload. Beautiful defaults.
|
|
6
29
|
|
|
7
30
|
```bash
|
|
8
31
|
bun install -g veslx
|
|
32
|
+
veslx serve
|
|
9
33
|
```
|
|
10
34
|
|
|
35
|
+
That's it. Your docs are live at `localhost:3000`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
11
39
|
## Quick Start
|
|
12
40
|
|
|
41
|
+
### Install
|
|
42
|
+
|
|
13
43
|
```bash
|
|
14
|
-
#
|
|
15
|
-
veslx
|
|
44
|
+
# Using bun (recommended)
|
|
45
|
+
bun install -g veslx
|
|
16
46
|
|
|
17
|
-
#
|
|
18
|
-
veslx
|
|
47
|
+
# Or npm
|
|
48
|
+
npm install -g veslx
|
|
49
|
+
```
|
|
19
50
|
|
|
20
|
-
|
|
21
|
-
|
|
51
|
+
### Create Your First Post
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
mkdir -p docs/hello-world
|
|
55
|
+
cat > docs/hello-world/README.mdx << 'EOF'
|
|
56
|
+
---
|
|
57
|
+
title: Hello World
|
|
58
|
+
date: 2025-01-15
|
|
59
|
+
description: My first veslx post
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
# Hello World
|
|
63
|
+
|
|
64
|
+
Welcome to **veslx**! This is MDX, so you can use React components:
|
|
65
|
+
|
|
66
|
+
$$
|
|
67
|
+
E = mc^2
|
|
68
|
+
$$
|
|
69
|
+
|
|
70
|
+
EOF
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Run
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd docs
|
|
77
|
+
veslx serve
|
|
22
78
|
```
|
|
23
79
|
|
|
24
|
-
|
|
80
|
+
Open [localhost:3000](http://localhost:3000). Done.
|
|
81
|
+
|
|
82
|
+
---
|
|
25
83
|
|
|
26
|
-
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
| Feature | Description |
|
|
27
87
|
|---------|-------------|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
88
|
+
| **MDX** | Write markdown with embedded React components |
|
|
89
|
+
| **LaTeX** | Beautiful math rendering via KaTeX |
|
|
90
|
+
| **Syntax Highlighting** | Code blocks with Shiki (150+ languages) |
|
|
91
|
+
| **Image Galleries** | Built-in `<Gallery>` component with lightbox |
|
|
92
|
+
| **Slides** | Create presentations from markdown |
|
|
93
|
+
| **Local Imports** | Import `.tsx` components from your content directory |
|
|
94
|
+
| **Parameter Tables** | Display YAML/JSON configs with collapsible sections |
|
|
95
|
+
| **Dark Mode** | Automatic theme switching |
|
|
96
|
+
| **Hot Reload** | Instant updates during development |
|
|
97
|
+
| **Print to PDF** | Export slides as landscape PDFs |
|
|
98
|
+
|
|
99
|
+
---
|
|
33
100
|
|
|
34
101
|
## Content Structure
|
|
35
102
|
|
|
36
|
-
veslx scans your
|
|
103
|
+
veslx scans your directory for `README.mdx` (posts) and `SLIDES.mdx` (presentations):
|
|
37
104
|
|
|
38
105
|
```
|
|
39
106
|
content/
|
|
40
107
|
├── my-post/
|
|
41
|
-
│ ├── README.mdx
|
|
42
|
-
│
|
|
43
|
-
|
|
44
|
-
│
|
|
45
|
-
|
|
108
|
+
│ ├── README.mdx # → /my-post
|
|
109
|
+
│ ├── Chart.tsx # Local component (importable)
|
|
110
|
+
│ └── images/
|
|
111
|
+
│ └── figure1.png
|
|
112
|
+
├── my-slides/
|
|
113
|
+
│ └── SLIDES.mdx # → /my-slides/slides
|
|
114
|
+
└── another-post/
|
|
115
|
+
└── README.mdx
|
|
46
116
|
```
|
|
47
117
|
|
|
48
118
|
### Frontmatter
|
|
49
119
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```mdx
|
|
120
|
+
```yaml
|
|
53
121
|
---
|
|
54
122
|
title: My Post Title
|
|
55
123
|
date: 2025-01-15
|
|
56
|
-
description: A brief description
|
|
57
|
-
visibility: public
|
|
124
|
+
description: A brief description
|
|
125
|
+
visibility: public # or "hidden" to hide from listings
|
|
126
|
+
---
|
|
127
|
+
```
|
|
128
|
+
|
|
58
129
|
---
|
|
59
130
|
|
|
60
|
-
|
|
131
|
+
## Components
|
|
132
|
+
|
|
133
|
+
### Gallery
|
|
134
|
+
|
|
135
|
+
Display images with titles, captions, and a fullscreen lightbox:
|
|
136
|
+
|
|
137
|
+
```mdx
|
|
138
|
+
<Gallery
|
|
139
|
+
path="my-post/images"
|
|
140
|
+
globs={["*.png", "*.jpg"]}
|
|
141
|
+
title="Experiment Results"
|
|
142
|
+
subtitle="Phase 1 measurements"
|
|
143
|
+
caption="Data collected over 30 days"
|
|
144
|
+
captionLabel="Figure 1"
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Prop | Type | Description |
|
|
149
|
+
|------|------|-------------|
|
|
150
|
+
| `path` | `string` | Path to images directory |
|
|
151
|
+
| `globs` | `string[]` | Glob patterns to match files |
|
|
152
|
+
| `title` | `string` | Gallery title |
|
|
153
|
+
| `subtitle` | `string` | Subtitle below title |
|
|
154
|
+
| `caption` | `string` | Caption below images |
|
|
155
|
+
| `captionLabel` | `string` | Label prefix (e.g., "Figure 1") |
|
|
156
|
+
| `limit` | `number` | Max images to show |
|
|
157
|
+
|
|
158
|
+
### ParameterTable
|
|
159
|
+
|
|
160
|
+
Display YAML or JSON configuration files with collapsible sections:
|
|
161
|
+
|
|
162
|
+
```mdx
|
|
163
|
+
<ParameterTable
|
|
164
|
+
path="config.yaml"
|
|
165
|
+
keys={[".simulation.timestep", ".model.layers"]}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Prop | Type | Description |
|
|
170
|
+
|------|------|-------------|
|
|
171
|
+
| `path` | `string` | Path to YAML/JSON file |
|
|
172
|
+
| `keys` | `string[]` | jq-like paths to filter (optional) |
|
|
173
|
+
|
|
174
|
+
### Local Imports
|
|
175
|
+
|
|
176
|
+
Import React components directly from your content directory:
|
|
177
|
+
|
|
178
|
+
```mdx
|
|
179
|
+
import Chart from './Chart.tsx'
|
|
180
|
+
import { DataTable } from './components/DataTable.tsx'
|
|
181
|
+
|
|
182
|
+
# My Analysis
|
|
183
|
+
|
|
184
|
+
<Chart data={[25, 50, 75, 40, 90]} />
|
|
185
|
+
<DataTable source="results.json" />
|
|
61
186
|
```
|
|
62
187
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
| `date` | Publication date |
|
|
67
|
-
| `description` | Short description shown in listings |
|
|
68
|
-
| `visibility` | Set to `hidden` to hide from listings |
|
|
188
|
+
Components are compiled at build time by Vite—no runtime overhead.
|
|
189
|
+
|
|
190
|
+
---
|
|
69
191
|
|
|
70
|
-
|
|
192
|
+
## Slides
|
|
71
193
|
|
|
72
194
|
Create presentations in `SLIDES.mdx` files. Separate slides with `---`:
|
|
73
195
|
|
|
74
196
|
```mdx
|
|
75
197
|
---
|
|
76
198
|
title: My Presentation
|
|
199
|
+
date: 2025-01-15
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
<FrontMatter />
|
|
203
|
+
|
|
77
204
|
---
|
|
78
205
|
|
|
79
|
-
#
|
|
206
|
+
# Introduction
|
|
207
|
+
|
|
208
|
+
First slide content
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
# Methods
|
|
213
|
+
|
|
214
|
+
Second slide with an image gallery:
|
|
215
|
+
|
|
216
|
+
<Gallery path="images" globs={["chart*.png"]} />
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
# Conclusion
|
|
221
|
+
|
|
222
|
+
Final thoughts
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Navigation
|
|
226
|
+
|
|
227
|
+
| Key | Action |
|
|
228
|
+
|-----|--------|
|
|
229
|
+
| `↓` `→` `j` | Next slide |
|
|
230
|
+
| `↑` `←` `k` | Previous slide |
|
|
231
|
+
| Scroll | Natural trackpad scrolling |
|
|
232
|
+
|
|
233
|
+
### Print to PDF
|
|
80
234
|
|
|
81
|
-
|
|
235
|
+
1. Open slides in browser
|
|
236
|
+
2. Press `Cmd+P` (or `Ctrl+P`)
|
|
237
|
+
3. Select "Save as PDF"
|
|
238
|
+
4. Choose **Landscape** orientation
|
|
239
|
+
|
|
240
|
+
Each slide becomes one PDF page, centered and optimized for print.
|
|
82
241
|
|
|
83
242
|
---
|
|
84
243
|
|
|
85
|
-
|
|
244
|
+
## CLI Commands
|
|
86
245
|
|
|
87
|
-
|
|
246
|
+
```bash
|
|
247
|
+
veslx init # Create veslx.config.ts
|
|
248
|
+
veslx serve # Start dev server with hot reload
|
|
249
|
+
veslx build # Build for production → dist/
|
|
250
|
+
veslx start # Run as background daemon (PM2)
|
|
251
|
+
veslx stop # Stop the daemon
|
|
88
252
|
```
|
|
89
253
|
|
|
90
|
-
|
|
254
|
+
---
|
|
91
255
|
|
|
92
256
|
## Configuration
|
|
93
257
|
|
|
94
|
-
|
|
258
|
+
Create `veslx.config.ts` in your content root:
|
|
95
259
|
|
|
96
260
|
```typescript
|
|
97
261
|
export default {
|
|
98
|
-
dir: './content',
|
|
262
|
+
dir: './content', // Content directory (default: current dir)
|
|
99
263
|
}
|
|
100
264
|
```
|
|
101
265
|
|
|
102
|
-
|
|
266
|
+
Or run `veslx init` to generate it.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Styling
|
|
103
271
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- **
|
|
107
|
-
- **
|
|
108
|
-
- **
|
|
109
|
-
|
|
110
|
-
|
|
272
|
+
veslx uses a clean, technical aesthetic out of the box:
|
|
273
|
+
|
|
274
|
+
- **Typography**: Inter + JetBrains Mono
|
|
275
|
+
- **Colors**: Neutral palette with cyan accents
|
|
276
|
+
- **Dark mode**: Automatic system detection + manual toggle
|
|
277
|
+
|
|
278
|
+
Built on [Tailwind CSS](https://tailwindcss.com) and [shadcn/ui](https://ui.shadcn.com) components.
|
|
279
|
+
|
|
280
|
+
---
|
|
111
281
|
|
|
112
282
|
## Development
|
|
113
283
|
|
|
114
284
|
```bash
|
|
285
|
+
# Clone the repo
|
|
286
|
+
git clone https://github.com/eoinmurray/veslx.git
|
|
287
|
+
cd veslx
|
|
288
|
+
|
|
115
289
|
# Install dependencies
|
|
116
290
|
bun install
|
|
117
291
|
|
|
118
|
-
# Run
|
|
292
|
+
# Run in dev mode (with hot reload on src/)
|
|
119
293
|
bun run dev
|
|
120
294
|
|
|
121
|
-
#
|
|
122
|
-
bun run
|
|
295
|
+
# Build for production
|
|
296
|
+
bun run build
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Project Structure
|
|
123
300
|
|
|
124
|
-
# Lint
|
|
125
|
-
bun run lint
|
|
126
301
|
```
|
|
302
|
+
veslx/
|
|
303
|
+
├── bin/ # CLI entry point
|
|
304
|
+
├── plugin/ # Vite plugins (MDX, content scanning)
|
|
305
|
+
├── src/
|
|
306
|
+
│ ├── components/ # React components (Gallery, Slides, etc.)
|
|
307
|
+
│ ├── hooks/ # React hooks
|
|
308
|
+
│ ├── pages/ # Route pages
|
|
309
|
+
│ └── lib/ # Utilities
|
|
310
|
+
└── test-content/ # Example content for testing
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Tech Stack
|
|
316
|
+
|
|
317
|
+
- **Runtime**: [Bun](https://bun.sh)
|
|
318
|
+
- **Build**: [Vite](https://vitejs.dev)
|
|
319
|
+
- **Framework**: [React 19](https://react.dev)
|
|
320
|
+
- **Styling**: [Tailwind CSS 4](https://tailwindcss.com)
|
|
321
|
+
- **Components**: [shadcn/ui](https://ui.shadcn.com) + [Radix](https://radix-ui.com)
|
|
322
|
+
- **MDX**: [@mdx-js/rollup](https://mdxjs.com)
|
|
323
|
+
- **Math**: [KaTeX](https://katex.org)
|
|
324
|
+
- **Syntax**: [Shiki](https://shiki.style)
|
|
325
|
+
- **Daemon**: [PM2](https://pm2.keymetrics.io)
|
|
326
|
+
|
|
327
|
+
---
|
|
127
328
|
|
|
128
329
|
## License
|
|
129
330
|
|
|
130
331
|
MIT
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
<p align="center">
|
|
336
|
+
<sub>Built with care for researchers, engineers, and anyone who writes technical content.</sub>
|
|
337
|
+
</p>
|
package/bin/lib/build.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
1
|
import { build } from 'vite'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import fs from 'fs'
|
|
5
5
|
import importConfig from "./import-config";
|
|
@@ -24,17 +24,69 @@ function copyDirSync(src: string, dest: string) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
interface PackageJson {
|
|
28
|
+
name?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function readPackageJson(cwd: string): Promise<PackageJson | null> {
|
|
33
|
+
const file = Bun.file(path.join(cwd, 'package.json'));
|
|
34
|
+
if (!await file.exists()) return null;
|
|
35
|
+
try {
|
|
36
|
+
return await file.json();
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getGitHubRepo(cwd: string): string {
|
|
43
|
+
try {
|
|
44
|
+
const remote = execSync('git remote get-url origin', { cwd, encoding: 'utf-8' }).trim();
|
|
45
|
+
const match = remote.match(/github\.com[:/]([^/]+\/[^/.]+)/);
|
|
46
|
+
return match ? match[1] : '';
|
|
47
|
+
} catch {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function getDefaultConfig(cwd: string) {
|
|
53
|
+
const pkg = await readPackageJson(cwd);
|
|
54
|
+
const folderName = path.basename(cwd);
|
|
55
|
+
const name = pkg?.name || folderName;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
dir: '.',
|
|
59
|
+
site: {
|
|
60
|
+
name,
|
|
61
|
+
description: pkg?.description || '',
|
|
62
|
+
github: getGitHubRepo(cwd),
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default async function buildApp(dir?: string) {
|
|
28
68
|
const cwd = process.cwd()
|
|
29
69
|
|
|
30
70
|
console.log(`Building veslx app in ${cwd}`);
|
|
31
71
|
|
|
32
|
-
|
|
72
|
+
// Resolve content directory from CLI arg
|
|
73
|
+
const contentDir = dir
|
|
74
|
+
? (path.isAbsolute(dir) ? dir : path.resolve(cwd, dir))
|
|
75
|
+
: cwd;
|
|
33
76
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
77
|
+
// Get defaults first, then merge with config file if it exists
|
|
78
|
+
// Look for config in content directory first, then fall back to cwd
|
|
79
|
+
const defaults = await getDefaultConfig(contentDir);
|
|
80
|
+
const fileConfig = await importConfig(contentDir) || await importConfig(cwd);
|
|
81
|
+
|
|
82
|
+
// CLI argument takes precedence over config file
|
|
83
|
+
const config = {
|
|
84
|
+
dir: dir || fileConfig?.dir || defaults.dir,
|
|
85
|
+
site: {
|
|
86
|
+
...defaults.site,
|
|
87
|
+
...fileConfig?.site,
|
|
88
|
+
}
|
|
89
|
+
};
|
|
38
90
|
|
|
39
91
|
const veslxRoot = new URL('../..', import.meta.url).pathname;
|
|
40
92
|
const configFile = new URL('../../vite.config.ts', import.meta.url).pathname;
|
|
@@ -43,10 +95,10 @@ export default async function buildApp() {
|
|
|
43
95
|
const tempOutDir = path.join(veslxRoot, '.veslx-build')
|
|
44
96
|
const finalOutDir = path.join(cwd, 'dist')
|
|
45
97
|
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
?
|
|
49
|
-
: path.resolve(cwd, config.dir);
|
|
98
|
+
// Final content directory: CLI arg already resolved, or resolve from config
|
|
99
|
+
const finalContentDir = dir
|
|
100
|
+
? contentDir
|
|
101
|
+
: (path.isAbsolute(config.dir) ? config.dir : path.resolve(cwd, config.dir));
|
|
50
102
|
|
|
51
103
|
await build({
|
|
52
104
|
root: veslxRoot,
|
|
@@ -63,7 +115,7 @@ export default async function buildApp() {
|
|
|
63
115
|
},
|
|
64
116
|
},
|
|
65
117
|
plugins: [
|
|
66
|
-
veslxPlugin(
|
|
118
|
+
veslxPlugin(finalContentDir, config)
|
|
67
119
|
],
|
|
68
120
|
logLevel: 'info',
|
|
69
121
|
})
|
|
@@ -78,4 +130,4 @@ export default async function buildApp() {
|
|
|
78
130
|
fs.rmSync(tempOutDir, { recursive: true })
|
|
79
131
|
|
|
80
132
|
console.log(`\nBuild complete: ${finalOutDir}`)
|
|
81
|
-
}
|
|
133
|
+
}
|
package/bin/lib/import-config.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import yaml from 'js-yaml';
|
|
2
|
+
import type { VeslxConfig } from '../../plugin/src/types';
|
|
1
3
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const file = Bun.file(`${root}/veslx.config.ts`);
|
|
4
|
+
export default async function importConfig(root: string): Promise<VeslxConfig | undefined> {
|
|
5
|
+
const file = Bun.file(`${root}/veslx.yaml`);
|
|
5
6
|
|
|
6
7
|
if (!await file.exists()) {
|
|
7
|
-
return
|
|
8
|
+
return undefined;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const config =
|
|
12
|
-
return config
|
|
13
|
-
}
|
|
10
|
+
|
|
11
|
+
const content = await file.text();
|
|
12
|
+
const config = yaml.load(content) as VeslxConfig;
|
|
13
|
+
return config;
|
|
14
|
+
}
|
package/bin/lib/init.ts
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
import
|
|
1
|
+
import nodePath from "path";
|
|
2
|
+
import yaml from "js-yaml";
|
|
2
3
|
|
|
3
4
|
export default async function createNewConfig() {
|
|
5
|
+
const configPath = "veslx.yaml";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
console.error(`Configuration file '${path}' already exists in the current directory.`);
|
|
9
|
-
return
|
|
7
|
+
if (await Bun.file(configPath).exists()) {
|
|
8
|
+
console.error(`Configuration file '${configPath}' already exists.`);
|
|
9
|
+
return;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const dir = createPrompt("Enter dir: ");
|
|
15
|
-
if (dir.error) {
|
|
16
|
-
console.error("Failed to read dir");
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const folderName = nodePath.basename(cwd);
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
const config = {
|
|
16
|
+
dir: ".",
|
|
17
|
+
site: {
|
|
18
|
+
name: folderName,
|
|
19
|
+
github: "",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
21
22
|
|
|
22
|
-
const configStr =
|
|
23
|
-
export default {
|
|
24
|
-
dir: '${dir.value}',
|
|
25
|
-
}`
|
|
23
|
+
const configStr = yaml.dump(config, { indent: 2, quotingType: '"' });
|
|
26
24
|
|
|
27
|
-
await Bun.write(
|
|
25
|
+
await Bun.write(configPath, configStr);
|
|
28
26
|
|
|
29
|
-
console.log(
|
|
30
|
-
console.log(
|
|
31
|
-
|
|
27
|
+
console.log(`Created veslx.yaml`);
|
|
28
|
+
console.log(`\nEdit the file to customize your site, then run:`);
|
|
29
|
+
console.log(` veslx serve`);
|
|
30
|
+
}
|
package/bin/lib/serve.ts
CHANGED
|
@@ -1,27 +1,81 @@
|
|
|
1
1
|
import { createServer } from 'vite'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
2
3
|
import importConfig from "./import-config";
|
|
3
4
|
import veslxPlugin from '../../plugin/src/plugin'
|
|
4
5
|
import path from 'path'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
interface PackageJson {
|
|
8
|
+
name?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function readPackageJson(cwd: string): Promise<PackageJson | null> {
|
|
13
|
+
const file = Bun.file(path.join(cwd, 'package.json'));
|
|
14
|
+
if (!await file.exists()) return null;
|
|
15
|
+
try {
|
|
16
|
+
return await file.json();
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getGitHubRepo(cwd: string): string {
|
|
23
|
+
try {
|
|
24
|
+
const remote = execSync('git remote get-url origin', { cwd, encoding: 'utf-8' }).trim();
|
|
25
|
+
// Parse github URL: git@github.com:user/repo.git or https://github.com/user/repo.git
|
|
26
|
+
const match = remote.match(/github\.com[:/]([^/]+\/[^/.]+)/);
|
|
27
|
+
return match ? match[1] : '';
|
|
28
|
+
} catch {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function getDefaultConfig(cwd: string) {
|
|
34
|
+
const pkg = await readPackageJson(cwd);
|
|
35
|
+
const folderName = path.basename(cwd);
|
|
36
|
+
const name = pkg?.name || folderName;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
dir: '.',
|
|
40
|
+
site: {
|
|
41
|
+
name,
|
|
42
|
+
description: pkg?.description || '',
|
|
43
|
+
github: getGitHubRepo(cwd),
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default async function start(dir?: string) {
|
|
7
49
|
const cwd = process.cwd()
|
|
8
50
|
|
|
9
51
|
console.log(`Starting veslx dev server in ${cwd}`);
|
|
10
52
|
|
|
11
|
-
|
|
53
|
+
// Resolve content directory - CLI arg takes precedence
|
|
54
|
+
const contentDir = dir
|
|
55
|
+
? (path.isAbsolute(dir) ? dir : path.resolve(cwd, dir))
|
|
56
|
+
: cwd;
|
|
12
57
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
58
|
+
// Get defaults first, then merge with config file if it exists
|
|
59
|
+
// Look for config in content directory first, then fall back to cwd
|
|
60
|
+
const defaults = await getDefaultConfig(contentDir);
|
|
61
|
+
const fileConfig = await importConfig(contentDir) || await importConfig(cwd);
|
|
62
|
+
|
|
63
|
+
// CLI argument takes precedence over config file
|
|
64
|
+
const config = {
|
|
65
|
+
dir: dir || fileConfig?.dir || defaults.dir,
|
|
66
|
+
site: {
|
|
67
|
+
...defaults.site,
|
|
68
|
+
...fileConfig?.site,
|
|
69
|
+
}
|
|
70
|
+
};
|
|
17
71
|
|
|
18
72
|
const veslxRoot = new URL('../..', import.meta.url).pathname;
|
|
19
73
|
const configFile = new URL('../../vite.config.ts', import.meta.url).pathname;
|
|
20
74
|
|
|
21
|
-
//
|
|
22
|
-
const
|
|
23
|
-
?
|
|
24
|
-
: path.resolve(cwd, config.dir);
|
|
75
|
+
// Final content directory: CLI arg already resolved, or resolve from config
|
|
76
|
+
const finalContentDir = dir
|
|
77
|
+
? contentDir
|
|
78
|
+
: (path.isAbsolute(config.dir) ? config.dir : path.resolve(cwd, config.dir));
|
|
25
79
|
|
|
26
80
|
const server = await createServer({
|
|
27
81
|
root: veslxRoot,
|
|
@@ -29,11 +83,11 @@ export default async function start() {
|
|
|
29
83
|
// Cache in user's project so it persists across bunx runs
|
|
30
84
|
cacheDir: path.join(cwd, 'node_modules/.vite'),
|
|
31
85
|
plugins: [
|
|
32
|
-
veslxPlugin(
|
|
86
|
+
veslxPlugin(finalContentDir, config)
|
|
33
87
|
],
|
|
34
88
|
})
|
|
35
89
|
|
|
36
90
|
await server.listen()
|
|
37
91
|
server.printUrls()
|
|
38
92
|
server.bindCLIShortcuts({ print: true })
|
|
39
|
-
}
|
|
93
|
+
}
|