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,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tjs types - Output type metadata as JSON
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from 'fs'
|
|
6
|
+
import { tjs } from '../../lang'
|
|
7
|
+
|
|
8
|
+
export async function types(file: string): Promise<void> {
|
|
9
|
+
const source = readFileSync(file, 'utf-8')
|
|
10
|
+
|
|
11
|
+
const result = tjs(source)
|
|
12
|
+
|
|
13
|
+
// Output the type information as JSON
|
|
14
|
+
const typeInfo = {
|
|
15
|
+
file,
|
|
16
|
+
...result.types,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log(JSON.stringify(typeInfo, null, 2))
|
|
20
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* create-tjs-app - Scaffold a new TJS project
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* bun create tjs-app my-app
|
|
7
|
+
* bunx create-tjs-app my-app
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
|
11
|
+
import { join, resolve } from 'path'
|
|
12
|
+
|
|
13
|
+
const HELP = `
|
|
14
|
+
create-tjs-app - Scaffold a new TJS project
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
bun create tjs-app <project-name>
|
|
18
|
+
bunx create-tjs-app <project-name>
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
-h, --help Show this help message
|
|
22
|
+
--minimal Create minimal project (no examples)
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
bun create tjs-app my-app
|
|
26
|
+
bun create tjs-app ./projects/new-app
|
|
27
|
+
bun create tjs-app my-app --minimal
|
|
28
|
+
`
|
|
29
|
+
|
|
30
|
+
const PACKAGE_JSON = (name: string) => `{
|
|
31
|
+
"name": "${name}",
|
|
32
|
+
"version": "0.1.0",
|
|
33
|
+
"type": "module",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"dev": "tjs-playground",
|
|
36
|
+
"check": "tjs check src/",
|
|
37
|
+
"build": "tjs emit src/ -o dist/",
|
|
38
|
+
"test": "tjs test"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"tjs-lang": "^0.1.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`
|
|
45
|
+
|
|
46
|
+
const MAIN_TJS = `/**
|
|
47
|
+
* Main entry point
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
// A simple function with type validation
|
|
51
|
+
function greet(name: 'World') -> '' {
|
|
52
|
+
return \`Hello, \${name}!\`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Run it
|
|
56
|
+
console.log(greet())
|
|
57
|
+
console.log(greet('TJS'))
|
|
58
|
+
`
|
|
59
|
+
|
|
60
|
+
const UTILS_TJS = `/**
|
|
61
|
+
* Utility functions with runtime type checking
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
// Math utilities
|
|
65
|
+
function add(a: 0, b: 0) -> 0 {
|
|
66
|
+
return a + b
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function multiply(a: 0, b: 0) -> 0 {
|
|
70
|
+
return a * b
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// String utilities
|
|
74
|
+
function capitalize(str: '') -> '' {
|
|
75
|
+
if (str.length === 0) return ''
|
|
76
|
+
return str[0].toUpperCase() + str.slice(1)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Array utilities
|
|
80
|
+
function sum(numbers: [0]) -> 0 {
|
|
81
|
+
return numbers.reduce((acc, n) => acc + n, 0)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { add, multiply, capitalize, sum }
|
|
85
|
+
`
|
|
86
|
+
|
|
87
|
+
const UTILS_TEST_TJS = `/**
|
|
88
|
+
* Tests for utility functions
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
import { add, multiply, capitalize, sum } from './utils.tjs'
|
|
92
|
+
|
|
93
|
+
test('add works with numbers', () => {
|
|
94
|
+
expect(add(1, 2)).toBe(3)
|
|
95
|
+
expect(add(-1, 1)).toBe(0)
|
|
96
|
+
expect(add(0.1, 0.2)).toBeCloseTo(0.3)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('multiply works with numbers', () => {
|
|
100
|
+
expect(multiply(2, 3)).toBe(6)
|
|
101
|
+
expect(multiply(-2, 3)).toBe(-6)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('capitalize handles strings', () => {
|
|
105
|
+
expect(capitalize('hello')).toBe('Hello')
|
|
106
|
+
expect(capitalize('')).toBe('')
|
|
107
|
+
expect(capitalize('a')).toBe('A')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('sum adds array elements', () => {
|
|
111
|
+
expect(sum([1, 2, 3])).toBe(6)
|
|
112
|
+
expect(sum([])).toBe(0)
|
|
113
|
+
expect(sum([5])).toBe(5)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('type validation rejects bad inputs', () => {
|
|
117
|
+
// These should return monadic errors, not throw
|
|
118
|
+
const result = add('not', 'numbers')
|
|
119
|
+
expect(result.error).toBeDefined()
|
|
120
|
+
})
|
|
121
|
+
`
|
|
122
|
+
|
|
123
|
+
const README = (name: string) => `# ${name}
|
|
124
|
+
|
|
125
|
+
A TJS (Typed JavaScript) project.
|
|
126
|
+
|
|
127
|
+
## Getting Started
|
|
128
|
+
|
|
129
|
+
\`\`\`bash
|
|
130
|
+
# Install dependencies
|
|
131
|
+
bun install
|
|
132
|
+
|
|
133
|
+
# Run the playground
|
|
134
|
+
bun run dev
|
|
135
|
+
|
|
136
|
+
# Type check all files
|
|
137
|
+
bun run check
|
|
138
|
+
|
|
139
|
+
# Run tests
|
|
140
|
+
bun run test
|
|
141
|
+
|
|
142
|
+
# Build for production
|
|
143
|
+
bun run build
|
|
144
|
+
\`\`\`
|
|
145
|
+
|
|
146
|
+
## Project Structure
|
|
147
|
+
|
|
148
|
+
\`\`\`
|
|
149
|
+
${name}/
|
|
150
|
+
├── src/
|
|
151
|
+
│ ├── main.tjs # Entry point
|
|
152
|
+
│ ├── utils.tjs # Utility functions
|
|
153
|
+
│ └── utils.test.tjs # Tests
|
|
154
|
+
├── dist/ # Built output (after build)
|
|
155
|
+
└── package.json
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
## TJS Features
|
|
159
|
+
|
|
160
|
+
TJS adds runtime type validation to JavaScript:
|
|
161
|
+
|
|
162
|
+
\`\`\`javascript
|
|
163
|
+
// Required parameter with type inference from example
|
|
164
|
+
function greet(name: 'Alice') -> '' {
|
|
165
|
+
return \`Hello, \${name}!\`
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
greet('Bob') // Returns "Hello, Bob!"
|
|
169
|
+
greet(123) // Returns { error: 'type mismatch', ... }
|
|
170
|
+
\`\`\`
|
|
171
|
+
|
|
172
|
+
Learn more at the [TJS documentation](https://github.com/tonioloewald/tjs-lang).
|
|
173
|
+
`
|
|
174
|
+
|
|
175
|
+
const GITIGNORE = `node_modules/
|
|
176
|
+
dist/
|
|
177
|
+
.DS_Store
|
|
178
|
+
*.log
|
|
179
|
+
`
|
|
180
|
+
|
|
181
|
+
async function main() {
|
|
182
|
+
const args = process.argv.slice(2)
|
|
183
|
+
|
|
184
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
185
|
+
console.log(HELP)
|
|
186
|
+
process.exit(0)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const minimal = args.includes('--minimal')
|
|
190
|
+
const projectArg = args.find((a) => !a.startsWith('-'))
|
|
191
|
+
|
|
192
|
+
if (!projectArg) {
|
|
193
|
+
console.error('Error: Please specify a project name')
|
|
194
|
+
console.error('Usage: bun create tjs-app <project-name>')
|
|
195
|
+
process.exit(1)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const projectDir = resolve(projectArg)
|
|
199
|
+
const projectName = projectArg.split('/').pop() || 'tjs-app'
|
|
200
|
+
|
|
201
|
+
// Check if directory exists
|
|
202
|
+
if (existsSync(projectDir)) {
|
|
203
|
+
console.error(`Error: Directory '${projectDir}' already exists`)
|
|
204
|
+
process.exit(1)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(`Creating TJS project in ${projectDir}...`)
|
|
208
|
+
|
|
209
|
+
// Create directories
|
|
210
|
+
mkdirSync(projectDir, { recursive: true })
|
|
211
|
+
mkdirSync(join(projectDir, 'src'), { recursive: true })
|
|
212
|
+
|
|
213
|
+
// Write files
|
|
214
|
+
writeFileSync(join(projectDir, 'package.json'), PACKAGE_JSON(projectName))
|
|
215
|
+
writeFileSync(join(projectDir, 'README.md'), README(projectName))
|
|
216
|
+
writeFileSync(join(projectDir, '.gitignore'), GITIGNORE)
|
|
217
|
+
writeFileSync(join(projectDir, 'src', 'main.tjs'), MAIN_TJS)
|
|
218
|
+
|
|
219
|
+
if (!minimal) {
|
|
220
|
+
writeFileSync(join(projectDir, 'src', 'utils.tjs'), UTILS_TJS)
|
|
221
|
+
writeFileSync(join(projectDir, 'src', 'utils.test.tjs'), UTILS_TEST_TJS)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log(`
|
|
225
|
+
Created ${projectName}!
|
|
226
|
+
|
|
227
|
+
Next steps:
|
|
228
|
+
cd ${projectArg}
|
|
229
|
+
bun install
|
|
230
|
+
bun run dev
|
|
231
|
+
|
|
232
|
+
Happy coding!
|
|
233
|
+
`)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
main()
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* tjs-playground - Serve the TJS playground locally
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* tjs-playground [--port 8699]
|
|
7
|
+
* bunx tjs-playground
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { watch } from 'fs'
|
|
11
|
+
import { join } from 'path'
|
|
12
|
+
import { $ } from 'bun'
|
|
13
|
+
|
|
14
|
+
const DEFAULT_PORT = 8699 // Homage to Agent-99
|
|
15
|
+
|
|
16
|
+
const HELP = `
|
|
17
|
+
tjs-playground - Serve the TJS playground locally
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
tjs-playground [options]
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
-p, --port <port> Port to serve on (default: ${DEFAULT_PORT})
|
|
24
|
+
-h, --help Show this help message
|
|
25
|
+
--no-watch Don't watch for file changes
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
tjs-playground
|
|
29
|
+
tjs-playground --port 3000
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
const args = process.argv.slice(2)
|
|
34
|
+
|
|
35
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
36
|
+
console.log(HELP)
|
|
37
|
+
process.exit(0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Parse port
|
|
41
|
+
const portIdx = args.findIndex((a) => a === '-p' || a === '--port')
|
|
42
|
+
const port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : DEFAULT_PORT
|
|
43
|
+
|
|
44
|
+
const noWatch = args.includes('--no-watch')
|
|
45
|
+
|
|
46
|
+
// Find the package root (where docs/ lives)
|
|
47
|
+
// When installed via npm, this will be in node_modules/tjs-lang
|
|
48
|
+
// When running locally, it's the repo root
|
|
49
|
+
let rootDir: string
|
|
50
|
+
|
|
51
|
+
// Check if we're in node_modules
|
|
52
|
+
const scriptPath = import.meta.path
|
|
53
|
+
if (scriptPath.includes('node_modules')) {
|
|
54
|
+
// Installed as a package - go up to find package root
|
|
55
|
+
rootDir = join(import.meta.dir, '..', '..')
|
|
56
|
+
} else {
|
|
57
|
+
// Running from repo
|
|
58
|
+
rootDir = join(import.meta.dir, '..', '..')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const docsDir = join(rootDir, 'docs')
|
|
62
|
+
const srcDir = join(rootDir, 'src')
|
|
63
|
+
const demoDir = join(rootDir, 'demo')
|
|
64
|
+
const editorsDir = join(rootDir, 'editors')
|
|
65
|
+
|
|
66
|
+
// Check if docs directory exists
|
|
67
|
+
const docsExists = await Bun.file(join(docsDir, 'index.html')).exists()
|
|
68
|
+
|
|
69
|
+
if (!docsExists) {
|
|
70
|
+
console.log('Docs not built yet. Building...')
|
|
71
|
+
await buildDocs(rootDir)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Kill any existing process on our port
|
|
75
|
+
try {
|
|
76
|
+
const result = await $`lsof -ti:${port}`.quiet()
|
|
77
|
+
const pids = result.text().trim().split('\n').filter(Boolean)
|
|
78
|
+
for (const pid of pids) {
|
|
79
|
+
console.log(`Killing existing process on port ${port} (PID: ${pid})`)
|
|
80
|
+
await $`kill -9 ${pid}`.quiet()
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// No process on port
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Build function
|
|
87
|
+
async function buildDocs(root: string) {
|
|
88
|
+
console.log('Building playground...')
|
|
89
|
+
try {
|
|
90
|
+
// Generate docs.json
|
|
91
|
+
const docsScript = join(root, 'bin', 'docs.js')
|
|
92
|
+
if (await Bun.file(docsScript).exists()) {
|
|
93
|
+
await $`node ${docsScript}`.cwd(root)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Build the demo app
|
|
97
|
+
const result = await Bun.build({
|
|
98
|
+
entrypoints: [join(root, 'demo', 'src', 'index.ts')],
|
|
99
|
+
outdir: join(root, 'docs'),
|
|
100
|
+
minify: false,
|
|
101
|
+
sourcemap: 'external',
|
|
102
|
+
target: 'browser',
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (!result.success) {
|
|
106
|
+
console.error('Build failed:')
|
|
107
|
+
for (const log of result.logs) {
|
|
108
|
+
console.error(log)
|
|
109
|
+
}
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Copy static files
|
|
114
|
+
const demoStatic = join(root, 'demo', 'static')
|
|
115
|
+
const demoIndex = join(root, 'demo', 'index.html')
|
|
116
|
+
const logoSvg = join(root, 'tjs-lang.svg')
|
|
117
|
+
const targetDocs = join(root, 'docs')
|
|
118
|
+
|
|
119
|
+
await $`cp ${demoIndex} ${logoSvg} ${targetDocs}/`.quiet()
|
|
120
|
+
await $`cp ${join(demoStatic, 'favicon.svg')} ${targetDocs}/`.quiet()
|
|
121
|
+
await $`cp ${join(demoStatic, 'photo-*.jpg')} ${targetDocs}/`.quiet()
|
|
122
|
+
await $`cp -r ${join(demoStatic, 'texts')} ${targetDocs}/`.quiet()
|
|
123
|
+
|
|
124
|
+
console.log('Build complete!')
|
|
125
|
+
return true
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(
|
|
128
|
+
'Build error:',
|
|
129
|
+
error instanceof Error ? error.message : error
|
|
130
|
+
)
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Debounced rebuild
|
|
136
|
+
let rebuildTimeout: ReturnType<typeof setTimeout> | null = null
|
|
137
|
+
let isBuilding = false
|
|
138
|
+
|
|
139
|
+
async function debouncedBuild(source: string) {
|
|
140
|
+
if (rebuildTimeout) clearTimeout(rebuildTimeout)
|
|
141
|
+
rebuildTimeout = setTimeout(async () => {
|
|
142
|
+
if (isBuilding) {
|
|
143
|
+
debouncedBuild(source)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
isBuilding = true
|
|
147
|
+
console.log(`\n${source}`)
|
|
148
|
+
await buildDocs(rootDir)
|
|
149
|
+
isBuilding = false
|
|
150
|
+
}, 100)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Watch for changes if not disabled
|
|
154
|
+
const watchers: ReturnType<typeof watch>[] = []
|
|
155
|
+
|
|
156
|
+
if (!noWatch) {
|
|
157
|
+
try {
|
|
158
|
+
watchers.push(
|
|
159
|
+
watch(srcDir, { recursive: true }, (event, filename) => {
|
|
160
|
+
debouncedBuild(`File changed: ${filename}`)
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
watchers.push(
|
|
164
|
+
watch(join(demoDir, 'src'), { recursive: true }, (event, filename) => {
|
|
165
|
+
debouncedBuild(`Demo file changed: ${filename}`)
|
|
166
|
+
})
|
|
167
|
+
)
|
|
168
|
+
watchers.push(
|
|
169
|
+
watch(editorsDir, { recursive: true }, (event, filename) => {
|
|
170
|
+
debouncedBuild(`Editor file changed: ${filename}`)
|
|
171
|
+
})
|
|
172
|
+
)
|
|
173
|
+
} catch {
|
|
174
|
+
// Watching may fail if directories don't exist (installed package)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Serve the docs directory
|
|
179
|
+
const server = Bun.serve({
|
|
180
|
+
port,
|
|
181
|
+
async fetch(req) {
|
|
182
|
+
const url = new URL(req.url)
|
|
183
|
+
let pathname = url.pathname
|
|
184
|
+
|
|
185
|
+
if (pathname === '/') {
|
|
186
|
+
pathname = '/index.html'
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const filePath = join(docsDir, pathname)
|
|
190
|
+
const file = Bun.file(filePath)
|
|
191
|
+
|
|
192
|
+
if (await file.exists()) {
|
|
193
|
+
const ext = pathname.split('.').pop()
|
|
194
|
+
const contentTypes: Record<string, string> = {
|
|
195
|
+
html: 'text/html',
|
|
196
|
+
js: 'application/javascript',
|
|
197
|
+
css: 'text/css',
|
|
198
|
+
json: 'application/json',
|
|
199
|
+
svg: 'image/svg+xml',
|
|
200
|
+
png: 'image/png',
|
|
201
|
+
jpg: 'image/jpeg',
|
|
202
|
+
jpeg: 'image/jpeg',
|
|
203
|
+
webp: 'image/webp',
|
|
204
|
+
gif: 'image/gif',
|
|
205
|
+
ico: 'image/x-icon',
|
|
206
|
+
map: 'application/json',
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return new Response(file, {
|
|
210
|
+
headers: {
|
|
211
|
+
'Content-Type': contentTypes[ext || 'html'] || 'text/plain',
|
|
212
|
+
'Access-Control-Allow-Origin': '*',
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// SPA fallback
|
|
218
|
+
const indexFile = Bun.file(join(docsDir, 'index.html'))
|
|
219
|
+
if (await indexFile.exists()) {
|
|
220
|
+
return new Response(indexFile, {
|
|
221
|
+
headers: { 'Content-Type': 'text/html' },
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return new Response('Not Found', { status: 404 })
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
console.log(`
|
|
230
|
+
TJS Playground running at http://localhost:${port}
|
|
231
|
+
${
|
|
232
|
+
!noWatch
|
|
233
|
+
? `
|
|
234
|
+
Watching for changes...`
|
|
235
|
+
: ''
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
Press Ctrl+C to stop
|
|
239
|
+
`)
|
|
240
|
+
|
|
241
|
+
// Graceful shutdown
|
|
242
|
+
process.on('SIGINT', () => {
|
|
243
|
+
console.log('\nShutting down...')
|
|
244
|
+
watchers.forEach((w) => w.close())
|
|
245
|
+
server.stop()
|
|
246
|
+
process.exit(0)
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
main()
|
package/src/cli/tjs.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* TJS CLI - Command line interface for Typed JavaScript
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* tjs check <file> - Parse and type check, report errors
|
|
7
|
+
* tjs run <file> - Transpile to JS and execute
|
|
8
|
+
* tjs types <file> - Output type metadata as JSON
|
|
9
|
+
* tjs emit <file> - Output transpiled JavaScript
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --help, -h - Show help
|
|
13
|
+
* --version, -v - Show version
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { check } from './commands/check'
|
|
17
|
+
import { run } from './commands/run'
|
|
18
|
+
import { types } from './commands/types'
|
|
19
|
+
import { emit } from './commands/emit'
|
|
20
|
+
import { convert } from './commands/convert'
|
|
21
|
+
import { test } from './commands/test'
|
|
22
|
+
|
|
23
|
+
const VERSION = '0.1.0'
|
|
24
|
+
|
|
25
|
+
const HELP = `
|
|
26
|
+
tjs - Typed JavaScript CLI
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
tjs <command> [options] <file>
|
|
30
|
+
|
|
31
|
+
Commands:
|
|
32
|
+
check <file> Parse and type check a TJS file
|
|
33
|
+
run <file> Transpile and execute a TJS file
|
|
34
|
+
types <file> Output type metadata as JSON
|
|
35
|
+
emit <file> Output transpiled JavaScript (+ docs)
|
|
36
|
+
test [file] Run .test.tjs test files
|
|
37
|
+
convert <src> Convert TypeScript files to TJS
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
-h, --help Show this help message
|
|
41
|
+
-v, --version Show version
|
|
42
|
+
--debug Include source locations in __tjs metadata (emit command)
|
|
43
|
+
--unsafe Strip __tjs metadata for production builds (emit command)
|
|
44
|
+
--no-docs Suppress documentation generation (emit command)
|
|
45
|
+
--docs-dir <d> Output docs to separate directory (emit command)
|
|
46
|
+
--jfdi Emit even if tests fail (just fucking do it)
|
|
47
|
+
-o <path> Output path (for emit, convert commands)
|
|
48
|
+
-t <pattern> Test name pattern (for test command)
|
|
49
|
+
--verbose, -V Verbose output
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
tjs check src/utils.tjs
|
|
53
|
+
tjs run examples/hello.tjs
|
|
54
|
+
tjs types lib/api.tjs > api-types.json
|
|
55
|
+
tjs emit src/utils.tjs > dist/utils.js
|
|
56
|
+
tjs emit --debug src/utils.tjs > dist/utils.debug.js
|
|
57
|
+
tjs emit src/ -o dist/ # Emit all .tjs files in directory
|
|
58
|
+
tjs emit --unsafe src/ -o dist/ # Emit without validation (production)
|
|
59
|
+
tjs test # Run all .test.tjs files
|
|
60
|
+
tjs test src/lib/ # Run tests in directory
|
|
61
|
+
tjs test -t "validation" # Run tests matching pattern
|
|
62
|
+
tjs convert src/ -o src-tjs/ # Convert TS files to TJS
|
|
63
|
+
`
|
|
64
|
+
|
|
65
|
+
async function main() {
|
|
66
|
+
const args = process.argv.slice(2)
|
|
67
|
+
|
|
68
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
69
|
+
console.log(HELP)
|
|
70
|
+
process.exit(0)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
74
|
+
console.log(`tjs v${VERSION}`)
|
|
75
|
+
process.exit(0)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Parse flags
|
|
79
|
+
const debug = args.includes('--debug')
|
|
80
|
+
const verbose = args.includes('--verbose') || args.includes('-V')
|
|
81
|
+
const unsafe = args.includes('--unsafe')
|
|
82
|
+
const noDocs = args.includes('--no-docs')
|
|
83
|
+
const jfdi = args.includes('--jfdi')
|
|
84
|
+
|
|
85
|
+
// Parse -o <output> option
|
|
86
|
+
const outputIdx = args.findIndex((a) => a === '-o' || a === '--output')
|
|
87
|
+
const output = outputIdx !== -1 ? args[outputIdx + 1] : undefined
|
|
88
|
+
|
|
89
|
+
// Parse --docs-dir <dir> option
|
|
90
|
+
const docsDirIdx = args.findIndex((a) => a === '--docs-dir')
|
|
91
|
+
const docsDir = docsDirIdx !== -1 ? args[docsDirIdx + 1] : undefined
|
|
92
|
+
|
|
93
|
+
// Parse -t <pattern> option for test command
|
|
94
|
+
const patternIdx = args.findIndex(
|
|
95
|
+
(a) => a === '-t' || a === '--test-name-pattern'
|
|
96
|
+
)
|
|
97
|
+
const testPattern = patternIdx !== -1 ? args[patternIdx + 1] : undefined
|
|
98
|
+
|
|
99
|
+
// Filter out flag arguments
|
|
100
|
+
const filteredArgs = args.filter((a, i) => {
|
|
101
|
+
if (a.startsWith('--') || a.startsWith('-')) return false
|
|
102
|
+
// Skip values that follow -o, -t, or --docs-dir
|
|
103
|
+
if (
|
|
104
|
+
i > 0 &&
|
|
105
|
+
(args[i - 1] === '-o' ||
|
|
106
|
+
args[i - 1] === '--output' ||
|
|
107
|
+
args[i - 1] === '-t' ||
|
|
108
|
+
args[i - 1] === '--test-name-pattern' ||
|
|
109
|
+
args[i - 1] === '--docs-dir')
|
|
110
|
+
)
|
|
111
|
+
return false
|
|
112
|
+
return true
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const command = filteredArgs[0]
|
|
116
|
+
const file = filteredArgs[1]
|
|
117
|
+
|
|
118
|
+
// Some commands don't require a file argument
|
|
119
|
+
const noFileRequired = ['test']
|
|
120
|
+
|
|
121
|
+
if (!file && !noFileRequired.includes(command)) {
|
|
122
|
+
console.error(`Error: No file specified for command '${command}'`)
|
|
123
|
+
console.error(`Usage: tjs ${command} <file>`)
|
|
124
|
+
process.exit(1)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
switch (command) {
|
|
129
|
+
case 'check':
|
|
130
|
+
await check(file)
|
|
131
|
+
break
|
|
132
|
+
case 'run':
|
|
133
|
+
await run(file)
|
|
134
|
+
break
|
|
135
|
+
case 'types':
|
|
136
|
+
await types(file)
|
|
137
|
+
break
|
|
138
|
+
case 'emit':
|
|
139
|
+
await emit(file, {
|
|
140
|
+
debug,
|
|
141
|
+
unsafe,
|
|
142
|
+
output,
|
|
143
|
+
verbose,
|
|
144
|
+
noDocs,
|
|
145
|
+
docsDir,
|
|
146
|
+
jfdi,
|
|
147
|
+
})
|
|
148
|
+
break
|
|
149
|
+
case 'test':
|
|
150
|
+
await test(file, { pattern: testPattern })
|
|
151
|
+
break
|
|
152
|
+
case 'convert':
|
|
153
|
+
await convert(file, { output, verbose })
|
|
154
|
+
break
|
|
155
|
+
default:
|
|
156
|
+
console.error(`Error: Unknown command '${command}'`)
|
|
157
|
+
console.error(`Run 'tjs --help' for usage`)
|
|
158
|
+
process.exit(1)
|
|
159
|
+
}
|
|
160
|
+
} catch (error: any) {
|
|
161
|
+
console.error(`Error: ${error.message}`)
|
|
162
|
+
process.exit(1)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main()
|