@panagos/jojoscript 0.7.1 → 0.9.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/README.md +809 -418
- package/bin/jojo.js +430 -170
- package/package.json +50 -59
- package/src/compiler.js +227 -186
- package/src/effect-parser.js +223 -0
- package/src/index.js +14 -0
- package/src/pipeline-hash.js +27 -0
- package/src/pipeline-ir.js +157 -0
- package/src/pipeline-parser.js +583 -418
- package/src/runtime/checkpoint-store.js +122 -0
- package/src/runtime/checkpoint.js +69 -0
- package/src/runtime/collections.js +467 -327
- package/src/runtime/effect-log.js +90 -0
- package/src/runtime/effects.js +263 -0
- package/src/runtime/errors.js +29 -0
- package/src/runtime/execution-context.js +86 -0
- package/src/runtime/index.js +70 -29
- package/src/runtime/inspect.js +62 -0
- package/src/runtime/metrics.js +41 -0
- package/src/runtime/retry.js +67 -0
- package/src/runtime/trace.js +66 -0
package/bin/jojo.js
CHANGED
|
@@ -1,170 +1,430 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from 'node:fs'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { spawn } from 'node:child_process'
|
|
7
|
-
|
|
8
|
-
import { compileFile, compileDirectory } from '../src/compiler.js'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
jojo --
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
jojo
|
|
28
|
-
jojo
|
|
29
|
-
jojo
|
|
30
|
-
jojo
|
|
31
|
-
jojo
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
if (!
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
6
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
7
|
+
|
|
8
|
+
import { compile, compileFile, compileDirectory } from '../src/compiler.js'
|
|
9
|
+
import { analyzePipelines, renderGraphText, renderGraphDot } from '../src/pipeline-ir.js'
|
|
10
|
+
import { FileCheckpointStore } from '../src/runtime/checkpoint-store.js'
|
|
11
|
+
|
|
12
|
+
const PACKAGE_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
13
|
+
const RUNTIME_IMPORT = pathToFileURL(path.join(PACKAGE_ROOT, 'src/runtime/index.js')).href
|
|
14
|
+
|
|
15
|
+
function packageVersion() {
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'))
|
|
17
|
+
return pkg.version
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function usage() {
|
|
21
|
+
console.log(`JojoScript CLI v${packageVersion()}
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
jojo build <file.jojo|directory> [--out <directory>] [--watch]
|
|
25
|
+
jojo run <file.jojo> [--out <directory>]
|
|
26
|
+
jojo graph <file.jojo> [--format text|dot]
|
|
27
|
+
jojo profile <file.jojo>
|
|
28
|
+
jojo resume <file.jojo> [--out <directory>]
|
|
29
|
+
jojo replay <file.jojo> --from <checkpoint> [--out <directory>]
|
|
30
|
+
jojo test <file.jojo>
|
|
31
|
+
jojo --version
|
|
32
|
+
jojo --help
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
jojo build src
|
|
36
|
+
jojo build src/main.jojo
|
|
37
|
+
jojo build src --out dist
|
|
38
|
+
jojo build src --watch
|
|
39
|
+
jojo run src/main.jojo
|
|
40
|
+
jojo graph orders.jojo
|
|
41
|
+
jojo graph orders.jojo --format dot
|
|
42
|
+
jojo profile orders.jojo
|
|
43
|
+
jojo resume orders.jojo
|
|
44
|
+
jojo replay orders.jojo --from enriched
|
|
45
|
+
jojo test orders.jojo
|
|
46
|
+
`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
* All CLI-facing failures (bad arguments, missing files, compiler syntax
|
|
51
|
+
* errors) are surfaced as a single friendly `jojo: <message>` line instead
|
|
52
|
+
* of a raw stack trace.
|
|
53
|
+
*/
|
|
54
|
+
function fail(message) {
|
|
55
|
+
throw new Error(message)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseFlags(args, startIndex) {
|
|
59
|
+
let out = null
|
|
60
|
+
let watch = false
|
|
61
|
+
|
|
62
|
+
for (let i = startIndex; i < args.length; i++) {
|
|
63
|
+
const arg = args[i]
|
|
64
|
+
|
|
65
|
+
if (arg === '--out') {
|
|
66
|
+
const value = args[++i]
|
|
67
|
+
if (!value) fail('--out requires a directory')
|
|
68
|
+
out = path.resolve(value)
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (arg === '--watch') {
|
|
73
|
+
watch = true
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fail(`Unknown option: ${arg}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { out, watch }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function build(input, out) {
|
|
84
|
+
const resolvedInput = path.resolve(input)
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(resolvedInput)) {
|
|
87
|
+
fail(`Not found: ${resolvedInput}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const isDirectory = fs.statSync(resolvedInput).isDirectory()
|
|
91
|
+
|
|
92
|
+
const result = isDirectory
|
|
93
|
+
? compileDirectory(resolvedInput, out, { runtimeImport: RUNTIME_IMPORT })
|
|
94
|
+
: [compileFile(resolvedInput, out, { runtimeImport: RUNTIME_IMPORT })]
|
|
95
|
+
|
|
96
|
+
for (const item of result) {
|
|
97
|
+
console.log(`Compiled ${item.input} -> ${item.output}`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function watch(input, out) {
|
|
104
|
+
const resolvedInput = path.resolve(input)
|
|
105
|
+
console.log(`Watching ${resolvedInput} for changes (Ctrl+C to stop)...`)
|
|
106
|
+
|
|
107
|
+
const rebuild = () => {
|
|
108
|
+
try {
|
|
109
|
+
build(input, out)
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(`jojo: ${error.message}`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
rebuild()
|
|
116
|
+
|
|
117
|
+
fs.watch(resolvedInput, { recursive: true }, (_, filename) => {
|
|
118
|
+
if (filename && !filename.endsWith('.jojo')) return
|
|
119
|
+
console.log(`\nChange detected${filename ? `: ${filename}` : ''}, rebuilding...`)
|
|
120
|
+
rebuild()
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function run(input, out) {
|
|
125
|
+
const resolvedInput = path.resolve(input)
|
|
126
|
+
|
|
127
|
+
if (!fs.existsSync(resolvedInput) || fs.statSync(resolvedInput).isDirectory()) {
|
|
128
|
+
fail('`jojo run` requires a single .jojo file, not a directory')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const [{ output }] = build(input, out)
|
|
132
|
+
|
|
133
|
+
const child = spawn(process.execPath, [output], { stdio: 'inherit' })
|
|
134
|
+
child.on('exit', code => process.exit(code ?? 0))
|
|
135
|
+
child.on('error', error => fail(error.message))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function requireSingleFile(command, input) {
|
|
139
|
+
const resolvedInput = path.resolve(input)
|
|
140
|
+
|
|
141
|
+
if (!fs.existsSync(resolvedInput)) fail(`Not found: ${resolvedInput}`)
|
|
142
|
+
if (fs.statSync(resolvedInput).isDirectory()) {
|
|
143
|
+
fail(`\`jojo ${command}\` requires a single .jojo file, not a directory`)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return resolvedInput
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function graph(input, format) {
|
|
150
|
+
const resolvedInput = requireSingleFile('graph', input)
|
|
151
|
+
const source = fs.readFileSync(resolvedInput, 'utf8')
|
|
152
|
+
const pipelines = analyzePipelines(source)
|
|
153
|
+
|
|
154
|
+
if (!pipelines.length) {
|
|
155
|
+
console.log('No pipelines found.')
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
pipelines.forEach((pipeline, index) => {
|
|
160
|
+
if (pipelines.length > 1) console.log(`\nPipeline #${index + 1} (id: ${pipeline.id})`)
|
|
161
|
+
console.log(format === 'dot' ? renderGraphDot(pipeline) : renderGraphText(pipeline))
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function profile(input) {
|
|
166
|
+
const resolvedInput = requireSingleFile('profile', input)
|
|
167
|
+
const source = fs.readFileSync(resolvedInput, 'utf8')
|
|
168
|
+
|
|
169
|
+
let compiled
|
|
170
|
+
try {
|
|
171
|
+
compiled = compile(source, { profile: true, runtimeImport: RUNTIME_IMPORT })
|
|
172
|
+
} catch (error) {
|
|
173
|
+
fail(error.message)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/*
|
|
177
|
+
* Compiled next to the input file (not under the OS temp directory):
|
|
178
|
+
* Node's ESM resolver walks *up* from the compiled file looking for the
|
|
179
|
+
* nearest `package.json` to resolve bare specifiers like the runtime
|
|
180
|
+
* import, and an unrelated/broken `package.json` anywhere above a temp
|
|
181
|
+
* directory would break that resolution.
|
|
182
|
+
*/
|
|
183
|
+
const tmpDir = fs.mkdtempSync(path.join(path.dirname(resolvedInput), '.jojo-profile-'))
|
|
184
|
+
const outFile = path.join(tmpDir, path.basename(resolvedInput, path.extname(resolvedInput)) + '.mjs')
|
|
185
|
+
const metricsFile = path.join(tmpDir, 'metrics.json')
|
|
186
|
+
|
|
187
|
+
fs.writeFileSync(outFile, compiled, 'utf8')
|
|
188
|
+
|
|
189
|
+
const result = spawnSync(process.execPath, [outFile], {
|
|
190
|
+
stdio: 'inherit',
|
|
191
|
+
env: { ...process.env, JOJO_PROFILE_OUT: metricsFile }
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
if (result.error) fail(result.error.message)
|
|
196
|
+
|
|
197
|
+
if (!fs.existsSync(metricsFile)) {
|
|
198
|
+
console.log('No pipeline metrics were recorded.')
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
printProfileTable(JSON.parse(fs.readFileSync(metricsFile, 'utf8')))
|
|
203
|
+
} finally {
|
|
204
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (result.status) process.exit(result.status)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function printProfileTable(metrics) {
|
|
211
|
+
const rows = metrics.nodes ?? []
|
|
212
|
+
|
|
213
|
+
if (!rows.length) {
|
|
214
|
+
console.log('No pipeline metrics were recorded.')
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const columns = [22, 13, 15]
|
|
219
|
+
console.log(
|
|
220
|
+
'Node'.padEnd(columns[0]) +
|
|
221
|
+
'Items'.padEnd(columns[1]) +
|
|
222
|
+
'Duration'.padEnd(columns[2]) +
|
|
223
|
+
'Throughput'
|
|
224
|
+
)
|
|
225
|
+
console.log('-'.repeat(columns.reduce((a, b) => a + b, 0) + 10))
|
|
226
|
+
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
const duration = `${(row.durationMs / 1000).toFixed(1)}s`
|
|
229
|
+
console.log(
|
|
230
|
+
String(row.type).padEnd(columns[0]) +
|
|
231
|
+
String(row.items).padEnd(columns[1]) +
|
|
232
|
+
duration.padEnd(columns[2]) +
|
|
233
|
+
formatThroughput(row.throughput)
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function formatThroughput(value) {
|
|
239
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M/s`
|
|
240
|
+
if (value >= 1e3) return `${(value / 1e3).toFixed(0)}K/s`
|
|
241
|
+
return `${Math.round(value)}/s`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function resume(input, out) {
|
|
245
|
+
const resolvedInput = requireSingleFile('resume', input)
|
|
246
|
+
const source = fs.readFileSync(resolvedInput, 'utf8')
|
|
247
|
+
const pipelines = analyzePipelines(source)
|
|
248
|
+
const checkpointDir = process.env.JOJO_CHECKPOINT_DIR || path.resolve('.jojo-checkpoints')
|
|
249
|
+
|
|
250
|
+
let anyFound = false
|
|
251
|
+
for (const pipeline of pipelines) {
|
|
252
|
+
const store = new FileCheckpointStore(checkpointDir)
|
|
253
|
+
const existing = store.list(pipeline.id)
|
|
254
|
+
if (existing.length) {
|
|
255
|
+
anyFound = true
|
|
256
|
+
console.log(`Resuming pipeline ${pipeline.id}: reusing checkpoint(s) ${existing.join(', ')}`)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!anyFound) {
|
|
261
|
+
console.log('No existing checkpoints found; running from the start.')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
run(input, out)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function replay(input, out, from) {
|
|
268
|
+
if (!from) fail('`jojo replay` requires --from <checkpoint>')
|
|
269
|
+
|
|
270
|
+
console.log(`Replaying from checkpoint: ${from}`)
|
|
271
|
+
process.env.JOJO_REPLAY_FROM = from
|
|
272
|
+
run(input, out)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function parseGraphFlags(args, startIndex) {
|
|
276
|
+
let format = 'text'
|
|
277
|
+
|
|
278
|
+
for (let i = startIndex; i < args.length; i++) {
|
|
279
|
+
const arg = args[i]
|
|
280
|
+
|
|
281
|
+
if (arg === '--format') {
|
|
282
|
+
const value = args[++i]
|
|
283
|
+
if (value !== 'text' && value !== 'dot') fail('--format must be "text" or "dot"')
|
|
284
|
+
format = value
|
|
285
|
+
continue
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
fail(`Unknown option: ${arg}`)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { format }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function parseReplayFlags(args, startIndex) {
|
|
295
|
+
let out = null
|
|
296
|
+
let from = null
|
|
297
|
+
|
|
298
|
+
for (let i = startIndex; i < args.length; i++) {
|
|
299
|
+
const arg = args[i]
|
|
300
|
+
|
|
301
|
+
if (arg === '--out') {
|
|
302
|
+
const value = args[++i]
|
|
303
|
+
if (!value) fail('--out requires a directory')
|
|
304
|
+
out = path.resolve(value)
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (arg === '--from') {
|
|
309
|
+
const value = args[++i]
|
|
310
|
+
if (!value) fail('--from requires a checkpoint name')
|
|
311
|
+
from = value
|
|
312
|
+
continue
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fail(`Unknown option: ${arg}`)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return { out, from }
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function testCmd(input) {
|
|
322
|
+
const resolvedInput = requireSingleFile('test', input)
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
compileFile(resolvedInput, null, { runtimeImport: RUNTIME_IMPORT })
|
|
326
|
+
} catch (error) {
|
|
327
|
+
fail(error.message)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const dir = path.dirname(resolvedInput)
|
|
331
|
+
const base = path.basename(resolvedInput, path.extname(resolvedInput))
|
|
332
|
+
const candidates = [
|
|
333
|
+
path.join(dir, `${base}.test.js`),
|
|
334
|
+
path.join(dir, 'test', `${base}.test.js`)
|
|
335
|
+
].filter(candidate => fs.existsSync(candidate))
|
|
336
|
+
|
|
337
|
+
if (!candidates.length) {
|
|
338
|
+
console.log(`${resolvedInput} compiled successfully. No test file found (looked for ${base}.test.js).`)
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const result = spawnSync(process.execPath, ['--test', ...candidates], {
|
|
343
|
+
encoding: 'utf8',
|
|
344
|
+
env: Object.fromEntries(
|
|
345
|
+
Object.entries(process.env).filter(([name]) => name !== 'NODE_TEST_CONTEXT')
|
|
346
|
+
)
|
|
347
|
+
})
|
|
348
|
+
if (result.stdout) process.stdout.write(result.stdout)
|
|
349
|
+
if (result.stderr) process.stdout.write(result.stderr)
|
|
350
|
+
process.exit(result.status ?? 0)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const COMMANDS = new Set(['build', 'run', 'graph', 'profile', 'resume', 'replay', 'test'])
|
|
354
|
+
|
|
355
|
+
function main() {
|
|
356
|
+
const args = process.argv.slice(2)
|
|
357
|
+
|
|
358
|
+
if (!args.length || args[0] === '--help' || args[0] === '-h') {
|
|
359
|
+
usage()
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (args[0] === '--version' || args[0] === '-v') {
|
|
364
|
+
console.log(packageVersion())
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const command = args[0]
|
|
369
|
+
|
|
370
|
+
if (!COMMANDS.has(command)) {
|
|
371
|
+
usage()
|
|
372
|
+
fail(`Unknown command: ${command}`)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!args[1] || args[1].startsWith('-')) {
|
|
376
|
+
fail(`\`jojo ${command}\` requires a file or directory argument`)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const input = args[1]
|
|
380
|
+
|
|
381
|
+
if (command === 'graph') {
|
|
382
|
+
const { format } = parseGraphFlags(args, 2)
|
|
383
|
+
graph(input, format)
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (command === 'profile') {
|
|
388
|
+
profile(input)
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (command === 'test') {
|
|
393
|
+
testCmd(input)
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (command === 'replay') {
|
|
398
|
+
const { out, from } = parseReplayFlags(args, 2)
|
|
399
|
+
replay(input, out, from)
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const { out, watch: shouldWatch } = parseFlags(args, 2)
|
|
404
|
+
|
|
405
|
+
if (command === 'resume') {
|
|
406
|
+
if (shouldWatch) fail('--watch is not supported with `jojo resume`')
|
|
407
|
+
resume(input, out)
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (command === 'run') {
|
|
412
|
+
if (shouldWatch) fail('--watch is not supported with `jojo run`, use `jojo build --watch` instead')
|
|
413
|
+
run(input, out)
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (shouldWatch) {
|
|
418
|
+
watch(input, out)
|
|
419
|
+
return
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
build(input, out)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
main()
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.error(`jojo: ${error.message}`)
|
|
429
|
+
process.exit(1)
|
|
430
|
+
}
|