veslx 0.0.21 → 0.0.22
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 +128 -1
- package/bin/lib/build.ts +6 -1
- package/bin/lib/serve.ts +6 -34
- package/package.json +1 -1
- package/plugin/src/plugin.ts +9 -6
- package/vite.config.ts +0 -3
package/README.md
CHANGED
|
@@ -1,3 +1,130 @@
|
|
|
1
|
-
|
|
2
1
|
# veslx
|
|
3
2
|
|
|
3
|
+
A CLI tool for turning markdown directories into beautiful documentation sites and slide presentations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install -g veslx
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Initialize in your content directory
|
|
15
|
+
veslx init
|
|
16
|
+
|
|
17
|
+
# Start development server
|
|
18
|
+
veslx serve
|
|
19
|
+
|
|
20
|
+
# Build for production
|
|
21
|
+
veslx build
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
| Command | Description |
|
|
27
|
+
|---------|-------------|
|
|
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 |
|
|
33
|
+
|
|
34
|
+
## Content Structure
|
|
35
|
+
|
|
36
|
+
veslx scans your content directory for markdown files and renders them as posts or slides:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
content/
|
|
40
|
+
├── my-post/
|
|
41
|
+
│ ├── README.mdx # Rendered as a blog post
|
|
42
|
+
│ └── SLIDES.mdx # Rendered as a presentation
|
|
43
|
+
├── another-post/
|
|
44
|
+
│ └── README.mdx
|
|
45
|
+
└── ...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Frontmatter
|
|
49
|
+
|
|
50
|
+
Add YAML frontmatter to control metadata:
|
|
51
|
+
|
|
52
|
+
```mdx
|
|
53
|
+
---
|
|
54
|
+
title: My Post Title
|
|
55
|
+
date: 2025-01-15
|
|
56
|
+
description: A brief description of the post
|
|
57
|
+
visibility: public
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
Your content here...
|
|
61
|
+
```
|
|
62
|
+
|
|
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 |
|
|
69
|
+
|
|
70
|
+
### Slides
|
|
71
|
+
|
|
72
|
+
Create presentations in `SLIDES.mdx` files. Separate slides with `---`:
|
|
73
|
+
|
|
74
|
+
```mdx
|
|
75
|
+
---
|
|
76
|
+
title: My Presentation
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
# Slide 1
|
|
80
|
+
|
|
81
|
+
Content for the first slide
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
# Slide 2
|
|
86
|
+
|
|
87
|
+
Content for the second slide
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Navigate slides with arrow keys or `j`/`k`.
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
The `veslx.config.ts` file specifies your content directory:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
export default {
|
|
98
|
+
dir: './content',
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Features
|
|
103
|
+
|
|
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
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Install dependencies
|
|
116
|
+
bun install
|
|
117
|
+
|
|
118
|
+
# Run locally
|
|
119
|
+
bun run dev
|
|
120
|
+
|
|
121
|
+
# Type check
|
|
122
|
+
bun run typecheck
|
|
123
|
+
|
|
124
|
+
# Lint
|
|
125
|
+
bun run lint
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT
|
package/bin/lib/build.ts
CHANGED
|
@@ -19,6 +19,11 @@ export default async function buildApp() {
|
|
|
19
19
|
const veslxRoot = new URL('../..', import.meta.url).pathname;
|
|
20
20
|
const outDir = path.join(cwd, 'dist')
|
|
21
21
|
|
|
22
|
+
// Resolve content directory relative to user's cwd (where config lives)
|
|
23
|
+
const contentDir = path.isAbsolute(config.dir)
|
|
24
|
+
? config.dir
|
|
25
|
+
: path.resolve(cwd, config.dir);
|
|
26
|
+
|
|
22
27
|
await build({
|
|
23
28
|
root: veslxRoot,
|
|
24
29
|
configFile: new URL('../../vite.config.ts', import.meta.url).pathname,
|
|
@@ -27,7 +32,7 @@ export default async function buildApp() {
|
|
|
27
32
|
emptyOutDir: true,
|
|
28
33
|
},
|
|
29
34
|
plugins: [
|
|
30
|
-
veslxPlugin(
|
|
35
|
+
veslxPlugin(contentDir)
|
|
31
36
|
],
|
|
32
37
|
})
|
|
33
38
|
|
package/bin/lib/serve.ts
CHANGED
|
@@ -3,26 +3,6 @@ import { createServer, preview } from 'vite'
|
|
|
3
3
|
import importConfig from "./import-config";
|
|
4
4
|
import veslxPlugin from '../../plugin/src/plugin'
|
|
5
5
|
import path from 'path'
|
|
6
|
-
import fs from 'fs'
|
|
7
|
-
|
|
8
|
-
// Find the node_modules directory that contains actual dependencies
|
|
9
|
-
// This handles both local dev (node_modules in project root) and
|
|
10
|
-
// bunx execution (node_modules in temp dir parent)
|
|
11
|
-
function findNodeModulesDir(startDir: string): string {
|
|
12
|
-
let dir = startDir
|
|
13
|
-
while (dir !== '/' && dir !== '.') {
|
|
14
|
-
const nm = path.join(dir, 'node_modules')
|
|
15
|
-
// Check if node_modules exists and has real packages (not just .vite cache)
|
|
16
|
-
if (fs.existsSync(nm)) {
|
|
17
|
-
const contents = fs.readdirSync(nm)
|
|
18
|
-
if (contents.some(f => !f.startsWith('.') && f !== 'veslx')) {
|
|
19
|
-
return nm
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
dir = path.dirname(dir)
|
|
23
|
-
}
|
|
24
|
-
return path.join(startDir, 'node_modules')
|
|
25
|
-
}
|
|
26
6
|
|
|
27
7
|
export default async function start() {
|
|
28
8
|
const cwd = process.cwd()
|
|
@@ -37,28 +17,20 @@ export default async function start() {
|
|
|
37
17
|
}
|
|
38
18
|
|
|
39
19
|
const veslxRoot = new URL('../..', import.meta.url).pathname;
|
|
40
|
-
|
|
20
|
+
|
|
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);
|
|
41
25
|
|
|
42
26
|
const server = await preview({
|
|
43
27
|
root: veslxRoot,
|
|
44
28
|
configFile: new URL('../../vite.config.ts', import.meta.url).pathname,
|
|
45
29
|
plugins: [
|
|
46
|
-
veslxPlugin(
|
|
30
|
+
veslxPlugin(contentDir)
|
|
47
31
|
],
|
|
48
|
-
// resolve: {
|
|
49
|
-
// // Tell Vite to look for modules in the found node_modules directory
|
|
50
|
-
// modules: [nodeModulesDir, 'node_modules'],
|
|
51
|
-
// },
|
|
52
|
-
// server: {
|
|
53
|
-
// fs: {
|
|
54
|
-
// // Allow serving from the parent node_modules
|
|
55
|
-
// allow: [veslxRoot, nodeModulesDir, cwd],
|
|
56
|
-
// },
|
|
57
|
-
// },
|
|
58
32
|
})
|
|
59
33
|
|
|
60
|
-
// await server.listen()
|
|
61
|
-
|
|
62
34
|
server.printUrls()
|
|
63
35
|
server.bindCLIShortcuts({ print: true })
|
|
64
36
|
}
|
package/package.json
CHANGED
package/plugin/src/plugin.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Plugin, type Connect } from 'vite'
|
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import { buildAll } from './lib'
|
|
5
|
-
import chokidar from 'chokidar'
|
|
5
|
+
import chokidar, { type FSWatcher } from 'chokidar'
|
|
6
6
|
import type { IncomingMessage, ServerResponse } from 'http'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -24,18 +24,21 @@ function copyDirSync(src: string, dest: string) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export default function contentPlugin(
|
|
27
|
+
export default function contentPlugin(contentDir: string): Plugin {
|
|
28
28
|
|
|
29
|
-
if (!
|
|
29
|
+
if (!contentDir) {
|
|
30
30
|
throw new Error('Content directory must be specified.')
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
if (!path.isAbsolute(contentDir)) {
|
|
34
|
+
throw new Error(`Content directory must be an absolute path, got: ${contentDir}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const dir = contentDir
|
|
35
38
|
|
|
36
39
|
const buildFn = () => buildAll([dir])
|
|
37
40
|
|
|
38
|
-
let watchers:
|
|
41
|
+
let watchers: FSWatcher[] = []
|
|
39
42
|
|
|
40
43
|
// Server middleware for serving content files
|
|
41
44
|
const urlToDir = new Map<string, string>()
|