flightdeck 0.2.0 → 0.2.1
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 +5 -1
- package/dist/flightdeck.bin.js +5668 -91
- package/dist/flightdeck.main.schema.json +5 -1
- package/dist/klaxon.bin.js +7 -4
- package/dist/lib.d.ts +35 -6
- package/dist/lib.js +5678 -95
- package/package.json +2 -1
- package/src/filesystem-storage.ts +67 -0
- package/src/flightdeck.bin.ts +1 -0
- package/src/flightdeck.lib.ts +142 -112
- package/src/klaxon.lib.ts +6 -2
- package/src/lib.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flightdeck",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jeremy Banka",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@t3-oss/env-core": "0.11.1",
|
|
25
|
+
"cron": "3.2.0",
|
|
25
26
|
"zod": "3.23.8",
|
|
26
27
|
"atom.io": "0.30.2",
|
|
27
28
|
"comline": "0.1.6"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
statSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs"
|
|
10
|
+
import { resolve } from "node:path"
|
|
11
|
+
|
|
12
|
+
export type FilesystemStorageOptions = {
|
|
13
|
+
path: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class FilesystemStorage<
|
|
17
|
+
T extends Record<string, string> = Record<string, string>,
|
|
18
|
+
> implements Storage
|
|
19
|
+
{
|
|
20
|
+
public rootDir: string
|
|
21
|
+
|
|
22
|
+
public constructor(options: FilesystemStorageOptions) {
|
|
23
|
+
this.rootDir = options.path
|
|
24
|
+
if (!existsSync(this.rootDir)) {
|
|
25
|
+
mkdirSync(this.rootDir, { recursive: true })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public getItem<K extends string & keyof T>(key: K): T[K] | null {
|
|
30
|
+
const filePath = resolve(this.rootDir, key)
|
|
31
|
+
if (existsSync(filePath)) {
|
|
32
|
+
return readFileSync(filePath, `utf-8`) as T[K]
|
|
33
|
+
}
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public setItem<K extends string & keyof T>(key: K, value: T[K]): void {
|
|
38
|
+
const filePath = resolve(this.rootDir, key)
|
|
39
|
+
writeFileSync(filePath, value)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public removeItem<K extends string & keyof T>(key: K): void {
|
|
43
|
+
const filePath = resolve(this.rootDir, key)
|
|
44
|
+
if (existsSync(filePath)) {
|
|
45
|
+
rmSync(filePath)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public key(index: number): (string & keyof T) | null {
|
|
50
|
+
const filePaths = readdirSync(this.rootDir)
|
|
51
|
+
const filePathsByDateCreated = filePaths.sort((a, b) => {
|
|
52
|
+
const aStat = statSync(a)
|
|
53
|
+
const bStat = statSync(b)
|
|
54
|
+
return bStat.ctimeMs - aStat.ctimeMs
|
|
55
|
+
})
|
|
56
|
+
return (filePathsByDateCreated[index] as string & keyof T) ?? null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public clear(): void {
|
|
60
|
+
rmSync(this.rootDir, { recursive: true })
|
|
61
|
+
mkdirSync(this.rootDir, { recursive: true })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public get length(): number {
|
|
65
|
+
return readdirSync(this.rootDir).length
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/flightdeck.bin.ts
CHANGED
package/src/flightdeck.lib.ts
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
import { execSync, spawn } from "node:child_process"
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
|
3
2
|
import type { Server } from "node:http"
|
|
4
3
|
import { createServer } from "node:http"
|
|
5
4
|
import { homedir } from "node:os"
|
|
6
5
|
import { resolve } from "node:path"
|
|
7
6
|
|
|
8
7
|
import { Future } from "atom.io/internal"
|
|
8
|
+
import { discoverType } from "atom.io/introspection"
|
|
9
9
|
import { fromEntries, toEntries } from "atom.io/json"
|
|
10
10
|
import { ChildSocket } from "atom.io/realtime-server"
|
|
11
|
+
import { CronJob } from "cron"
|
|
11
12
|
|
|
13
|
+
import { FilesystemStorage } from "./filesystem-storage"
|
|
12
14
|
import { env } from "./flightdeck.env"
|
|
13
15
|
|
|
16
|
+
export const FLIGHTDECK_SETUP_PHASES = [`downloaded`, `installed`] as const
|
|
17
|
+
|
|
18
|
+
export type FlightDeckSetupPhase = (typeof FLIGHTDECK_SETUP_PHASES)[number]
|
|
19
|
+
|
|
20
|
+
export const FLIGHTDECK_UPDATE_PHASES = [`notified`, `confirmed`] as const
|
|
21
|
+
|
|
22
|
+
export type FlightDeckUpdatePhase = (typeof FLIGHTDECK_UPDATE_PHASES)[number]
|
|
23
|
+
|
|
24
|
+
export function isVersionNumber(version: string): boolean {
|
|
25
|
+
return (
|
|
26
|
+
/^\d+\.\d+\.\d+$/.test(version) || !Number.isNaN(Number.parseFloat(version))
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
export type FlightDeckOptions<S extends string = string> = {
|
|
15
31
|
packageName: string
|
|
16
32
|
services: { [service in S]: { run: string; waitFor: boolean } }
|
|
17
33
|
scripts: {
|
|
18
34
|
download: string
|
|
19
35
|
install: string
|
|
36
|
+
checkAvailability?: string
|
|
20
37
|
}
|
|
21
38
|
port?: number | undefined
|
|
22
39
|
flightdeckRootDir?: string | undefined
|
|
@@ -25,6 +42,11 @@ export type FlightDeckOptions<S extends string = string> = {
|
|
|
25
42
|
export class FlightDeck<S extends string = string> {
|
|
26
43
|
protected safety = 0
|
|
27
44
|
|
|
45
|
+
protected storage: FilesystemStorage<{
|
|
46
|
+
setupPhase: FlightDeckSetupPhase
|
|
47
|
+
updatePhase: FlightDeckUpdatePhase
|
|
48
|
+
updateAwaitedVersion: string
|
|
49
|
+
}>
|
|
28
50
|
protected webhookServer: Server
|
|
29
51
|
protected services: {
|
|
30
52
|
[service in S]: ChildSocket<
|
|
@@ -35,13 +57,15 @@ export class FlightDeck<S extends string = string> {
|
|
|
35
57
|
protected serviceIdx: { readonly [service in S]: number }
|
|
36
58
|
public defaultServicesReadyToUpdate: { readonly [service in S]: boolean }
|
|
37
59
|
public servicesReadyToUpdate: { [service in S]: boolean }
|
|
38
|
-
public
|
|
60
|
+
public autoRespawnDeadServices: boolean
|
|
39
61
|
|
|
40
62
|
protected logger: Pick<Console, `error` | `info` | `warn`>
|
|
41
63
|
protected serviceLoggers: {
|
|
42
64
|
readonly [service in S]: Pick<Console, `error` | `info` | `warn`>
|
|
43
65
|
}
|
|
44
66
|
|
|
67
|
+
protected updateAvailabilityChecker: CronJob | null = null
|
|
68
|
+
|
|
45
69
|
public servicesLive: Future<void>[]
|
|
46
70
|
public servicesDead: Future<void>[]
|
|
47
71
|
public live = new Future(() => {})
|
|
@@ -49,11 +73,9 @@ export class FlightDeck<S extends string = string> {
|
|
|
49
73
|
|
|
50
74
|
protected restartTimes: number[] = []
|
|
51
75
|
|
|
52
|
-
protected persistentStateDir: string
|
|
53
|
-
|
|
54
76
|
public constructor(public readonly options: FlightDeckOptions<S>) {
|
|
55
77
|
const { FLIGHTDECK_SECRET } = env
|
|
56
|
-
const { flightdeckRootDir = resolve(homedir(), `
|
|
78
|
+
const { flightdeckRootDir = resolve(homedir(), `.flightdeck`) } = options
|
|
57
79
|
const port = options.port ?? 8080
|
|
58
80
|
const origin = `http://localhost:${port}`
|
|
59
81
|
|
|
@@ -71,7 +93,7 @@ export class FlightDeck<S extends string = string> {
|
|
|
71
93
|
]),
|
|
72
94
|
)
|
|
73
95
|
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate }
|
|
74
|
-
this.
|
|
96
|
+
this.autoRespawnDeadServices = true
|
|
75
97
|
|
|
76
98
|
this.logger = {
|
|
77
99
|
info: (...args: any[]) => {
|
|
@@ -109,14 +131,9 @@ export class FlightDeck<S extends string = string> {
|
|
|
109
131
|
this.live.use(Promise.all(this.servicesLive))
|
|
110
132
|
this.dead.use(Promise.all(this.servicesDead))
|
|
111
133
|
|
|
112
|
-
this.
|
|
113
|
-
flightdeckRootDir,
|
|
114
|
-
|
|
115
|
-
options.packageName,
|
|
116
|
-
)
|
|
117
|
-
if (!existsSync(this.persistentStateDir)) {
|
|
118
|
-
mkdirSync(this.persistentStateDir, { recursive: true })
|
|
119
|
-
}
|
|
134
|
+
this.storage = new FilesystemStorage({
|
|
135
|
+
path: resolve(flightdeckRootDir, `storage`, options.packageName),
|
|
136
|
+
})
|
|
120
137
|
|
|
121
138
|
if (FLIGHTDECK_SECRET === undefined) {
|
|
122
139
|
this.logger.warn(
|
|
@@ -142,62 +159,34 @@ export class FlightDeck<S extends string = string> {
|
|
|
142
159
|
}
|
|
143
160
|
const url = new URL(req.url, origin)
|
|
144
161
|
this.logger.info(req.method, url.pathname)
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
toEntries(this.servicesReadyToUpdate).every(
|
|
174
|
-
([, isReady]) => isReady,
|
|
175
|
-
)
|
|
176
|
-
) {
|
|
177
|
-
this.tryUpdate()
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
|
-
for (const entry of toEntries(this.services)) {
|
|
181
|
-
const [serviceName, service] = entry
|
|
182
|
-
if (service) {
|
|
183
|
-
if (this.options.services[serviceName].waitFor) {
|
|
184
|
-
service.emit(`updatesReady`)
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
this.startService(serviceName)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
break
|
|
192
|
-
|
|
193
|
-
default:
|
|
194
|
-
throw 404
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
break
|
|
198
|
-
|
|
199
|
-
default:
|
|
200
|
-
throw 405
|
|
162
|
+
|
|
163
|
+
const versionForeignInput = Buffer.concat(data).toString()
|
|
164
|
+
if (!isVersionNumber(versionForeignInput)) {
|
|
165
|
+
throw 400
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
res.writeHead(200)
|
|
169
|
+
res.end()
|
|
170
|
+
|
|
171
|
+
this.storage.setItem(`updatePhase`, `notified`)
|
|
172
|
+
this.storage.setItem(`updateAwaitedVersion`, versionForeignInput)
|
|
173
|
+
const { checkAvailability } = options.scripts
|
|
174
|
+
if (checkAvailability) {
|
|
175
|
+
this.updateAvailabilityChecker?.stop()
|
|
176
|
+
this.seekUpdate(versionForeignInput)
|
|
177
|
+
const updatePhase = this.storage.getItem(`updatePhase`)
|
|
178
|
+
this.logger.info(`> storage("updatePhase") >`, updatePhase)
|
|
179
|
+
if (updatePhase === `notified`) {
|
|
180
|
+
this.updateAvailabilityChecker = new CronJob(
|
|
181
|
+
`30 * * * * *`,
|
|
182
|
+
() => {
|
|
183
|
+
this.seekUpdate(versionForeignInput)
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
this.updateAvailabilityChecker.start()
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
this.downloadPackage()
|
|
201
190
|
}
|
|
202
191
|
} catch (thrown) {
|
|
203
192
|
this.logger.error(thrown, req.url)
|
|
@@ -225,6 +214,43 @@ export class FlightDeck<S extends string = string> {
|
|
|
225
214
|
})
|
|
226
215
|
}
|
|
227
216
|
|
|
217
|
+
protected seekUpdate(version: string): void {
|
|
218
|
+
this.logger.info(`Checking for updates...`)
|
|
219
|
+
const { checkAvailability } = this.options.scripts
|
|
220
|
+
if (!checkAvailability) {
|
|
221
|
+
this.logger.info(`No checkAvailability script found.`)
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const out = execSync(`${checkAvailability} ${version}`)
|
|
226
|
+
this.logger.info(`Check stdout:`, out.toString())
|
|
227
|
+
this.updateAvailabilityChecker?.stop()
|
|
228
|
+
this.storage.setItem(`updatePhase`, `confirmed`)
|
|
229
|
+
this.downloadPackage()
|
|
230
|
+
this.announceUpdate()
|
|
231
|
+
} catch (thrown) {
|
|
232
|
+
if (thrown instanceof Error) {
|
|
233
|
+
this.logger.error(`Check failed:`, thrown.message)
|
|
234
|
+
} else {
|
|
235
|
+
const thrownType = discoverType(thrown)
|
|
236
|
+
this.logger.error(`Check threw`, thrownType, thrown)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
protected announceUpdate(): void {
|
|
242
|
+
for (const entry of toEntries(this.services)) {
|
|
243
|
+
const [serviceName, service] = entry
|
|
244
|
+
if (service) {
|
|
245
|
+
if (this.options.services[serviceName].waitFor) {
|
|
246
|
+
service.emit(`updatesReady`)
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
this.startService(serviceName)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
228
254
|
protected tryUpdate(): void {
|
|
229
255
|
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
230
256
|
this.logger.info(`All services are ready to update.`)
|
|
@@ -254,11 +280,26 @@ export class FlightDeck<S extends string = string> {
|
|
|
254
280
|
|
|
255
281
|
protected startAllServices(): Future<unknown> {
|
|
256
282
|
this.logger.info(`Starting all services...`)
|
|
257
|
-
this.
|
|
258
|
-
|
|
259
|
-
|
|
283
|
+
this.autoRespawnDeadServices = true
|
|
284
|
+
const setupPhase = this.storage.getItem(`setupPhase`)
|
|
285
|
+
this.logger.info(`> storage("setupPhase") >`, setupPhase)
|
|
286
|
+
switch (setupPhase) {
|
|
287
|
+
case null:
|
|
288
|
+
this.logger.info(`Starting from scratch.`)
|
|
289
|
+
this.downloadPackage()
|
|
290
|
+
this.installPackage()
|
|
291
|
+
return this.startAllServices()
|
|
292
|
+
case `downloaded`:
|
|
293
|
+
this.logger.info(`Found package downloaded but not installed.`)
|
|
294
|
+
this.installPackage()
|
|
295
|
+
return this.startAllServices()
|
|
296
|
+
case `installed`: {
|
|
297
|
+
for (const [serviceName] of toEntries(this.services)) {
|
|
298
|
+
this.startService(serviceName)
|
|
299
|
+
}
|
|
300
|
+
return this.live
|
|
301
|
+
}
|
|
260
302
|
}
|
|
261
|
-
return this.live
|
|
262
303
|
}
|
|
263
304
|
|
|
264
305
|
protected startService(serviceName: S): void {
|
|
@@ -269,17 +310,6 @@ export class FlightDeck<S extends string = string> {
|
|
|
269
310
|
throw new Error(`Out of tries...`)
|
|
270
311
|
}
|
|
271
312
|
this.safety++
|
|
272
|
-
const readyFile = resolve(this.persistentStateDir, `ready`)
|
|
273
|
-
if (!existsSync(readyFile)) {
|
|
274
|
-
this.logger.info(
|
|
275
|
-
`Tried to start service but failed: could not find readyFile: ${readyFile}`,
|
|
276
|
-
)
|
|
277
|
-
this.getLatestRelease()
|
|
278
|
-
this.applyUpdate()
|
|
279
|
-
this.startService(serviceName)
|
|
280
|
-
|
|
281
|
-
return
|
|
282
|
-
}
|
|
283
313
|
|
|
284
314
|
const [exe, ...args] = this.options.services[serviceName].run.split(` `)
|
|
285
315
|
const serviceProcess = spawn(exe, args, {
|
|
@@ -292,10 +322,10 @@ export class FlightDeck<S extends string = string> {
|
|
|
292
322
|
console,
|
|
293
323
|
)
|
|
294
324
|
this.services[serviceName].onAny((...messages) => {
|
|
295
|
-
this.
|
|
325
|
+
this.serviceLoggers[serviceName].info(`💬`, ...messages)
|
|
296
326
|
})
|
|
297
327
|
this.services[serviceName].on(`readyToUpdate`, () => {
|
|
298
|
-
this.
|
|
328
|
+
this.logger.info(`Service "${serviceName}" is ready to update.`)
|
|
299
329
|
this.servicesReadyToUpdate[serviceName] = true
|
|
300
330
|
this.tryUpdate()
|
|
301
331
|
})
|
|
@@ -308,18 +338,21 @@ export class FlightDeck<S extends string = string> {
|
|
|
308
338
|
this.dead.use(Promise.all(this.servicesDead))
|
|
309
339
|
})
|
|
310
340
|
this.services[serviceName].process.once(`close`, (exitCode) => {
|
|
311
|
-
this.
|
|
341
|
+
this.logger.info(
|
|
342
|
+
`Auto-respawn saw "${serviceName}" exit with code ${exitCode}`,
|
|
343
|
+
)
|
|
312
344
|
this.services[serviceName] = null
|
|
313
|
-
if (!this.
|
|
314
|
-
this.
|
|
345
|
+
if (!this.autoRespawnDeadServices) {
|
|
346
|
+
this.logger.info(`Auto-respawn is off; "${serviceName}" rests.`)
|
|
315
347
|
return
|
|
316
348
|
}
|
|
317
|
-
const
|
|
318
|
-
|
|
349
|
+
const updatePhase = this.storage.getItem(`updatePhase`)
|
|
350
|
+
this.logger.info(`> storage("updatePhase") >`, updatePhase)
|
|
351
|
+
const updatesAreReady = updatePhase === `confirmed`
|
|
319
352
|
if (updatesAreReady) {
|
|
320
353
|
this.serviceLoggers[serviceName].info(`Updating before startup...`)
|
|
321
354
|
this.restartTimes = []
|
|
322
|
-
this.
|
|
355
|
+
this.installPackage()
|
|
323
356
|
this.startService(serviceName)
|
|
324
357
|
} else {
|
|
325
358
|
const now = Date.now()
|
|
@@ -342,18 +375,13 @@ export class FlightDeck<S extends string = string> {
|
|
|
342
375
|
this.safety = 0
|
|
343
376
|
}
|
|
344
377
|
|
|
345
|
-
protected
|
|
346
|
-
this.logger.info(`
|
|
347
|
-
|
|
378
|
+
protected downloadPackage(): void {
|
|
379
|
+
this.logger.info(`Downloading...`)
|
|
348
380
|
try {
|
|
349
|
-
execSync(this.options.scripts.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
const readyFile = resolve(this.persistentStateDir, `ready`)
|
|
355
|
-
writeFileSync(readyFile, ``)
|
|
356
|
-
this.logger.info(`Installed!`)
|
|
381
|
+
const out = execSync(this.options.scripts.download)
|
|
382
|
+
this.logger.info(`Download stdout:`, out.toString())
|
|
383
|
+
this.storage.setItem(`setupPhase`, `downloaded`)
|
|
384
|
+
this.logger.info(`Downloaded!`)
|
|
357
385
|
} catch (thrown) {
|
|
358
386
|
if (thrown instanceof Error) {
|
|
359
387
|
this.logger.error(`Failed to get the latest release: ${thrown.message}`)
|
|
@@ -362,12 +390,14 @@ export class FlightDeck<S extends string = string> {
|
|
|
362
390
|
}
|
|
363
391
|
}
|
|
364
392
|
|
|
365
|
-
protected
|
|
366
|
-
this.logger.info(`
|
|
393
|
+
protected installPackage(): void {
|
|
394
|
+
this.logger.info(`Installing...`)
|
|
367
395
|
|
|
368
396
|
try {
|
|
369
|
-
execSync(this.options.scripts.
|
|
370
|
-
this.logger.info(`
|
|
397
|
+
const out = execSync(this.options.scripts.install)
|
|
398
|
+
this.logger.info(`Install stdout:`, out.toString())
|
|
399
|
+
this.storage.setItem(`setupPhase`, `installed`)
|
|
400
|
+
this.logger.info(`Installed!`)
|
|
371
401
|
} catch (thrown) {
|
|
372
402
|
if (thrown instanceof Error) {
|
|
373
403
|
this.logger.error(`Failed to get the latest release: ${thrown.message}`)
|
|
@@ -377,8 +407,8 @@ export class FlightDeck<S extends string = string> {
|
|
|
377
407
|
}
|
|
378
408
|
|
|
379
409
|
public stopAllServices(): Future<unknown> {
|
|
380
|
-
this.logger.info(`Stopping all services
|
|
381
|
-
this.
|
|
410
|
+
this.logger.info(`Stopping all services... auto-respawn disabled.`)
|
|
411
|
+
this.autoRespawnDeadServices = false
|
|
382
412
|
for (const [serviceName] of toEntries(this.services)) {
|
|
383
413
|
this.stopService(serviceName)
|
|
384
414
|
}
|
|
@@ -388,13 +418,13 @@ export class FlightDeck<S extends string = string> {
|
|
|
388
418
|
public stopService(serviceName: S): void {
|
|
389
419
|
const service = this.services[serviceName]
|
|
390
420
|
if (service) {
|
|
391
|
-
this.
|
|
421
|
+
this.logger.info(`Stopping service "${serviceName}"...`)
|
|
392
422
|
this.servicesDead[this.serviceIdx[serviceName]].use(
|
|
393
423
|
new Promise((pass) => {
|
|
394
424
|
service.emit(`timeToStop`)
|
|
395
425
|
service.process.once(`close`, (exitCode) => {
|
|
396
426
|
this.logger.info(
|
|
397
|
-
|
|
427
|
+
`Stopped service "${serviceName}"; exited with code ${exitCode}`,
|
|
398
428
|
)
|
|
399
429
|
this.services[serviceName] = null
|
|
400
430
|
pass()
|
package/src/klaxon.lib.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
export type AlertOptions = {
|
|
2
2
|
secret: string
|
|
3
3
|
endpoint: string
|
|
4
|
+
version: string
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
export async function alert({
|
|
7
8
|
secret,
|
|
8
9
|
endpoint,
|
|
10
|
+
version,
|
|
9
11
|
}: AlertOptions): Promise<Response> {
|
|
10
12
|
const response = await fetch(endpoint, {
|
|
11
13
|
method: `POST`,
|
|
12
14
|
headers: {
|
|
13
|
-
"Content-Type": `
|
|
15
|
+
"Content-Type": `text/plain;charset=UTF-8`,
|
|
14
16
|
Authorization: `Bearer ${secret}`,
|
|
15
17
|
},
|
|
18
|
+
body: version,
|
|
16
19
|
})
|
|
17
20
|
|
|
18
21
|
return response
|
|
@@ -64,7 +67,8 @@ export async function scramble<K extends string = string>({
|
|
|
64
67
|
const name = publishedPackage.name as K
|
|
65
68
|
const { endpoint } = packageConfig[name]
|
|
66
69
|
const secret = secretsConfig[name]
|
|
67
|
-
const
|
|
70
|
+
const version = publishedPackage.version
|
|
71
|
+
const alertResultPromise = alert({ secret, endpoint, version }).then(
|
|
68
72
|
(alertResult) => [name, alertResult] as const,
|
|
69
73
|
)
|
|
70
74
|
alertResults.push(alertResultPromise)
|
package/src/lib.ts
CHANGED