tjs-lang 0.2.0

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 (91) hide show
  1. package/CONTEXT.md +594 -0
  2. package/LICENSE +190 -0
  3. package/README.md +220 -0
  4. package/bin/benchmarks.ts +351 -0
  5. package/bin/dev.ts +205 -0
  6. package/bin/docs.js +170 -0
  7. package/bin/install-cursor.sh +71 -0
  8. package/bin/install-vscode.sh +71 -0
  9. package/bin/select-local-models.d.ts +1 -0
  10. package/bin/select-local-models.js +28 -0
  11. package/bin/select-local-models.ts +31 -0
  12. package/demo/autocomplete.test.ts +232 -0
  13. package/demo/docs.json +186 -0
  14. package/demo/examples.test.ts +598 -0
  15. package/demo/index.html +91 -0
  16. package/demo/src/autocomplete.ts +482 -0
  17. package/demo/src/capabilities.ts +859 -0
  18. package/demo/src/demo-nav.ts +2097 -0
  19. package/demo/src/examples.test.ts +161 -0
  20. package/demo/src/examples.ts +476 -0
  21. package/demo/src/imports.test.ts +196 -0
  22. package/demo/src/imports.ts +421 -0
  23. package/demo/src/index.ts +639 -0
  24. package/demo/src/module-store.ts +635 -0
  25. package/demo/src/module-sw.ts +132 -0
  26. package/demo/src/playground.ts +949 -0
  27. package/demo/src/service-host.ts +389 -0
  28. package/demo/src/settings.ts +440 -0
  29. package/demo/src/style.ts +280 -0
  30. package/demo/src/tjs-playground.ts +1605 -0
  31. package/demo/src/ts-examples.ts +478 -0
  32. package/demo/src/ts-playground.ts +1092 -0
  33. package/demo/static/favicon.svg +30 -0
  34. package/demo/static/photo-1.jpg +0 -0
  35. package/demo/static/photo-2.jpg +0 -0
  36. package/demo/static/texts/ai-history.txt +9 -0
  37. package/demo/static/texts/coffee-origins.txt +9 -0
  38. package/demo/static/texts/renewable-energy.txt +9 -0
  39. package/dist/index.js +256 -0
  40. package/dist/index.js.map +37 -0
  41. package/dist/tjs-batteries.js +4 -0
  42. package/dist/tjs-batteries.js.map +15 -0
  43. package/dist/tjs-full.js +256 -0
  44. package/dist/tjs-full.js.map +37 -0
  45. package/dist/tjs-transpiler.js +220 -0
  46. package/dist/tjs-transpiler.js.map +21 -0
  47. package/dist/tjs-vm.js +4 -0
  48. package/dist/tjs-vm.js.map +14 -0
  49. package/docs/CNAME +1 -0
  50. package/docs/favicon.svg +30 -0
  51. package/docs/index.html +91 -0
  52. package/docs/index.js +10468 -0
  53. package/docs/index.js.map +92 -0
  54. package/docs/photo-1.jpg +0 -0
  55. package/docs/photo-1.webp +0 -0
  56. package/docs/photo-2.jpg +0 -0
  57. package/docs/photo-2.webp +0 -0
  58. package/docs/texts/ai-history.txt +9 -0
  59. package/docs/texts/coffee-origins.txt +9 -0
  60. package/docs/texts/renewable-energy.txt +9 -0
  61. package/docs/tjs-lang.svg +31 -0
  62. package/docs/tosijs-agent.svg +31 -0
  63. package/editors/README.md +325 -0
  64. package/editors/ace/ajs-mode.js +328 -0
  65. package/editors/ace/ajs-mode.ts +269 -0
  66. package/editors/ajs-syntax.ts +212 -0
  67. package/editors/build-grammars.ts +510 -0
  68. package/editors/codemirror/ajs-language.js +287 -0
  69. package/editors/codemirror/ajs-language.ts +1447 -0
  70. package/editors/codemirror/autocomplete.test.ts +531 -0
  71. package/editors/codemirror/component.ts +404 -0
  72. package/editors/monaco/ajs-monarch.js +243 -0
  73. package/editors/monaco/ajs-monarch.ts +225 -0
  74. package/editors/tjs-syntax.ts +115 -0
  75. package/editors/vscode/language-configuration.json +37 -0
  76. package/editors/vscode/package.json +65 -0
  77. package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
  78. package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
  79. package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
  80. package/package.json +83 -0
  81. package/src/cli/commands/check.ts +41 -0
  82. package/src/cli/commands/convert.ts +133 -0
  83. package/src/cli/commands/emit.ts +260 -0
  84. package/src/cli/commands/run.ts +68 -0
  85. package/src/cli/commands/test.ts +194 -0
  86. package/src/cli/commands/types.ts +20 -0
  87. package/src/cli/create-app.ts +236 -0
  88. package/src/cli/playground.ts +250 -0
  89. package/src/cli/tjs.ts +166 -0
  90. package/src/cli/tjsx.ts +160 -0
  91. package/tjs-lang.svg +31 -0
@@ -0,0 +1,351 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * TJS Benchmark Runner
4
+ *
5
+ * Runs performance benchmarks and generates benchmarks.md
6
+ *
7
+ * Usage: bun bin/benchmarks.ts
8
+ */
9
+
10
+ import { writeFileSync } from 'fs'
11
+ import { execSync } from 'child_process'
12
+ import { tjs } from '../src/lang'
13
+
14
+ const ITERATIONS = 100_000
15
+
16
+ interface BenchmarkResult {
17
+ name: string
18
+ baseline: number
19
+ safe?: number
20
+ unsafe?: number
21
+ unit: string
22
+ }
23
+
24
+ const results: BenchmarkResult[] = []
25
+
26
+ function benchmark(name: string, fn: () => void): number {
27
+ // Warmup
28
+ for (let i = 0; i < 1000; i++) fn()
29
+
30
+ const start = performance.now()
31
+ for (let i = 0; i < ITERATIONS; i++) fn()
32
+ return performance.now() - start
33
+ }
34
+
35
+ function formatRatio(value: number, baseline: number): string {
36
+ const ratio = value / baseline
37
+ if (ratio < 1.05 && ratio > 0.95) return '~1.0x'
38
+ return `${ratio.toFixed(1)}x`
39
+ }
40
+
41
+ console.log('Running TJS benchmarks...\n')
42
+
43
+ // CLI Cold Start
44
+ console.log('CLI Cold Start:')
45
+ const testFile = '/tmp/bench-test.tjs'
46
+ writeFileSync(testFile, `function add(a: 1, b: 2) -> 0 { return a + b }`)
47
+
48
+ function measureCLI(cmd: string): number {
49
+ const times: number[] = []
50
+ for (let i = 0; i < 5; i++) {
51
+ const start = performance.now()
52
+ execSync(cmd, { stdio: 'pipe' })
53
+ times.push(performance.now() - start)
54
+ }
55
+ times.sort((a, b) => a - b)
56
+ return times[Math.floor(times.length / 2)]
57
+ }
58
+
59
+ const bunTsTime = measureCLI('bun /tmp/bench-test.tjs 2>/dev/null || true')
60
+ const tjsxTime = measureCLI(
61
+ `bun ${import.meta.dir}/../src/cli/tjsx.ts ${testFile}`
62
+ )
63
+ const tjsEmitTime = measureCLI(
64
+ `bun ${import.meta.dir}/../src/cli/tjs.ts emit ${testFile}`
65
+ )
66
+ const tjsCheckTime = measureCLI(
67
+ `bun ${import.meta.dir}/../src/cli/tjs.ts check ${testFile}`
68
+ )
69
+
70
+ console.log(` Bun (TS baseline): ${bunTsTime.toFixed(0)}ms`)
71
+ console.log(` tjsx: ${tjsxTime.toFixed(0)}ms`)
72
+ console.log(` tjs emit: ${tjsEmitTime.toFixed(0)}ms`)
73
+ console.log(` tjs check: ${tjsCheckTime.toFixed(0)}ms`)
74
+
75
+ results.push({
76
+ name: 'CLI: Bun + TypeScript',
77
+ baseline: bunTsTime,
78
+ unit: 'ms',
79
+ })
80
+ results.push({
81
+ name: 'CLI: tjsx (execute TJS)',
82
+ baseline: tjsxTime,
83
+ unit: 'ms',
84
+ })
85
+ results.push({
86
+ name: 'CLI: tjs emit',
87
+ baseline: tjsEmitTime,
88
+ unit: 'ms',
89
+ })
90
+ results.push({
91
+ name: 'CLI: tjs check',
92
+ baseline: tjsCheckTime,
93
+ unit: 'ms',
94
+ })
95
+
96
+ // Simple Arithmetic - comparing safe vs unsafe(!)
97
+ console.log('\nSimple Arithmetic:')
98
+ function legacyDouble(x: number): number {
99
+ return x * 2
100
+ }
101
+
102
+ // Safe function (default) - will have inline validation
103
+ const safeDoubleResult = tjs(`function safeDouble(x: 0) -> 0 { return x * 2 }`)
104
+ const safeDouble = new Function(
105
+ `${safeDoubleResult.code}; return safeDouble;`
106
+ )()
107
+
108
+ // Unsafe function with (!) - no validation wrapper
109
+ const unsafeDoubleResult = tjs(
110
+ `function unsafeDouble(! x: 0) -> 0 { return x * 2 }`
111
+ )
112
+ const unsafeDouble = new Function(
113
+ `${unsafeDoubleResult.code}; return unsafeDouble;`
114
+ )()
115
+
116
+ const legacyTime = benchmark('legacy', () => legacyDouble(42))
117
+ const safeTime = benchmark('safe', () => safeDouble(42))
118
+ const unsafeTime = benchmark('unsafe(!)', () => unsafeDouble(42))
119
+
120
+ console.log(` Legacy JS: ${legacyTime.toFixed(2)}ms`)
121
+ console.log(
122
+ ` Safe (default): ${safeTime.toFixed(2)}ms (${formatRatio(
123
+ safeTime,
124
+ legacyTime
125
+ )})`
126
+ )
127
+ console.log(
128
+ ` Unsafe (!): ${unsafeTime.toFixed(2)}ms (${formatRatio(
129
+ unsafeTime,
130
+ legacyTime
131
+ )})`
132
+ )
133
+
134
+ results.push({
135
+ name: 'Simple arithmetic (100K iterations)',
136
+ baseline: legacyTime,
137
+ safe: safeTime,
138
+ unsafe: unsafeTime,
139
+ unit: 'ms',
140
+ })
141
+
142
+ // Object Manipulation
143
+ console.log('\nObject Manipulation:')
144
+ function legacyTransform(x: number, y: number) {
145
+ return { sum: x + y, product: x * y }
146
+ }
147
+
148
+ const safeTransformResult = tjs(`
149
+ function safeTransform(x: 0, y: 0) -> { sum: 0, product: 0 } {
150
+ return { sum: x + y, product: x * y }
151
+ }
152
+ `)
153
+ const safeTransform = new Function(
154
+ `${safeTransformResult.code}; return safeTransform;`
155
+ )()
156
+
157
+ const unsafeTransformResult = tjs(`
158
+ function unsafeTransform(! x: 0, y: 0) -> { sum: 0, product: 0 } {
159
+ return { sum: x + y, product: x * y }
160
+ }
161
+ `)
162
+ const unsafeTransform = new Function(
163
+ `${unsafeTransformResult.code}; return unsafeTransform;`
164
+ )()
165
+
166
+ const legacyObjTime = benchmark('legacy', () => legacyTransform(3, 4))
167
+ const safeObjTime = benchmark('safe', () => safeTransform(3, 4))
168
+ const unsafeObjTime = benchmark('unsafe(!)', () => unsafeTransform(3, 4))
169
+
170
+ console.log(` Legacy JS: ${legacyObjTime.toFixed(2)}ms`)
171
+ console.log(
172
+ ` Safe (default): ${safeObjTime.toFixed(2)}ms (${formatRatio(
173
+ safeObjTime,
174
+ legacyObjTime
175
+ )})`
176
+ )
177
+ console.log(
178
+ ` Unsafe (!): ${unsafeObjTime.toFixed(2)}ms (${formatRatio(
179
+ unsafeObjTime,
180
+ legacyObjTime
181
+ )})`
182
+ )
183
+
184
+ results.push({
185
+ name: 'Object manipulation (100K iterations)',
186
+ baseline: legacyObjTime,
187
+ safe: safeObjTime,
188
+ unsafe: unsafeObjTime,
189
+ unit: 'ms',
190
+ })
191
+
192
+ // 3-Function Chain - safe vs unsafe
193
+ console.log('\n3-Function Chain:')
194
+
195
+ // Create safe chain
196
+ const safeStep1Result = tjs(`function safeStep1(x: 0) -> 0 { return x * 2 }`)
197
+ const safeStep2Result = tjs(`function safeStep2(x: 0) -> 0 { return x + 10 }`)
198
+ const safeStep3Result = tjs(`function safeStep3(x: 0) -> 0 { return x / 2 }`)
199
+
200
+ const safeStep1 = new Function(`${safeStep1Result.code}; return safeStep1;`)()
201
+ const safeStep2 = new Function(`${safeStep2Result.code}; return safeStep2;`)()
202
+ const safeStep3 = new Function(`${safeStep3Result.code}; return safeStep3;`)()
203
+
204
+ // Create unsafe chain with (!)
205
+ const unsafeStep1Result = tjs(
206
+ `function unsafeStep1(! x: 0) -> 0 { return x * 2 }`
207
+ )
208
+ const unsafeStep2Result = tjs(
209
+ `function unsafeStep2(! x: 0) -> 0 { return x + 10 }`
210
+ )
211
+ const unsafeStep3Result = tjs(
212
+ `function unsafeStep3(! x: 0) -> 0 { return x / 2 }`
213
+ )
214
+
215
+ const unsafeStep1 = new Function(
216
+ `${unsafeStep1Result.code}; return unsafeStep1;`
217
+ )()
218
+ const unsafeStep2 = new Function(
219
+ `${unsafeStep2Result.code}; return unsafeStep2;`
220
+ )()
221
+ const unsafeStep3 = new Function(
222
+ `${unsafeStep3Result.code}; return unsafeStep3;`
223
+ )()
224
+
225
+ const plainChain = (x: number) => (x * 2 + 10) / 2
226
+
227
+ const plainChainTime = benchmark('plain', () => plainChain(5))
228
+ const safeChainTime = benchmark('safe', () =>
229
+ safeStep3(safeStep2(safeStep1(5)))
230
+ )
231
+ const unsafeChainTime = benchmark('unsafe(!)', () =>
232
+ unsafeStep3(unsafeStep2(unsafeStep1(5)))
233
+ )
234
+
235
+ console.log(` Plain chain: ${plainChainTime.toFixed(2)}ms`)
236
+ console.log(
237
+ ` Safe chain: ${safeChainTime.toFixed(2)}ms (${formatRatio(
238
+ safeChainTime,
239
+ plainChainTime
240
+ )})`
241
+ )
242
+ console.log(
243
+ ` Unsafe chain: ${unsafeChainTime.toFixed(2)}ms (${formatRatio(
244
+ unsafeChainTime,
245
+ plainChainTime
246
+ )})`
247
+ )
248
+
249
+ results.push({
250
+ name: '3-function chain (100K iterations)',
251
+ baseline: plainChainTime,
252
+ safe: safeChainTime,
253
+ unsafe: unsafeChainTime,
254
+ unit: 'ms',
255
+ })
256
+
257
+ // Generate Markdown
258
+ console.log('\nGenerating benchmarks.md...')
259
+
260
+ const date = new Date().toISOString().split('T')[0]
261
+ const nodeVersion = process.versions.bun || process.version
262
+ const platform = `${process.platform} ${process.arch}`
263
+
264
+ let markdown = `# TJS Benchmarks
265
+
266
+ Generated: ${date}
267
+ Runtime: Bun ${nodeVersion}
268
+ Platform: ${platform}
269
+ Iterations: ${ITERATIONS.toLocaleString()} per test
270
+
271
+ ## Summary
272
+
273
+ | Benchmark | Baseline | Safe (default) | Unsafe (!) |
274
+ |-----------|----------|----------------|------------|
275
+ `
276
+
277
+ for (const r of results) {
278
+ const baseline = `${r.baseline.toFixed(1)}${r.unit}`
279
+ const safeCol = r.safe
280
+ ? `${r.safe.toFixed(1)}${r.unit} (${formatRatio(r.safe, r.baseline)})`
281
+ : '-'
282
+ const unsafeCol = r.unsafe
283
+ ? `${r.unsafe.toFixed(1)}${r.unit} (${formatRatio(r.unsafe, r.baseline)})`
284
+ : '-'
285
+ markdown += `| ${r.name} | ${baseline} | ${safeCol} | ${unsafeCol} |\n`
286
+ }
287
+
288
+ markdown += `
289
+ ## Key Findings
290
+
291
+ ### CLI Cold Start
292
+
293
+ - **Bun + TypeScript**: ~${bunTsTime.toFixed(0)}ms (native, baseline)
294
+ - **tjsx**: ~${tjsxTime.toFixed(0)}ms (includes TJS transpiler load)
295
+ - **Overhead**: ${(tjsxTime - bunTsTime).toFixed(
296
+ 0
297
+ )}ms for transpiler initialization
298
+
299
+ The ~${(tjsxTime - bunTsTime).toFixed(
300
+ 0
301
+ )}ms overhead is from loading the acorn parser and TJS transpiler.
302
+ A compiled binary (via \`bun build --compile\`) reduces this to ~20ms.
303
+
304
+ ### Safe vs Unsafe Functions
305
+
306
+ TJS functions are **safe by default** with runtime type validation.
307
+ Use \`(!)\` to mark functions as unsafe for performance-critical code:
308
+
309
+ \`\`\`javascript
310
+ // Safe (default) - validates types at runtime
311
+ function add(a: 0, b: 0) -> 0 { return a + b }
312
+
313
+ // Unsafe - no validation, maximum performance
314
+ function fastAdd(! a: 0, b: 0) -> 0 { return a + b }
315
+ \`\`\`
316
+
317
+ Performance comparison:
318
+ - Simple arithmetic: Safe ${formatRatio(
319
+ safeTime,
320
+ legacyTime
321
+ )} vs Unsafe ${formatRatio(unsafeTime, legacyTime)}
322
+ - Object manipulation: Safe ${formatRatio(
323
+ safeObjTime,
324
+ legacyObjTime
325
+ )} vs Unsafe ${formatRatio(unsafeObjTime, legacyObjTime)}
326
+ - 3-function chain: Safe ${formatRatio(
327
+ safeChainTime,
328
+ plainChainTime
329
+ )} vs Unsafe ${formatRatio(unsafeChainTime, plainChainTime)}
330
+
331
+ ## Recommendations
332
+
333
+ 1. **Use safe functions at API boundaries** - The default is correct for most code
334
+ 2. **Use \`(!)\` for internal hot paths** - When inputs are already validated
335
+ 3. **Consider compiled binary for CLI** - \`bun build --compile\` for ~20ms startup
336
+
337
+ ## Running Benchmarks
338
+
339
+ \`\`\`bash
340
+ bun run bench
341
+ \`\`\`
342
+
343
+ Or run the test suite with timing output:
344
+
345
+ \`\`\`bash
346
+ bun test src/lang/perf.test.ts
347
+ \`\`\`
348
+ `
349
+
350
+ writeFileSync('benchmarks.md', markdown)
351
+ console.log('Done! Written to benchmarks.md')
package/bin/dev.ts ADDED
@@ -0,0 +1,205 @@
1
+ /*
2
+ * dev.ts - Development server for agent-99 demo site
3
+ *
4
+ * Run with: bun run bin/dev.ts
5
+ *
6
+ * Features:
7
+ * - Serves demo site on https://localhost:8080
8
+ * - Watches for file changes and rebuilds
9
+ * - Auto-regenerates docs.json when source files change
10
+ */
11
+
12
+ import { watch } from 'fs'
13
+ import { join } from 'path'
14
+ import { $, spawn } from 'bun'
15
+
16
+ const PORT = 8699 // Homage to Agent-99
17
+
18
+ // Kill any existing process on our port
19
+ async function killExistingServer() {
20
+ try {
21
+ const result = await $`lsof -ti:${PORT}`.quiet()
22
+ const pids = result.text().trim().split('\n').filter(Boolean)
23
+ for (const pid of pids) {
24
+ console.log(`Killing existing process on port ${PORT} (PID: ${pid})`)
25
+ await $`kill -9 ${pid}`.quiet()
26
+ }
27
+ } catch {
28
+ // No process on port, that's fine
29
+ }
30
+ }
31
+
32
+ await killExistingServer()
33
+ const DEMO_DIR = './demo'
34
+ const DOCS_DIR = './docs'
35
+ const SRC_DIR = './src'
36
+ const EDITORS_DIR = './editors'
37
+ const ROOT_DIR = '.'
38
+
39
+ // Build the demo
40
+ async function buildDemo() {
41
+ console.log('Building demo...')
42
+
43
+ try {
44
+ // Generate docs.json
45
+ await $`node bin/docs.js`
46
+
47
+ // Build the demo app (bundle everything for browser)
48
+ const result = await Bun.build({
49
+ entrypoints: ['./demo/src/index.ts'],
50
+ outdir: './docs',
51
+ minify: false,
52
+ sourcemap: 'external',
53
+ target: 'browser',
54
+ })
55
+
56
+ if (!result.success) {
57
+ console.error('Build failed:')
58
+ for (const log of result.logs) {
59
+ console.error(log)
60
+ }
61
+ return
62
+ }
63
+
64
+ // Copy static files
65
+ await $`cp demo/index.html demo/static/favicon.svg demo/static/photo-*.jpg tjs-lang.svg docs/`
66
+ await $`cp -r demo/static/texts docs/`
67
+
68
+ console.log('Build complete!')
69
+ } catch (error) {
70
+ console.error(
71
+ 'Build error:',
72
+ error instanceof Error ? error.message : error
73
+ )
74
+ }
75
+ }
76
+
77
+ // Initial build
78
+ await buildDemo()
79
+
80
+ // Debounce rebuilds to avoid multiple rapid rebuilds
81
+ let rebuildTimeout: ReturnType<typeof setTimeout> | null = null
82
+ let isBuilding = false
83
+
84
+ async function debouncedBuild(source: string) {
85
+ if (rebuildTimeout) {
86
+ clearTimeout(rebuildTimeout)
87
+ }
88
+ rebuildTimeout = setTimeout(async () => {
89
+ if (isBuilding) {
90
+ // Schedule another build after current one finishes
91
+ debouncedBuild(source)
92
+ return
93
+ }
94
+ isBuilding = true
95
+ console.log(`\n${source}`)
96
+ await buildDemo()
97
+ isBuilding = false
98
+ }, 100)
99
+ }
100
+
101
+ // Watch for changes
102
+ const watcher = watch(SRC_DIR, { recursive: true }, (event, filename) => {
103
+ debouncedBuild(`File changed: ${filename}`)
104
+ })
105
+
106
+ const demoWatcher = watch(
107
+ `${DEMO_DIR}/src`,
108
+ { recursive: true },
109
+ (event, filename) => {
110
+ debouncedBuild(`Demo file changed: ${filename}`)
111
+ }
112
+ )
113
+
114
+ // Watch for editor changes
115
+ const editorsWatcher = watch(
116
+ EDITORS_DIR,
117
+ { recursive: true },
118
+ (event, filename) => {
119
+ debouncedBuild(`Editor file changed: ${filename}`)
120
+ }
121
+ )
122
+
123
+ // Watch for markdown file changes in root
124
+ const mdWatcher = watch(ROOT_DIR, { recursive: false }, (event, filename) => {
125
+ if (filename && filename.endsWith('.md')) {
126
+ debouncedBuild(`Markdown file changed: ${filename}`)
127
+ }
128
+ })
129
+
130
+ // Serve the docs directory
131
+ const server = Bun.serve({
132
+ port: PORT,
133
+ async fetch(req) {
134
+ const url = new URL(req.url)
135
+ let pathname = url.pathname
136
+
137
+ // Default to index.html
138
+ if (pathname === '/') {
139
+ pathname = '/index.html'
140
+ }
141
+
142
+ // Try to serve from docs directory
143
+ const filePath = join(DOCS_DIR, pathname)
144
+ const file = Bun.file(filePath)
145
+
146
+ if (await file.exists()) {
147
+ // Set appropriate content type
148
+ const ext = pathname.split('.').pop()
149
+ const contentTypes: Record<string, string> = {
150
+ html: 'text/html',
151
+ js: 'application/javascript',
152
+ css: 'text/css',
153
+ json: 'application/json',
154
+ svg: 'image/svg+xml',
155
+ png: 'image/png',
156
+ jpg: 'image/jpeg',
157
+ jpeg: 'image/jpeg',
158
+ webp: 'image/webp',
159
+ gif: 'image/gif',
160
+ ico: 'image/x-icon',
161
+ map: 'application/json',
162
+ }
163
+
164
+ return new Response(file, {
165
+ headers: {
166
+ 'Content-Type': contentTypes[ext || 'html'] || 'text/plain',
167
+ 'Access-Control-Allow-Origin': '*',
168
+ },
169
+ })
170
+ }
171
+
172
+ // For SPA routing, serve index.html for unknown paths
173
+ const indexFile = Bun.file(join(DOCS_DIR, 'index.html'))
174
+ if (await indexFile.exists()) {
175
+ return new Response(indexFile, {
176
+ headers: { 'Content-Type': 'text/html' },
177
+ })
178
+ }
179
+
180
+ return new Response('Not Found', { status: 404 })
181
+ },
182
+ })
183
+
184
+ console.log(`
185
+ Development server running at http://localhost:${PORT}
186
+
187
+ Watching for changes in:
188
+ - ${SRC_DIR}/
189
+ - ${DEMO_DIR}/src/
190
+ - ${EDITORS_DIR}/
191
+ - *.md (root directory)
192
+
193
+ Press Ctrl+C to stop
194
+ `)
195
+
196
+ // Handle graceful shutdown
197
+ process.on('SIGINT', () => {
198
+ console.log('\nShutting down...')
199
+ watcher.close()
200
+ demoWatcher.close()
201
+ editorsWatcher.close()
202
+ mdWatcher.close()
203
+ server.stop()
204
+ process.exit(0)
205
+ })