• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DanielXMoore / Civet / 21772072879

07 Feb 2026 01:50AM UTC coverage: 91.42% (-0.2%) from 91.596%
21772072879

Pull #1779

github

web-flow
Merge 817af4c6a into 3f8c07ee4
Pull Request #1779: LSP: fix(diagnostics), ensure immediate updates for opened dependent files on change

3777 of 4118 branches covered (91.72%)

Branch coverage included in aggregate %.

19185 of 20999 relevant lines covered (91.36%)

17409.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

33.15
/source/cli.civet
1
{ compile, generate, parse, lib, isCompileError, SourceMap } from ./main.civet
1✔
2
type { ASTError, BlockStatement, ParseError, ParseErrors } from ./main.civet
1✔
3
{ findConfig, loadConfig } from ./config.civet
1✔
4

1✔
5
// unplugin ends up getting installed in the same dist directory
1✔
6
{rawPlugin} from ./unplugin/unplugin.civet
1✔
7
let unplugin:
1✔
8
  buildStart: () => Promise<void>
1✔
9
  buildEnd: (this: {emitFile: (data: {source: string, fileName: string, type: string}) => void}, useConfigFileNames?: boolean) => Promise<void>
1✔
10
  load: (this: {addWatchFile: (filename: string) => void}, filename: string) => Promise<{code: string, map: unknown}>
1✔
11

1✔
12
export function version: Promise<string>
×
13
  // Once import assertions (with form) are universal, we can switch to this:
×
14
  //import package from "../package.json" with type: "json"
×
15
  //package.version
×
16
  if import.meta.url  // ESM mode (e.g. testing)
×
17
    { createRequire } from node:module
×
18
    createRequire(import.meta.url)('../package.json').version
×
19
  else
×
20
    require('../package.json').version
×
21

1✔
22
encoding .= "utf8" as BufferEncoding
1✔
23

1✔
24
fs from node:fs/promises
1✔
25
type { Stats } from node:fs
1✔
26
path from node:path
1✔
27

1✔
28
// TODO: Once the types are exported within the Civet source code,
1✔
29
// we should import them directly here, instead of looking at an old release.
1✔
30
type { CompileOptions } from @danielx/civet
1✔
31

1✔
32
export interface Options extends CompileOptions
1✔
33
  version?: boolean
1✔
34
  help?: boolean
1✔
35
  run?: boolean
1✔
36
  compile?: boolean
1✔
37
  output?: string
1✔
38
  outputDir?: string
1✔
39
  outputExt?: string
1✔
40
  outputPath?: path.ParsedPath
1✔
41
  eval?: string
1✔
42
  config?: string | false | undefined
1✔
43
  ast?: boolean | "raw"
1✔
44
  repl?: boolean
1✔
45
  typecheck?: boolean
1✔
46
  emitDeclaration?: boolean
1✔
47
  typescript?: boolean
1✔
48

1✔
49
export interface ParsedArgs
1✔
50
  filenames: string[]
1✔
51
  scriptArgs: string[]
1✔
52
  options: Options
1✔
53

1✔
54
export function parseArgs(args: string[], isTTY = process.stdin.isTTY): Promise<ParsedArgs>
54✔
55
  options: Options := {}
54✔
56
  isRun := => not (or)
54✔
57
    options.ast
60✔
58
    options.compile
59✔
59
    options.typecheck
42✔
60
    options.emitDeclaration
39✔
61
  filenames: string[] .= []
54✔
62
  scriptArgs: string[] .= []
54✔
63

54✔
64
  options.version = true if args.includes '-version'
54✔
65
  options.help = true if args.includes '-help'
54✔
66
  return {filenames, scriptArgs, options} if options.version or options.help
54✔
67

52✔
68
  i .= 0
52✔
69
  errors .= 0
52✔
70
  function endOfArgs(j: number): void
52✔
71
    i = args.length  // trigger end of loop
×
72
    return if j >= args.length  // no more args
×
73
    if options.run
×
74
      filenames.push args[j]
×
75
      scriptArgs = args[j+1..]
×
76
    else
×
77
      filenames.push ...args[j..]
×
78
  while i < args.length
52✔
79
    arg := args[i]
66✔
80
    // Split -ab into -a -b
66✔
81
    if /^-\w{2,}$/.test arg
66✔
82
      args[i..i] =
1✔
83
        for each char of arg[1..]
1✔
84
          `-${char}`
2✔
85
      continue
1✔
86
    // Main argument handling
65✔
87
    switch arg
65✔
88
      when '-v', '--version'
2✔
89
        options.version = true
2✔
90
      when '-h', '--help'
3✔
91
        options.help = true
3✔
92
      when '-c', '--compile'
8✔
93
        options.compile = true
8✔
94
      when '-o', '--output'
16✔
95
        options.output = args[++i]
16✔
96
      when '-e', '--eval'
66✔
97
        options.eval = args[++i]
2✔
98
      when '--config'
66✔
99
        options.config = args[++i]
1✔
100
      when '--no-config'
66✔
101
        options.config = false
1✔
102
      when '--civet'
66✔
103
        Object.assign options.parseOptions ??= {},
5✔
104
          parse `civet ${args[++i]}`,
5✔
105
            startRule: 'CivetPrologueContent'
5✔
106
            filename: '--civet argument'
5✔
107
          |> .config
5✔
108
      when '--comptime'
66✔
109
        (options.parseOptions ??= {}).comptime = true
3✔
110
      when '--no-comptime'
66✔
111
        (options.parseOptions ??= {}).comptime = false
3✔
112
      when '--ast'
66✔
113
        options.ast = true
1✔
114
      when '--no-cache'
66✔
115
        options.noCache = true
1✔
116
      when '--inline-map'
66✔
117
        options.inlineMap = true
1✔
118
      when '--js'
66✔
119
        options.js = true
1✔
120
      when '--hits'
66✔
121
        options.hits = args[++i]
1✔
122
      when '--trace'
66✔
123
        options.trace = args[++i]
1✔
124
      when '--typecheck'
66✔
125
        options.typecheck = true
3✔
126
      when '--emit-declaration', '--emitDeclaration'
4✔
127
        options.emitDeclaration = true
4✔
128
      when '--'
66!
129
        endOfArgs ++i  // remaining arguments are filename and/or arguments
66✔
130
      else
66✔
131
        if arg.startsWith('-') and arg is not '-'
8!
132
          console.error `Invalid command-line argument: ${arg}`
×
133
          errors++
×
134
        else if (options.run = isRun())
8✔
135
          endOfArgs i  // remaining arguments are arguments to the script
8!
136
        else
8✔
137
          filenames.push arg
8✔
138
    i++
65✔
139

52✔
140
  options.typescript = true if options.typecheck or options.emitDeclaration
54✔
141

52✔
142
  unless filenames.length or options.typescript or options.eval
54✔
143
    if isTTY
41✔
144
      options.repl = true
40✔
145
    else
1✔
146
      // When piped, default to old behavior of transpiling stdin to stdout
1✔
147
      options.compile = true
1✔
148
      options.run = false
1✔
149
      filenames = ['-']
1✔
150

52✔
151
  // Parse `output` option into forced directory, extension, and/or full path
52✔
152
  if options.output and options.output is not '-'
54✔
153
    optionsPath := path.parse options.output
14✔
154
    let stat: Stats | null
14✔
155
    try
14✔
156
      stat = await fs.stat options.output
2✔
157
    catch
14✔
158
      stat = null
12✔
159
    if stat?.isDirectory() or options.output.endsWith(path.sep) or
14✔
160
                              options.output.endsWith('/')
10✔
161
      // -o dir writes outputs into that directory with default name
4✔
162
      options.outputDir = options.output
4✔
163
    else if /^(\.[^]+)+$/.test optionsPath.base
10✔
164
      // -o .js or .ts or .civet.js or .civet.ts etc. to control extension
6✔
165
      options.outputExt = optionsPath.base
6✔
166
      // Can also be prefixed by a directory name
6✔
167
      options.outputDir = optionsPath.dir if optionsPath.dir
6✔
168
    else
4✔
169
      // -o filename fully specifies the output filename
4✔
170
      // (don't use this with multiple input files)
4✔
171
      options.outputPath = optionsPath
4✔
172
      options.outputExt = optionsPath.ext
4✔
173
  // For CLI compilations, default to rewriting imports to output extension,
54✔
174
  // with .jsx instead of .tsx to satisfy TypeScript.
54✔
175
  (options.parseOptions ??= {}).rewriteCivetImports ??=
54✔
176
    options.outputExt ?? '.civet.jsx'
54✔
177

54✔
178
  process.exit Math.min 255, errors if errors
54!
179
  options.run = isRun()
52✔
180
  {filenames, scriptArgs, options}
52✔
181

1✔
182
type ReadFile = (
1✔
183
  filename: string
1✔
184
  stdin: boolean
1✔
185
  content?: string
1✔
186
  error?: unknown
1✔
187
) & ((content: string) | (error: unknown))
1✔
188

1✔
189
function readFiles(filenames: string[], evalString?: string): AsyncGenerator<ReadFile>
×
190
  if evalString?
×
191
    yield
×
192
      filename: '<eval>'
×
193
      content: evalString + '\n'
×
194
      stdin: true
×
195

×
196
  for each let filename of filenames
×
197
    stdin := filename is '-'
×
198
    try
×
199
      let content: string
×
200
      if stdin
×
201
        process.stdin.setEncoding encoding
×
202

×
203
        // Try to guess filename for stdin, such as /dev/fd/filename
×
204
        filename = "<stdin>"
×
205
        try
×
206
          filename = await fs.realpath '/dev/stdin'
×
207

×
208
        if process.stdin.isTTY
×
209
          // In interactive stdin, `readline` lets user end the file via ^D.
×
210
          lines: string[] := []
×
211
          rl := import('node:readline') |> await |> .createInterface process.stdin, process.stdout
×
212
          rl.on 'line', (buffer: string) => lines.push buffer + '\n'
×
213
          content = await new Promise (resolve, reject) =>
×
214
            rl.on 'SIGINT', =>
×
215
              reject '^C'
×
216
            rl.on 'close', =>
×
217
              resolve lines.join ''
×
218
        else
×
219
          // For piped stdin, read stdin directly to avoid potential echo.
×
220
          content = (chunk for await chunk of process.stdin).join ''
×
221
      else
×
222
        content = await fs.readFile filename
×
223
      yield {filename, content, stdin}
×
224
    catch error
×
225
      yield {filename, error, stdin}
×
226

1✔
227
declare global
1✔
228
  var quit: () => void, exit: () => void
1✔
229
  var v8debug: unknown
1✔
230

1✔
231
export function repl(args: string[], options: Options)
×
232
  * as vm from 'node:vm'
×
233
  // Node 21.7.0+ supports dynamic import() in vm calls via this constant:
×
234
  importModuleDynamically .= vm.constants?.USE_MAIN_CONTEXT_DEFAULT_LOADER
×
235
  unless importModuleDynamically
×
236
    // For older Node, we need to provide our own dynamic import function,
×
237
    // which requires `--experimental-vm-modules`.  Check if we did it already:
×
238
    if vm.SourceTextModule?
×
239
      { pathToFileURL } from node:url
×
240
      importModuleDynamically = (specifier: string) =>
×
241
        if /^\.\.?[/\\]/.test specifier
×
242
          import pathToFileURL(path.join process.cwd(), specifier).href
×
243
        else
×
244
          import specifier
×
245
    else
×
246
      // If not, run this script with `--experimental-vm-modules`.
×
247
      execArgv := [ '--experimental-vm-modules' ]
×
248
      /* This doesn't work; --loader seems to force ESM mode which breaks CLI.
×
249
      { register } from node:module
×
250
      unless register
×
251
        // On Node <20.6.0, we need to use `--loader` for the ESM loader.
×
252
        execArgv.push '--loader', '@danielx/civet/esm'
×
253
      */
×
254
      { fork } from node:child_process
×
255
      fork __filename, args, {
×
256
        execArgv
×
257
        stdio: 'inherit'
×
258
      }
×
259
      return
×
260

×
261
  await import '../register.js'
×
262
  console.log `Civet ${await version()} REPL.  Enter a blank line to ${
×
263
    switch
×
264
      when options.ast then 'parse'
×
265
      when options.compile then 'transpile'
×
266
      else 'execute'
×
267
  } code.`
×
268
  global.quit = global.exit = => process.exit 0
×
269
  import * as nodeRepl from node:repl
×
270
  prompt :=
×
271
    switch
×
272
      when options.ast then '🌲> '
×
273
      when options.compile then '🐈> '
×
274
      else '🐱> '
×
275
  r := nodeRepl.start {}
×
276
    prompt
×
277
    writer:
×
278
      if options.ast
×
279
        (obj: unknown) =>
×
280
          try
×
281
            JSON.stringify obj, null, 2
×
282
          catch e
×
283
            console.log `Failed to stringify: ${e}`
×
284
            ''
×
285
      else if options.compile
×
286
        (obj: unknown) =>
×
287
          if obj <? 'string'
×
288
            obj?.replace /\n*$/, ''
×
289
          else
×
290
            ''
×
291
    eval: (input: string, context, filename: string, callback) ->
×
292
      // Newer Node versions use \r for intermediate line endings
×
293
      input = input.replace /\r/g, '\n'
×
294
      if input is '\n'  // blank input
×
295
        callback null, undefined
×
296
      else if input in ['quit\n', 'exit\n', 'quit()\n', 'exit()\n']
×
297
        process.exit 0
×
298
      else if input.endsWith '\n\n'  // finished input with blank line
×
299
        function showError(error: ???)
×
300
          console.error "Error while parsing Civet code:"
×
301
          if isCompileError error
×
302
            // Unwrap ParseErrors to first error
×
303
            if (error as ParseErrors).errors?
×
304
              error = (error as ParseErrors).errors[0]
×
305
            console.log ```
×
306
              ${input.split('\n')[0...(error as ParseError).line].join '\n'}
×
307
              ${' '.repeat (error as ParseError).column - 1}^ ${(error as ParseError).header}
×
308
            ```
×
309
          else
×
310
            console.error error
×
311

×
312
        let output: string
×
313
        if options.compile or options.ast
×
314
          try
×
315
            output = await compile input, {...options, filename}
×
316
          catch error
×
317
            showError error
×
318
            return callback null, undefined
×
319
          return callback null, output
×
320

×
321
        parseOptions := {...options.parseOptions,
×
322
          repl: true
×
323
        }
×
324

×
325
        let ast: BlockStatement
×
326
        try
×
327
          ast = await compile input, {...options, parseOptions, filename, ast: true}
×
328
        catch error
×
329
          showError error
×
330
          return callback null, undefined
×
331

×
332
        errors: ASTError[] .= []
×
333
        try
×
334
          output = generate ast, { ...options, errors, sourceMap: undefined }
×
335
        catch error
×
336
          //console.error "Failed to transpile Civet:"
×
337
          console.error error
×
338
          return callback null, undefined
×
339
        if errors#
×
340
          // Rerun with sourceMap
×
341
          errors = []
×
342
          generate ast, { ...options, errors, sourceMap: new SourceMap input }
×
343
          showError errors[0]
×
344
          return callback null, undefined
×
345

×
346
        let result: string
×
347
        try
×
348
          result = vm.runInContext output, context, {
×
349
            filename
×
350
            importModuleDynamically
×
351
          }
×
352
        catch error
×
353
          return callback error as Error, undefined
×
354

×
355
        if ast.topLevelAwait
×
356
          // If there was a top-level await, the result is a promise
×
357
          // that we need to await before returning it.
×
358
          try
×
359
            result = await result
×
360
          catch error
×
361
            callback error as Error, undefined
×
362
          else
×
363
            callback null, result
×
364
        else
×
365
          callback null, result
×
366
      else  // still reading
×
367
        callback (new nodeRepl.Recoverable new Error "Enter a blank line to execute code."), null
×
368

×
369
  // Hack to force '...' prompt for continuation input lines
×
370
  bufferedCommandSymbol :=
×
371
    Object.getOwnPropertySymbols(r).find (symbol) =>
×
372
      symbol.description is 'bufferedCommand'
×
373
  interfaceSetPrompt :=
×
374
    Object.getPrototypeOf(Object.getPrototypeOf(r))?.setPrompt
×
375
  writeToOutputSymbol :=
×
376
    Object.getOwnPropertySymbols(Object.getPrototypeOf(Object.getPrototypeOf(r))).find (symbol) =>
×
377
      symbol.description is '_writeToOutput'
×
378
  if bufferedCommandSymbol and interfaceSetPrompt and writeToOutputSymbol
×
379
    // Fix display of continuation prompt from `| ` to `... `
×
380
    originalWriteToOutput := r[writeToOutputSymbol]
×
381
    Object.defineProperty r, writeToOutputSymbol,
×
382
      value: (text: string) =>
×
383
        originalWriteToOutput.call r, text.replace /(^|\n)\| /g, '$1... '
×
384
      configurable: true
×
385
      writable: true
×
386
    // Fix internal prompt string from `| ` to `... `
×
387
    r.displayPrompt = (preserveCursor?: boolean) =>
×
388
      interfaceSetPrompt.call r,
×
389
        if r[bufferedCommandSymbol]?#
×
390
          '... '
×
391
        else
×
392
          prompt
×
393
      r.prompt preserveCursor
×
394

1✔
395
export function cli(args = process.argv[2..])
×
396
  // process.argv gets overridden when running scripts, but gets saved here
×
397

×
398
  {filenames, scriptArgs, options} .= await parseArgs args
×
399

×
400
  if options.version
×
401
    console.log await version()
×
402
    process.exit(0)
×
403

×
404
  if options.help
×
405
    process.stderr.write """
×
406
       ▄▄· ▪   ▌ ▐·▄▄▄ .▄▄▄▄▄
×
407
      ▐█ ▌▪██ ▪█·█▌▀▄.▀·•██       _._     _,-'""`-._
×
408
      ██ ▄▄▐█·▐█▐█•▐▀▀▪▄ ▐█.▪    (,-.`._,'(       |\\`-/|
×
409
      ▐███▌▐█▌ ███ ▐█▄▄▌ ▐█▌·        `-.-' \\ )-`( , o o)
×
410
      ·▀▀▀ ▀▀▀. ▀   ▀▀▀  ▀▀▀               `-    \\`_`"'-
×
411

×
412

×
413
Usage:
×
414

×
415
    civet                                        # REPL for executing code
×
416
    civet -c                                     # REPL for transpiling code
×
417
    civet --ast                                  # REPL for parsing code
×
418
    civet [options] input.civet                  # run input.civet
×
419
    civet [options] -c input.civet               # -> input.civet.tsx
×
420
    civet [options] -c input.civet -o .ts        # -> input.ts
×
421
    civet [options] -c input.civet -o dir        # -> dir/input.civet.tsx
×
422
    civet [options] -c input.civet -o dir/.ts    # -> dir/input.ts
×
423
    civet [options] -c input.civet -o output.ts  # -> output.ts
×
424
    civet [options] < input.civet > output.ts    # pipe form
×
425

×
426
Options:
×
427
  --help           Show this help message
×
428
  --version        Show the version number
×
429
  -o / --output XX Specify output directory and/or extension, or filename
×
430
  -c / --compile   Compile input files to TypeScript (or JavaScript)
×
431
  -e / --eval XX   Evaluate specified code (or compile it with -c)
×
432
  --config XX      Specify a config file (default scans for a config.civet, civet.json, civetconfig.civet or civetconfig.json file, optionally in a .config directory, or starting with a .)
×
433
  --civet XX       Specify civet compiler flag, as in "civet XX" prologue
×
434
  --comptime       Enable execution of code during compilation via comptime
×
435
  --no-config      Don't scan for a config file
×
436
  --js             Strip out all type annotations; default to .jsx extension
×
437
  --ast            Print the AST instead of the compiled code
×
438
  --inline-map     Generate a sourcemap
×
439
  --no-cache       Disable compiler caching (slow, for debugging)
×
440
  --typecheck      Run TypeScript and output diagnostics
×
441
  --emit-declaration  Run TypeScript and emit .d.ts files (if no errors)
×
442
  --trace XX       Log detailed parsing notes to a file, for parser debugging
×
443

×
444
You can use - to read from stdin or (prefixed by -o) write to stdout.
×
445

×
446
By default, .civet imports get rewritten to use the output extension.
×
447
You can override this behavior via: --civet rewriteCivetImports=.ext
×
448

×
449

×
450
    """
×
451
    process.exit(0)
×
452

×
453
  if options.config is undefined
×
454
    options.config = await findConfig process.cwd()
×
455
  if options.config
×
456
    parsed := await loadConfig options.config as string
×
457
    options = {
×
458
      ...parsed
×
459
      ...options
×
460
    }
×
461
    // Deep merge
×
462
    if parsed.parseOptions and options.parseOptions
×
463
      options.parseOptions = {
×
464
        ...parsed.parseOptions
×
465
        ...options.parseOptions
×
466
      }
×
467

×
468
  if options.typescript
×
469
    // In case we're installed globally, allow for co-installed typescript
×
470
    unless import.meta.url  // CJS mode only
×
471
      try
×
472
        require 'typescript'
×
473
      catch
×
474
        modulePkg := require('node:module') as! {globalPaths?: string[]}
×
475
        if match := __dirname.match /([/\\]@danielx)?[/\\][cC]ivet[/\\]dist[/\\]?$/
×
476
          if match[1]
×
477
            modulePkg.globalPaths?.push path.join __dirname, '..', '..', '..'
×
478
          else
×
479
            modulePkg.globalPaths?.push path.join __dirname, '..', '..'
×
480
        try
×
481
          require 'typescript'
×
482
        catch
×
483
          console.error "Civet could not find TypeScript, which is required for --typecheck or --emit-declaration. Please install typescript using the same package manager you used to install @danielx/civet. With NPM for example: `npm install typescript` if you are local to a project, or `npm install -g typescript` if you installed Civet globally."
×
484
          process.exit 1
×
485

×
486
    unpluginOptions := {
×
487
      ...options
×
488
      ts: if options.js then 'civet' else 'preserve'
×
489
      outputExtension: '.tsx'
×
490
      declarationExtension:
×
491
        options.outputExt?.replace /\.[jt]sx?$/i, '.d.ts'
×
492
    }
×
493
    // TypeScript wants .civet.tsx files imported as .civet.jsx
×
494
    (unpluginOptions.parseOptions ??= {}).rewriteCivetImports = '.civet.jsx'
×
495
    unplugin = rawPlugin unpluginOptions, framework: 'civet-cli'
×
496
    await unplugin.buildStart()
×
497

×
498
  // In run mode, compile to JS with source maps
×
499
  if options.run
×
500
    options.js = true
×
501
    options.inlineMap = true
×
502

×
503
  return repl args, options if options.repl
×
504

×
505
  // Ignore EPIPE errors, e.g. when piping to `head`
×
506
  process.stdout.on "error", (e) =>
×
507
    if e.code is in ['EPIPE', 'EOF']
×
508
      process.exit 0
×
509
    else
×
510
      console.error e
×
511
      process.exit 1
×
512

×
513
  errors .= 0
×
514
  for await let {filename, error, content, stdin} of readFiles filenames, options.eval
×
515
    if error
×
516
      console.error `${filename} failed to load:`
×
517
      console.error error
×
518
      errors++
×
519
      continue
×
520

×
521
    outputPath: path.FormatInputPathObject := options.outputPath ??
×
522
      path.parse filename
×
523
      ||> delete .base  // use name and ext
×
524
      ||> .ext +=  // default extension
×
525
        if options.js
×
526
          ".jsx"
×
527
        else
×
528
          ".tsx"
×
529
      ||> ($) =>  // `output` option overrides
×
530
        $.dir = options.outputDir if options.outputDir?
×
531
        $.ext = options.outputExt if options.outputExt?
×
532
    outputFilename := path.format outputPath
×
533

×
534
    // Transpile
×
535
    let output: string
×
536
    try
×
537
      if unplugin?
×
538
        output = unplugin.load.call {
×
539
          addWatchFile();
×
540
        }, `${filename}.tsx`
×
541
        |> await
×
542
        |> .code
×
543
      else
×
544
        output = await compile content!, {...options, filename, outputFilename}
×
545
    catch error
×
546
      //console.error `${filename} failed to transpile:`
×
547
      console.error error
×
548
      errors++
×
549
      continue
×
550

×
551
    if options.ast
×
552
      process.stdout.write JSON.stringify(output, null, 2)
×
553
    else if options.compile
×
554
      if (stdin and not options.output) or options.output is '-'
×
555
        process.stdout.write output
×
556
      else
×
557
        // Make output directory in case it doesn't already exist
×
558
        outputDir := path.dirname outputFilename
×
559
        await fs.mkdir outputDir, recursive: true unless outputDir is '.'
×
560
        try
×
561
          await fs.writeFile outputFilename, output
×
562
        catch error
×
563
          console.error `${outputFilename} failed to write:`
×
564
          console.error error
×
565
          errors++
×
566
    else if options.run
×
567
      esm := do
×
568
        if output is like /\b(await|import|export)\b/  // potentially ESM
×
569
          ast := await compile content!, {...options, ast: true, filename}
×
570
          (or)
×
571
            lib.hasAwait ast
×
572
            lib.hasImportDeclaration ast
×
573
            lib.hasExportDeclaration ast
×
574
      if esm
×
575
        // Run ESM code via `node --import @danielx/civet/register` subprocess
×
576
        if stdin
×
577
          // If code was read on stdin via command-line argument "-", try to
×
578
          // save it in a temporary file in same directory so paths are correct.
×
579
          filename = `.stdin-${process.pid}.civet`
×
580
          try
×
581
            await fs.writeFile filename, content!, {encoding}
×
582
          catch e
×
583
            console.error `Could not write ${filename} for Civet ESM mode:`
×
584
            console.error e
×
585
            process.exit 1
×
586
        { fork } from node:child_process
×
587

×
588
        { register } from node:module
×
589
        let execArgv
×
590
        if register
×
591
          // On Node 20.6.0+, module.register does the work for us;
×
592
          // we just need to `--import` ESM/CJS registration.
×
593
          { join } from path
×
594
          { pathToFileURL } from node:url
×
595
          execArgv = [
×
596
            '--import'
×
597
            pathToFileURL(join __dirname, '../register.js').href
×
598
          ]
×
599
        else
×
600
          // On Node <20.6.0, we need to use `--loader` for the ESM loader.
×
601
          execArgv = [
×
602
            '--loader', '@danielx/civet/esm' // ESM
×
603
            '--require', '@danielx/civet/register' // CJS
×
604
          ]
×
605

×
606
        debugRe := /--debug|--inspect/
×
607
        isDebug := v8debug <? "object" or debugRe.test(process.execArgv.join(' ')) or debugRe.test(process.env.NODE_OPTIONS ?? '')
×
608
        if isDebug
×
609
          execArgv.push "--inspect=" + (process.debugPort + 1)
×
610
        child := fork filename, [
×
611
          ...scriptArgs
×
612
        ], {
×
613
          execArgv
×
614
          stdio: 'inherit'
×
615
        }
×
616
        child.on 'exit', (code) =>
×
617
          if stdin
×
618
            // Delete temporary file
×
619
            await fs.unlink filename
×
620

×
621
          process.exit code ?? 1
×
622
        // Instead of default behavior of exiting, pass on signals to
×
623
        // child process and let it decide whether to exit
×
624
        for signal of ['SIGINT', 'SIGTERM', 'SIGHUP']
×
625
          process.on signal, => child.kill signal
×
626

×
627
      else
×
628
        await import '../register.js'
×
629
        try
×
630
          module.filename = await fs.realpath filename
×
631
        catch
×
632
          module.filename = filename
×
633
        process.argv = ["civet", module.filename, ...scriptArgs]
×
634
        module.paths =
×
635
          import 'node:module' |> await |> ._nodeModulePaths path.dirname module.filename
×
636
        try
×
637
          module._compile output, module.filename
×
638
        catch error
×
639
          console.error `${filename} crashed while running in CJS mode:`
×
640
          console.error error
×
641
          process.exit 1
×
642

×
643
  process.exitCode = Math.min 255, errors
×
644
  if unplugin?
×
645
    try
×
646
      await unplugin.buildEnd.call {
×
647
        emitFile({source, fileName})
×
648
          fs.writeFile fileName, source
×
649
      }, not filenames.length
×
650
    catch error
×
651
      if match := (error as Error).message.match /Aborting build because of (\d+) TypeScript diagnostic/
×
652
        process.exitCode = Math.min 255, errors + +match[1]
×
653
      else
×
654
        process.exitCode = 1
×
655
        throw error
×
656

1✔
657
// build/build.sh adds a call to cli() at the end here
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc