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.
- package/CONTEXT.md +594 -0
- package/LICENSE +190 -0
- package/README.md +220 -0
- package/bin/benchmarks.ts +351 -0
- package/bin/dev.ts +205 -0
- package/bin/docs.js +170 -0
- package/bin/install-cursor.sh +71 -0
- package/bin/install-vscode.sh +71 -0
- package/bin/select-local-models.d.ts +1 -0
- package/bin/select-local-models.js +28 -0
- package/bin/select-local-models.ts +31 -0
- package/demo/autocomplete.test.ts +232 -0
- package/demo/docs.json +186 -0
- package/demo/examples.test.ts +598 -0
- package/demo/index.html +91 -0
- package/demo/src/autocomplete.ts +482 -0
- package/demo/src/capabilities.ts +859 -0
- package/demo/src/demo-nav.ts +2097 -0
- package/demo/src/examples.test.ts +161 -0
- package/demo/src/examples.ts +476 -0
- package/demo/src/imports.test.ts +196 -0
- package/demo/src/imports.ts +421 -0
- package/demo/src/index.ts +639 -0
- package/demo/src/module-store.ts +635 -0
- package/demo/src/module-sw.ts +132 -0
- package/demo/src/playground.ts +949 -0
- package/demo/src/service-host.ts +389 -0
- package/demo/src/settings.ts +440 -0
- package/demo/src/style.ts +280 -0
- package/demo/src/tjs-playground.ts +1605 -0
- package/demo/src/ts-examples.ts +478 -0
- package/demo/src/ts-playground.ts +1092 -0
- package/demo/static/favicon.svg +30 -0
- package/demo/static/photo-1.jpg +0 -0
- package/demo/static/photo-2.jpg +0 -0
- package/demo/static/texts/ai-history.txt +9 -0
- package/demo/static/texts/coffee-origins.txt +9 -0
- package/demo/static/texts/renewable-energy.txt +9 -0
- package/dist/index.js +256 -0
- package/dist/index.js.map +37 -0
- package/dist/tjs-batteries.js +4 -0
- package/dist/tjs-batteries.js.map +15 -0
- package/dist/tjs-full.js +256 -0
- package/dist/tjs-full.js.map +37 -0
- package/dist/tjs-transpiler.js +220 -0
- package/dist/tjs-transpiler.js.map +21 -0
- package/dist/tjs-vm.js +4 -0
- package/dist/tjs-vm.js.map +14 -0
- package/docs/CNAME +1 -0
- package/docs/favicon.svg +30 -0
- package/docs/index.html +91 -0
- package/docs/index.js +10468 -0
- package/docs/index.js.map +92 -0
- package/docs/photo-1.jpg +0 -0
- package/docs/photo-1.webp +0 -0
- package/docs/photo-2.jpg +0 -0
- package/docs/photo-2.webp +0 -0
- package/docs/texts/ai-history.txt +9 -0
- package/docs/texts/coffee-origins.txt +9 -0
- package/docs/texts/renewable-energy.txt +9 -0
- package/docs/tjs-lang.svg +31 -0
- package/docs/tosijs-agent.svg +31 -0
- package/editors/README.md +325 -0
- package/editors/ace/ajs-mode.js +328 -0
- package/editors/ace/ajs-mode.ts +269 -0
- package/editors/ajs-syntax.ts +212 -0
- package/editors/build-grammars.ts +510 -0
- package/editors/codemirror/ajs-language.js +287 -0
- package/editors/codemirror/ajs-language.ts +1447 -0
- package/editors/codemirror/autocomplete.test.ts +531 -0
- package/editors/codemirror/component.ts +404 -0
- package/editors/monaco/ajs-monarch.js +243 -0
- package/editors/monaco/ajs-monarch.ts +225 -0
- package/editors/tjs-syntax.ts +115 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +65 -0
- package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
- package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
- package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
- package/package.json +83 -0
- package/src/cli/commands/check.ts +41 -0
- package/src/cli/commands/convert.ts +133 -0
- package/src/cli/commands/emit.ts +260 -0
- package/src/cli/commands/run.ts +68 -0
- package/src/cli/commands/test.ts +194 -0
- package/src/cli/commands/types.ts +20 -0
- package/src/cli/create-app.ts +236 -0
- package/src/cli/playground.ts +250 -0
- package/src/cli/tjs.ts +166 -0
- package/src/cli/tjsx.ts +160 -0
- 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
|
+
})
|