flightdeck 0.2.3 → 0.2.5
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/dist/flightdeck.$configPath.schema.json +3 -0
- package/dist/flightdeck.bin.js +6 -6071
- package/dist/flightdeck.main.schema.json +3 -0
- package/dist/flightdeck_log.json +68 -0
- package/dist/klaxon.bin.js +3 -88
- package/dist/lib.d.ts +891 -2
- package/dist/lib.js +6 -6030
- package/gen/.gitkeep +0 -0
- package/gen/flightdeck_log.json +168 -0
- package/gen/lnav-format-schema.gen.ts +1558 -0
- package/package.json +15 -11
- package/src/flightdeck.bin.ts +20 -2
- package/src/flightdeck.lib.ts +233 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flightdeck",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jeremy Banka",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"type": "module",
|
|
13
13
|
"files": [
|
|
14
14
|
"dist",
|
|
15
|
+
"gen",
|
|
15
16
|
"src"
|
|
16
17
|
],
|
|
17
18
|
"main": "dist/lib.js",
|
|
@@ -22,26 +23,29 @@
|
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
25
|
"@t3-oss/env-core": "0.11.1",
|
|
25
|
-
"cron": "3.
|
|
26
|
-
"zod": "3.
|
|
27
|
-
"atom.io": "0.30.
|
|
26
|
+
"cron": "3.3.1",
|
|
27
|
+
"zod": "3.24.1",
|
|
28
|
+
"atom.io": "0.30.6",
|
|
28
29
|
"comline": "0.1.6"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
|
-
"@
|
|
32
|
+
"@biomejs/js-api": "0.7.1",
|
|
33
|
+
"@biomejs/wasm-nodejs": "1.9.4",
|
|
34
|
+
"@types/node": "22.10.2",
|
|
32
35
|
"@types/tmp": "0.2.6",
|
|
33
|
-
"bun-types": "1.1.
|
|
34
|
-
"concurrently": "9.1.
|
|
36
|
+
"bun-types": "1.1.42",
|
|
37
|
+
"concurrently": "9.1.1",
|
|
38
|
+
"json-schema-to-zod": "2.6.0",
|
|
35
39
|
"rimraf": "6.0.1",
|
|
36
40
|
"tmp": "0.2.3",
|
|
37
41
|
"tsup": "8.3.5",
|
|
38
|
-
"vitest": "
|
|
42
|
+
"vitest": "3.0.0-beta.3",
|
|
43
|
+
"varmint": "0.3.4"
|
|
39
44
|
},
|
|
40
45
|
"scripts": {
|
|
46
|
+
"gen": "bun ./__scripts__/gen.bun.ts",
|
|
41
47
|
"build": "rimraf dist && concurrently \"bun:build:*\" && concurrently \"bun:schema:*\"",
|
|
42
|
-
"build:
|
|
43
|
-
"build:bin:klaxon": "bun build --outdir dist --target node --external flightdeck --external atom.io --external comline --external zod -- src/klaxon.bin.ts",
|
|
44
|
-
"build:lib": "bun build --outdir dist --target node --external flightdeck --external atom.io --external comline --external zod -- src/lib.ts ",
|
|
48
|
+
"build:js": "bun ./__scripts__/build.bun.ts",
|
|
45
49
|
"build:dts": "tsup",
|
|
46
50
|
"schema:flightdeck": "bun ./src/flightdeck.bin.ts --outdir=dist -- schema",
|
|
47
51
|
"lint:biome": "biome check -- .",
|
package/src/flightdeck.bin.ts
CHANGED
|
@@ -3,11 +3,21 @@
|
|
|
3
3
|
import * as path from "node:path"
|
|
4
4
|
|
|
5
5
|
import type { OptionsGroup } from "comline"
|
|
6
|
-
import { cli, optional, parseNumberOption } from "comline"
|
|
6
|
+
import { cli, optional, parseBooleanOption, parseNumberOption } from "comline"
|
|
7
7
|
import { z } from "zod"
|
|
8
8
|
|
|
9
9
|
import type { FlightDeckOptions } from "./flightdeck.lib"
|
|
10
|
-
import { FlightDeck } from "./flightdeck.lib"
|
|
10
|
+
import { FlightDeck, FlightDeckLogger } from "./flightdeck.lib"
|
|
11
|
+
|
|
12
|
+
const CLI_LOGGER = new FlightDeckLogger(`comline`, process.pid, undefined, {
|
|
13
|
+
jsonLogging: true,
|
|
14
|
+
})
|
|
15
|
+
Object.assign(console, {
|
|
16
|
+
log: CLI_LOGGER.info.bind(CLI_LOGGER),
|
|
17
|
+
info: CLI_LOGGER.info.bind(CLI_LOGGER),
|
|
18
|
+
warn: CLI_LOGGER.warn.bind(CLI_LOGGER),
|
|
19
|
+
error: CLI_LOGGER.error.bind(CLI_LOGGER),
|
|
20
|
+
})
|
|
11
21
|
|
|
12
22
|
const FLIGHTDECK_MANUAL = {
|
|
13
23
|
optionsSchema: z.object({
|
|
@@ -20,6 +30,7 @@ const FLIGHTDECK_MANUAL = {
|
|
|
20
30
|
install: z.string(),
|
|
21
31
|
checkAvailability: z.string(),
|
|
22
32
|
}),
|
|
33
|
+
jsonLogging: z.boolean().optional(),
|
|
23
34
|
}),
|
|
24
35
|
options: {
|
|
25
36
|
port: {
|
|
@@ -55,6 +66,13 @@ const FLIGHTDECK_MANUAL = {
|
|
|
55
66
|
example: `--scripts="{\\"download\\":\\"npm i",\\"install\\":\\"npm run build\\"}"`,
|
|
56
67
|
parse: JSON.parse,
|
|
57
68
|
},
|
|
69
|
+
jsonLogging: {
|
|
70
|
+
flag: `j`,
|
|
71
|
+
required: false,
|
|
72
|
+
description: `Enable json logging.`,
|
|
73
|
+
example: `--jsonLogging`,
|
|
74
|
+
parse: parseBooleanOption,
|
|
75
|
+
},
|
|
58
76
|
},
|
|
59
77
|
} satisfies OptionsGroup<FlightDeckOptions>
|
|
60
78
|
|
package/src/flightdeck.lib.ts
CHANGED
|
@@ -3,13 +3,16 @@ import type { Server } from "node:http"
|
|
|
3
3
|
import { createServer } from "node:http"
|
|
4
4
|
import { homedir } from "node:os"
|
|
5
5
|
import { resolve } from "node:path"
|
|
6
|
+
import { inspect } from "node:util"
|
|
6
7
|
|
|
7
8
|
import { Future } from "atom.io/internal"
|
|
8
9
|
import { discoverType } from "atom.io/introspection"
|
|
9
10
|
import { fromEntries, toEntries } from "atom.io/json"
|
|
10
11
|
import { ChildSocket } from "atom.io/realtime-server"
|
|
11
12
|
import { CronJob } from "cron"
|
|
13
|
+
import { z } from "zod"
|
|
12
14
|
|
|
15
|
+
import type { LnavFormat } from "../gen/lnav-format-schema.gen"
|
|
13
16
|
import { FilesystemStorage } from "./filesystem-storage"
|
|
14
17
|
import { env } from "./flightdeck.env"
|
|
15
18
|
|
|
@@ -37,6 +40,7 @@ export type FlightDeckOptions<S extends string = string> = {
|
|
|
37
40
|
}
|
|
38
41
|
port?: number | undefined
|
|
39
42
|
flightdeckRootDir?: string | undefined
|
|
43
|
+
jsonLogging?: boolean | undefined
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
export class FlightDeck<S extends string = string> {
|
|
@@ -61,7 +65,7 @@ export class FlightDeck<S extends string = string> {
|
|
|
61
65
|
|
|
62
66
|
protected logger: Pick<Console, `error` | `info` | `warn`>
|
|
63
67
|
protected serviceLoggers: {
|
|
64
|
-
readonly [service in S]:
|
|
68
|
+
readonly [service in S]: FlightDeckLogger
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
protected updateAvailabilityChecker: CronJob | null = null
|
|
@@ -95,34 +99,21 @@ export class FlightDeck<S extends string = string> {
|
|
|
95
99
|
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate }
|
|
96
100
|
this.autoRespawnDeadServices = true
|
|
97
101
|
|
|
98
|
-
this.logger =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
error: (...args: any[]) => {
|
|
106
|
-
console.error(`${this.options.packageName}:`, ...args)
|
|
107
|
-
},
|
|
108
|
-
}
|
|
102
|
+
this.logger = new FlightDeckLogger(
|
|
103
|
+
this.options.packageName,
|
|
104
|
+
process.pid,
|
|
105
|
+
undefined,
|
|
106
|
+
{ jsonLogging: this.options.jsonLogging ?? false },
|
|
107
|
+
)
|
|
109
108
|
this.serviceLoggers = fromEntries(
|
|
110
109
|
servicesEntries.map(([serviceName]) => [
|
|
111
110
|
serviceName,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
},
|
|
119
|
-
error: (...args: any[]) => {
|
|
120
|
-
console.error(
|
|
121
|
-
`${this.options.packageName}::${serviceName}:`,
|
|
122
|
-
...args,
|
|
123
|
-
)
|
|
124
|
-
},
|
|
125
|
-
},
|
|
111
|
+
new FlightDeckLogger(
|
|
112
|
+
this.options.packageName,
|
|
113
|
+
process.pid,
|
|
114
|
+
serviceName,
|
|
115
|
+
{ jsonLogging: this.options.jsonLogging ?? false },
|
|
116
|
+
),
|
|
126
117
|
]),
|
|
127
118
|
)
|
|
128
119
|
|
|
@@ -316,13 +307,15 @@ export class FlightDeck<S extends string = string> {
|
|
|
316
307
|
cwd: this.options.flightdeckRootDir,
|
|
317
308
|
env: import.meta.env,
|
|
318
309
|
})
|
|
319
|
-
this.
|
|
310
|
+
const serviceLogger = this.serviceLoggers[serviceName]
|
|
311
|
+
const service = (this.services[serviceName] = new ChildSocket(
|
|
320
312
|
serviceProcess,
|
|
321
313
|
`${this.options.packageName}::${serviceName}`,
|
|
322
|
-
|
|
323
|
-
)
|
|
314
|
+
serviceLogger,
|
|
315
|
+
))
|
|
316
|
+
serviceLogger.processCode = service.process.pid ?? -1
|
|
324
317
|
this.services[serviceName].onAny((...messages) => {
|
|
325
|
-
|
|
318
|
+
serviceLogger.info(`💬`, ...messages)
|
|
326
319
|
})
|
|
327
320
|
this.services[serviceName].on(`readyToUpdate`, () => {
|
|
328
321
|
this.logger.info(`Service "${serviceName}" is ready to update.`)
|
|
@@ -444,3 +437,213 @@ export class FlightDeck<S extends string = string> {
|
|
|
444
437
|
}
|
|
445
438
|
}
|
|
446
439
|
}
|
|
440
|
+
|
|
441
|
+
export const flightDeckLogSchema = z.object({
|
|
442
|
+
level: z.union([z.literal(`info`), z.literal(`warn`), z.literal(`ERR!`)]),
|
|
443
|
+
timestamp: z.number(),
|
|
444
|
+
package: z.string(),
|
|
445
|
+
service: z.string().optional(),
|
|
446
|
+
process: z.number(),
|
|
447
|
+
body: z.string(),
|
|
448
|
+
})
|
|
449
|
+
export type FlightDeckLog = z.infer<typeof flightDeckLogSchema>
|
|
450
|
+
|
|
451
|
+
const LINE_FORMAT = `line-format` satisfies keyof LnavFormat
|
|
452
|
+
const VALUE = `value` satisfies keyof LnavFormat
|
|
453
|
+
|
|
454
|
+
export type LnavFormatVisualComponent = Exclude<
|
|
455
|
+
Exclude<LnavFormat[`line-format`], undefined>[number],
|
|
456
|
+
string
|
|
457
|
+
>
|
|
458
|
+
|
|
459
|
+
export type LnavFormatBreakdown = Exclude<LnavFormat[`value`], undefined>
|
|
460
|
+
export type MemberOf<T> = T[keyof T]
|
|
461
|
+
export type LnavFormatValueDefinition = MemberOf<LnavFormatBreakdown>
|
|
462
|
+
|
|
463
|
+
export type FlightDeckFormat = {
|
|
464
|
+
[LINE_FORMAT]: (
|
|
465
|
+
| string
|
|
466
|
+
| (LnavFormatVisualComponent & {
|
|
467
|
+
field: keyof FlightDeckLog | `__level__` | `__timestamp__`
|
|
468
|
+
})
|
|
469
|
+
)[]
|
|
470
|
+
[VALUE]: {
|
|
471
|
+
[K in keyof FlightDeckLog]: LnavFormatValueDefinition & {
|
|
472
|
+
kind: FlightDeckLog[K] extends number | undefined
|
|
473
|
+
? `integer`
|
|
474
|
+
: FlightDeckLog[K] extends string | undefined
|
|
475
|
+
? `string`
|
|
476
|
+
: never
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export const FLIGHTDECK_LNAV_FORMAT = {
|
|
482
|
+
title: `FlightDeck Log`,
|
|
483
|
+
description: `Format for events logged by the FlightDeck process manager.`,
|
|
484
|
+
"file-type": `json`,
|
|
485
|
+
"timestamp-field": `timestamp`,
|
|
486
|
+
"subsecond-field": `subsecond`,
|
|
487
|
+
"subsecond-units": `milli`,
|
|
488
|
+
"opid-field": `service`,
|
|
489
|
+
"level-field": `level`,
|
|
490
|
+
level: {
|
|
491
|
+
info: `info`,
|
|
492
|
+
warning: `warn`,
|
|
493
|
+
error: `err!`,
|
|
494
|
+
},
|
|
495
|
+
"ordered-by-time": true,
|
|
496
|
+
|
|
497
|
+
[LINE_FORMAT]: [
|
|
498
|
+
{
|
|
499
|
+
field: `__level__`,
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
prefix: ` `,
|
|
503
|
+
field: `__timestamp__`,
|
|
504
|
+
"timestamp-format": `%Y-%m-%dT%H:%M:%S.%L%Z`,
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
prefix: ` `,
|
|
508
|
+
field: `package`,
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
prefix: `::`,
|
|
512
|
+
field: `service`,
|
|
513
|
+
"default-value": ``,
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
prefix: `[`,
|
|
517
|
+
field: `process`,
|
|
518
|
+
suffix: `]`,
|
|
519
|
+
"min-width": 5,
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
prefix: `: `,
|
|
523
|
+
field: `body`,
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
|
|
527
|
+
[VALUE]: {
|
|
528
|
+
timestamp: {
|
|
529
|
+
kind: `integer`,
|
|
530
|
+
},
|
|
531
|
+
level: {
|
|
532
|
+
kind: `string`,
|
|
533
|
+
},
|
|
534
|
+
package: {
|
|
535
|
+
kind: `string`,
|
|
536
|
+
},
|
|
537
|
+
service: {
|
|
538
|
+
kind: `string`,
|
|
539
|
+
},
|
|
540
|
+
process: {
|
|
541
|
+
kind: `integer`,
|
|
542
|
+
},
|
|
543
|
+
body: {
|
|
544
|
+
kind: `string`,
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
} as const satisfies FlightDeckFormat & LnavFormat
|
|
548
|
+
|
|
549
|
+
export class FlightDeckLogger
|
|
550
|
+
implements Pick<Console, `error` | `info` | `warn`>
|
|
551
|
+
{
|
|
552
|
+
public readonly packageName: string
|
|
553
|
+
public readonly serviceName?: string
|
|
554
|
+
public readonly jsonLogging: boolean
|
|
555
|
+
public processCode: number
|
|
556
|
+
public constructor(
|
|
557
|
+
packageName: string,
|
|
558
|
+
processCode: number,
|
|
559
|
+
serviceName?: string,
|
|
560
|
+
options?: { jsonLogging: boolean },
|
|
561
|
+
) {
|
|
562
|
+
this.packageName = packageName
|
|
563
|
+
if (serviceName) {
|
|
564
|
+
this.serviceName = serviceName
|
|
565
|
+
}
|
|
566
|
+
this.processCode = processCode
|
|
567
|
+
this.jsonLogging = options?.jsonLogging ?? false
|
|
568
|
+
}
|
|
569
|
+
public info(...messages: unknown[]): void {
|
|
570
|
+
if (this.jsonLogging) {
|
|
571
|
+
const log: FlightDeckLog = {
|
|
572
|
+
timestamp: Date.now(),
|
|
573
|
+
level: `info`,
|
|
574
|
+
process: this.processCode,
|
|
575
|
+
package: this.packageName,
|
|
576
|
+
body: messages
|
|
577
|
+
.map((message) =>
|
|
578
|
+
typeof message === `string`
|
|
579
|
+
? message
|
|
580
|
+
: inspect(message, false, null, true),
|
|
581
|
+
)
|
|
582
|
+
.join(` `),
|
|
583
|
+
}
|
|
584
|
+
if (this.serviceName) {
|
|
585
|
+
log.service = this.serviceName
|
|
586
|
+
}
|
|
587
|
+
process.stdout.write(JSON.stringify(log) + `\n`)
|
|
588
|
+
} else {
|
|
589
|
+
const source = this.serviceName
|
|
590
|
+
? `${this.packageName}::${this.serviceName}`
|
|
591
|
+
: this.packageName
|
|
592
|
+
console.log(`${source}:`, ...messages)
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
public warn(...messages: unknown[]): void {
|
|
597
|
+
if (this.jsonLogging) {
|
|
598
|
+
const log: FlightDeckLog = {
|
|
599
|
+
timestamp: Date.now(),
|
|
600
|
+
level: `warn`,
|
|
601
|
+
process: this.processCode,
|
|
602
|
+
package: this.packageName,
|
|
603
|
+
body: messages
|
|
604
|
+
.map((message) =>
|
|
605
|
+
typeof message === `string`
|
|
606
|
+
? message
|
|
607
|
+
: inspect(message, false, null, true),
|
|
608
|
+
)
|
|
609
|
+
.join(` `),
|
|
610
|
+
}
|
|
611
|
+
if (this.serviceName) {
|
|
612
|
+
log.service = this.serviceName
|
|
613
|
+
}
|
|
614
|
+
process.stdout.write(JSON.stringify(log) + `\n`)
|
|
615
|
+
} else {
|
|
616
|
+
const source = this.serviceName
|
|
617
|
+
? `${this.packageName}::${this.serviceName}`
|
|
618
|
+
: this.packageName
|
|
619
|
+
console.warn(`${source}:`, ...messages)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
public error(...messages: unknown[]): void {
|
|
624
|
+
if (this.jsonLogging) {
|
|
625
|
+
const log: FlightDeckLog = {
|
|
626
|
+
timestamp: Date.now() + Math.floor(Math.random() * 1000),
|
|
627
|
+
level: `ERR!`,
|
|
628
|
+
process: this.processCode,
|
|
629
|
+
package: this.packageName,
|
|
630
|
+
body: messages
|
|
631
|
+
.map((message) =>
|
|
632
|
+
typeof message === `string`
|
|
633
|
+
? message
|
|
634
|
+
: inspect(message, false, null, true),
|
|
635
|
+
)
|
|
636
|
+
.join(` `),
|
|
637
|
+
}
|
|
638
|
+
if (this.serviceName) {
|
|
639
|
+
log.service = this.serviceName
|
|
640
|
+
}
|
|
641
|
+
process.stdout.write(JSON.stringify(log) + `\n`)
|
|
642
|
+
} else {
|
|
643
|
+
const source = this.serviceName
|
|
644
|
+
? `${this.packageName}::${this.serviceName}`
|
|
645
|
+
: this.packageName
|
|
646
|
+
console.error(`${source}:`, ...messages)
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|