veslx 0.1.14 → 0.1.16

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.
Files changed (82) hide show
  1. package/README.md +262 -55
  2. package/bin/lib/build.ts +65 -13
  3. package/bin/lib/import-config.ts +10 -9
  4. package/bin/lib/init.ts +21 -22
  5. package/bin/lib/serve.ts +66 -12
  6. package/bin/veslx.ts +2 -2
  7. package/dist/client/App.js +3 -9
  8. package/dist/client/App.js.map +1 -1
  9. package/dist/client/components/front-matter.js +11 -25
  10. package/dist/client/components/front-matter.js.map +1 -1
  11. package/dist/client/components/gallery/components/figure-caption.js +6 -4
  12. package/dist/client/components/gallery/components/figure-caption.js.map +1 -1
  13. package/dist/client/components/gallery/components/figure-header.js +3 -3
  14. package/dist/client/components/gallery/components/figure-header.js.map +1 -1
  15. package/dist/client/components/gallery/components/lightbox.js +13 -13
  16. package/dist/client/components/gallery/components/lightbox.js.map +1 -1
  17. package/dist/client/components/gallery/components/loading-image.js +11 -10
  18. package/dist/client/components/gallery/components/loading-image.js.map +1 -1
  19. package/dist/client/components/gallery/hooks/use-gallery-images.js +31 -7
  20. package/dist/client/components/gallery/hooks/use-gallery-images.js.map +1 -1
  21. package/dist/client/components/gallery/index.js +22 -15
  22. package/dist/client/components/gallery/index.js.map +1 -1
  23. package/dist/client/components/header.js +5 -3
  24. package/dist/client/components/header.js.map +1 -1
  25. package/dist/client/components/mdx-components.js +42 -8
  26. package/dist/client/components/mdx-components.js.map +1 -1
  27. package/dist/client/components/post-list.js +97 -90
  28. package/dist/client/components/post-list.js.map +1 -1
  29. package/dist/client/components/running-bar.js +1 -1
  30. package/dist/client/components/running-bar.js.map +1 -1
  31. package/dist/client/components/slide.js +18 -0
  32. package/dist/client/components/slide.js.map +1 -0
  33. package/dist/client/components/slides-renderer.js +7 -71
  34. package/dist/client/components/slides-renderer.js.map +1 -1
  35. package/dist/client/hooks/use-mdx-content.js +55 -9
  36. package/dist/client/hooks/use-mdx-content.js.map +1 -1
  37. package/dist/client/main.js +1 -0
  38. package/dist/client/main.js.map +1 -1
  39. package/dist/client/pages/content-router.js +19 -0
  40. package/dist/client/pages/content-router.js.map +1 -0
  41. package/dist/client/pages/home.js +11 -7
  42. package/dist/client/pages/home.js.map +1 -1
  43. package/dist/client/pages/post.js +8 -20
  44. package/dist/client/pages/post.js.map +1 -1
  45. package/dist/client/pages/slides.js +62 -86
  46. package/dist/client/pages/slides.js.map +1 -1
  47. package/dist/client/plugin/src/client.js +58 -96
  48. package/dist/client/plugin/src/client.js.map +1 -1
  49. package/dist/client/plugin/src/directory-tree.js +111 -0
  50. package/dist/client/plugin/src/directory-tree.js.map +1 -0
  51. package/index.html +1 -1
  52. package/package.json +27 -15
  53. package/plugin/src/client.tsx +64 -116
  54. package/plugin/src/directory-tree.ts +171 -0
  55. package/plugin/src/lib.ts +6 -249
  56. package/plugin/src/plugin.ts +93 -50
  57. package/plugin/src/remark-slides.ts +100 -0
  58. package/plugin/src/types.ts +22 -0
  59. package/src/App.tsx +3 -6
  60. package/src/components/front-matter.tsx +14 -29
  61. package/src/components/gallery/components/figure-caption.tsx +15 -7
  62. package/src/components/gallery/components/figure-header.tsx +3 -3
  63. package/src/components/gallery/components/lightbox.tsx +15 -13
  64. package/src/components/gallery/components/loading-image.tsx +15 -12
  65. package/src/components/gallery/hooks/use-gallery-images.ts +51 -10
  66. package/src/components/gallery/index.tsx +32 -26
  67. package/src/components/header.tsx +14 -9
  68. package/src/components/mdx-components.tsx +61 -8
  69. package/src/components/post-list.tsx +149 -115
  70. package/src/components/running-bar.tsx +1 -1
  71. package/src/components/slide.tsx +22 -5
  72. package/src/components/slides-renderer.tsx +7 -115
  73. package/src/components/welcome.tsx +11 -14
  74. package/src/hooks/use-mdx-content.ts +94 -9
  75. package/src/index.css +159 -0
  76. package/src/main.tsx +1 -0
  77. package/src/pages/content-router.tsx +27 -0
  78. package/src/pages/home.tsx +16 -2
  79. package/src/pages/post.tsx +10 -13
  80. package/src/pages/slides.tsx +75 -88
  81. package/src/vite-env.d.ts +7 -17
  82. package/vite.config.ts +31 -6
package/README.md CHANGED
@@ -1,130 +1,337 @@
1
- # veslx
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
- A CLI tool for turning markdown directories into beautiful documentation sites and slide presentations.
22
+ ---
23
+
24
+ ## Why veslx?
4
25
 
5
- ## Installation
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
- # Initialize in your content directory
15
- veslx init
44
+ # Using bun (recommended)
45
+ bun install -g veslx
16
46
 
17
- # Start development server
18
- veslx serve
47
+ # Or npm
48
+ npm install -g veslx
49
+ ```
19
50
 
20
- # Build for production
21
- veslx build
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
- ## Commands
80
+ Open [localhost:3000](http://localhost:3000). Done.
81
+
82
+ ---
25
83
 
26
- | Command | Description |
84
+ ## Features
85
+
86
+ | Feature | Description |
27
87
  |---------|-------------|
28
- | `veslx init` | Create a `veslx.config.ts` configuration file |
29
- | `veslx serve` | Start development server with hot reload |
30
- | `veslx build` | Build for production to `dist/` |
31
- | `veslx start` | Run as a background daemon (PM2) |
32
- | `veslx stop` | Stop the daemon |
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 content directory for markdown files and renders them as posts or slides:
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 # Rendered as a blog post
42
- └── SLIDES.mdx # Rendered as a presentation
43
- ├── another-post/
44
- └── README.mdx
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
- Add YAML frontmatter to control metadata:
51
-
52
- ```mdx
120
+ ```yaml
53
121
  ---
54
122
  title: My Post Title
55
123
  date: 2025-01-15
56
- description: A brief description of the post
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
- Your content here...
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
- | Field | Description |
64
- |-------|-------------|
65
- | `title` | Display title (falls back to directory name) |
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
- ### Slides
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
- # Slide 1
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
- Content for the first slide
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
- # Slide 2
244
+ ## CLI Commands
86
245
 
87
- Content for the second slide
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
- Navigate slides with arrow keys or `j`/`k`.
254
+ ---
91
255
 
92
256
  ## Configuration
93
257
 
94
- The `veslx.config.ts` file specifies your content directory:
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
- ## Features
266
+ Or run `veslx init` to generate it.
267
+
268
+ ---
269
+
270
+ ## Styling
103
271
 
104
- - **MDX Support** - Write with React components in markdown
105
- - **Math Rendering** - LaTeX equations via KaTeX
106
- - **Syntax Highlighting** - Code blocks with Shiki
107
- - **Hot Reload** - See changes instantly during development
108
- - **Slide Presentations** - Keyboard-navigable slides from markdown
109
- - **Dark Mode** - Automatic theme switching
110
- - **Daemon Mode** - Run as a background service with PM2
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 locally
292
+ # Run in dev mode (with hot reload on src/)
119
293
  bun run dev
120
294
 
121
- # Type check
122
- bun run typecheck
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
- export default async function buildApp() {
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
- const config = await importConfig(cwd);
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
- if (!config) {
35
- console.error("Configuration file 'veslx.config.ts' not found in the current directory.");
36
- return
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
- // Resolve content directory relative to user's cwd (where config lives)
47
- const contentDir = path.isAbsolute(config.dir)
48
- ? config.dir
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(contentDir)
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
+ }
@@ -1,13 +1,14 @@
1
+ import yaml from 'js-yaml';
2
+ import type { VeslxConfig } from '../../plugin/src/types';
1
3
 
2
-
3
- export default async function importConfig(root: string) {
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 module = await import(`file://${root}/veslx.config.ts`);
11
- const config = module.default;
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 { createPrompt } from "bun-promptx";
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
- const path = "veslx.config.ts"
6
-
7
- if (await Bun.file(path).exists()) {
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
- console.log("Initializing a new veslx project...");
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
- console.log("You entered:", dir.value);
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(path, configStr.trim());
25
+ await Bun.write(configPath, configStr);
28
26
 
29
- console.log("Created 'veslx.config.ts' in the current directory.");
30
- console.log("You can now run 'veslx serve' to start the development server.");
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
- export default async function start() {
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
- const config = await importConfig(cwd);
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
- if (!config) {
14
- console.error("Configuration file 'veslx.config.ts' not found in the current directory.");
15
- return
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
- // Resolve content directory relative to user's cwd (where config lives)
22
- const contentDir = path.isAbsolute(config.dir)
23
- ? config.dir
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(contentDir)
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
+ }