flightdeck 0.0.4 → 0.0.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 +16 -6
- package/dist/flightdeck.bin.js +55 -40
- package/dist/flightdeck.main.schema.json +16 -6
- package/dist/lib.d.ts +5 -4
- package/dist/lib.js +45 -33
- package/package.json +2 -2
- package/src/flightdeck.bin.ts +11 -8
- package/src/flightdeck.lib.ts +60 -53
|
@@ -32,11 +32,21 @@
|
|
|
32
32
|
"flightdeckRootDir": {
|
|
33
33
|
"type": "string"
|
|
34
34
|
},
|
|
35
|
-
"
|
|
36
|
-
"type": "
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
35
|
+
"scripts": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"download": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
"install": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"required": [
|
|
46
|
+
"download",
|
|
47
|
+
"install"
|
|
48
|
+
],
|
|
49
|
+
"additionalProperties": false
|
|
40
50
|
}
|
|
41
51
|
},
|
|
42
52
|
"required": [
|
|
@@ -44,7 +54,7 @@
|
|
|
44
54
|
"packageName",
|
|
45
55
|
"services",
|
|
46
56
|
"flightdeckRootDir",
|
|
47
|
-
"
|
|
57
|
+
"scripts"
|
|
48
58
|
],
|
|
49
59
|
"additionalProperties": false,
|
|
50
60
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
package/dist/flightdeck.bin.js
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
// src/flightdeck.bin.ts
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import {cli, optional
|
|
5
|
+
import {cli, optional} from "comline";
|
|
6
6
|
import {z} from "zod";
|
|
7
7
|
|
|
8
8
|
// src/flightdeck.lib.ts
|
|
9
9
|
import {execSync, spawn} from "node:child_process";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
writeFileSync
|
|
15
|
+
} from "node:fs";
|
|
11
16
|
import {createServer} from "node:http";
|
|
12
17
|
import {homedir} from "node:os";
|
|
13
18
|
import {resolve} from "node:path";
|
|
@@ -35,9 +40,7 @@ class FlightDeck {
|
|
|
35
40
|
dead = new Future(() => {
|
|
36
41
|
});
|
|
37
42
|
restartTimes = [];
|
|
38
|
-
|
|
39
|
-
updateServiceDir;
|
|
40
|
-
backupServiceDir;
|
|
43
|
+
persistentStateDir;
|
|
41
44
|
constructor(options) {
|
|
42
45
|
this.options = options;
|
|
43
46
|
const { secret, flightdeckRootDir = resolve(homedir(), `services`) } = options;
|
|
@@ -81,9 +84,10 @@ class FlightDeck {
|
|
|
81
84
|
}));
|
|
82
85
|
this.live.use(Promise.all(this.servicesLive));
|
|
83
86
|
this.dead.use(Promise.all(this.servicesDead));
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
this.persistentStateDir = resolve(flightdeckRootDir, `.state`, options.packageName);
|
|
88
|
+
if (!existsSync(this.persistentStateDir)) {
|
|
89
|
+
mkdirSync(this.persistentStateDir, { recursive: true });
|
|
90
|
+
}
|
|
87
91
|
createServer((req, res) => {
|
|
88
92
|
let data = [];
|
|
89
93
|
req.on(`data`, (chunk) => {
|
|
@@ -105,6 +109,16 @@ class FlightDeck {
|
|
|
105
109
|
{
|
|
106
110
|
res.writeHead(200);
|
|
107
111
|
res.end();
|
|
112
|
+
const installFile = resolve(this.persistentStateDir, `install`);
|
|
113
|
+
const readyFile = resolve(this.persistentStateDir, `ready`);
|
|
114
|
+
if (!existsSync(installFile)) {
|
|
115
|
+
this.logger.info(`Install file does not exist yet. Creating...`);
|
|
116
|
+
writeFileSync(installFile, ``);
|
|
117
|
+
}
|
|
118
|
+
if (existsSync(readyFile)) {
|
|
119
|
+
this.logger.info(`Ready file exists. Removing...`);
|
|
120
|
+
rmSync(readyFile);
|
|
121
|
+
}
|
|
108
122
|
this.getLatestRelease();
|
|
109
123
|
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
110
124
|
this.logger.info(`All services are ready to update!`);
|
|
@@ -158,17 +172,17 @@ class FlightDeck {
|
|
|
158
172
|
throw new Error(`Out of tries...`);
|
|
159
173
|
}
|
|
160
174
|
this.safety++;
|
|
161
|
-
|
|
162
|
-
|
|
175
|
+
const readyFile = resolve(this.persistentStateDir, `ready`);
|
|
176
|
+
if (!existsSync(readyFile)) {
|
|
177
|
+
this.logger.info(`Tried to start service but failed: could not find readyFile: ${readyFile}`);
|
|
163
178
|
this.getLatestRelease();
|
|
164
179
|
this.applyUpdate();
|
|
165
180
|
this.startService(serviceName);
|
|
166
181
|
return;
|
|
167
182
|
}
|
|
168
183
|
const [executable, ...args] = this.options.services[serviceName].run;
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
cwd: this.currentServiceDir,
|
|
184
|
+
const serviceProcess = spawn(executable, args, {
|
|
185
|
+
cwd: this.options.flightdeckRootDir,
|
|
172
186
|
env: import.meta.env
|
|
173
187
|
});
|
|
174
188
|
this.services[serviceName] = new ChildSocket(serviceProcess, `${this.options.packageName}::${serviceName}`, console);
|
|
@@ -200,7 +214,8 @@ class FlightDeck {
|
|
|
200
214
|
this.serviceLoggers[serviceName].info(`Will not be restarted.`);
|
|
201
215
|
return;
|
|
202
216
|
}
|
|
203
|
-
const
|
|
217
|
+
const installFile = resolve(this.persistentStateDir, `install`);
|
|
218
|
+
const updatesAreReady = existsSync(installFile);
|
|
204
219
|
if (updatesAreReady) {
|
|
205
220
|
this.serviceLoggers[serviceName].info(`Updating before startup...`);
|
|
206
221
|
this.restartTimes = [];
|
|
@@ -222,32 +237,28 @@ class FlightDeck {
|
|
|
222
237
|
this.safety = 0;
|
|
223
238
|
}
|
|
224
239
|
applyUpdate() {
|
|
225
|
-
this.logger.info(`
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
240
|
+
this.logger.info(`Installing...`);
|
|
241
|
+
try {
|
|
242
|
+
execSync(this.options.scripts.install);
|
|
243
|
+
const installFile = resolve(this.persistentStateDir, `install`);
|
|
244
|
+
if (existsSync(installFile)) {
|
|
245
|
+
rmSync(installFile);
|
|
231
246
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
renameSync(this.currentServiceDir, this.backupServiceDir);
|
|
247
|
+
const readyFile = resolve(this.persistentStateDir, `ready`);
|
|
248
|
+
writeFileSync(readyFile, ``);
|
|
249
|
+
this.logger.info(`Installed!`);
|
|
250
|
+
} catch (thrown) {
|
|
251
|
+
if (thrown instanceof Error) {
|
|
252
|
+
this.logger.error(`Failed to get the latest release: ${thrown.message}`);
|
|
239
253
|
}
|
|
240
|
-
|
|
241
|
-
this.restartTimes = [];
|
|
242
|
-
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate };
|
|
243
|
-
} else {
|
|
244
|
-
this.logger.error(`Tried to apply update but failed: could not find update directory ${this.updateServiceDir}`);
|
|
254
|
+
return;
|
|
245
255
|
}
|
|
246
256
|
}
|
|
247
257
|
getLatestRelease() {
|
|
248
|
-
this.logger.info(`
|
|
258
|
+
this.logger.info(`Downloading...`);
|
|
249
259
|
try {
|
|
250
|
-
execSync(this.options.
|
|
260
|
+
execSync(this.options.scripts.download);
|
|
261
|
+
this.logger.info(`Downloaded!`);
|
|
251
262
|
} catch (thrown) {
|
|
252
263
|
if (thrown instanceof Error) {
|
|
253
264
|
this.logger.error(`Failed to get the latest release: ${thrown.message}`);
|
|
@@ -279,6 +290,7 @@ class FlightDeck {
|
|
|
279
290
|
}
|
|
280
291
|
}
|
|
281
292
|
shutdown() {
|
|
293
|
+
this.logger.info(`Shutting down...`);
|
|
282
294
|
this.servicesShouldRestart = false;
|
|
283
295
|
this.stopAllServices();
|
|
284
296
|
}
|
|
@@ -291,7 +303,10 @@ var FLIGHTDECK_MANUAL = {
|
|
|
291
303
|
packageName: z.string(),
|
|
292
304
|
services: z.record(z.object({ run: z.array(z.string()), waitFor: z.boolean() })),
|
|
293
305
|
flightdeckRootDir: z.string(),
|
|
294
|
-
|
|
306
|
+
scripts: z.object({
|
|
307
|
+
download: z.string(),
|
|
308
|
+
install: z.string()
|
|
309
|
+
})
|
|
295
310
|
}),
|
|
296
311
|
options: {
|
|
297
312
|
secret: {
|
|
@@ -319,12 +334,12 @@ var FLIGHTDECK_MANUAL = {
|
|
|
319
334
|
description: `Directory where the service is stored.`,
|
|
320
335
|
example: `--flightdeckRootDir=\"./services/sample/repo/my-app/current\"`
|
|
321
336
|
},
|
|
322
|
-
|
|
323
|
-
flag: `
|
|
337
|
+
scripts: {
|
|
338
|
+
flag: `r`,
|
|
324
339
|
required: true,
|
|
325
|
-
description: `
|
|
326
|
-
example: `--
|
|
327
|
-
parse:
|
|
340
|
+
description: `Map of scripts to run.`,
|
|
341
|
+
example: `--scripts="{\\"download\\":\\"npm i",\\"install\\":\\"npm run build\\"}"`,
|
|
342
|
+
parse: JSON.parse
|
|
328
343
|
}
|
|
329
344
|
}
|
|
330
345
|
};
|
|
@@ -32,11 +32,21 @@
|
|
|
32
32
|
"flightdeckRootDir": {
|
|
33
33
|
"type": "string"
|
|
34
34
|
},
|
|
35
|
-
"
|
|
36
|
-
"type": "
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
35
|
+
"scripts": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"download": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
"install": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"required": [
|
|
46
|
+
"download",
|
|
47
|
+
"install"
|
|
48
|
+
],
|
|
49
|
+
"additionalProperties": false
|
|
40
50
|
}
|
|
41
51
|
},
|
|
42
52
|
"required": [
|
|
@@ -44,7 +54,7 @@
|
|
|
44
54
|
"packageName",
|
|
45
55
|
"services",
|
|
46
56
|
"flightdeckRootDir",
|
|
47
|
-
"
|
|
57
|
+
"scripts"
|
|
48
58
|
],
|
|
49
59
|
"additionalProperties": false,
|
|
50
60
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
package/dist/lib.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ type FlightDeckOptions<S extends string = string> = {
|
|
|
11
11
|
waitFor: boolean;
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
|
-
|
|
14
|
+
scripts: {
|
|
15
|
+
download: string;
|
|
16
|
+
install: string;
|
|
17
|
+
};
|
|
15
18
|
flightdeckRootDir?: string | undefined;
|
|
16
19
|
};
|
|
17
20
|
declare class FlightDeck<S extends string = string> {
|
|
@@ -45,9 +48,7 @@ declare class FlightDeck<S extends string = string> {
|
|
|
45
48
|
live: Future<unknown>;
|
|
46
49
|
dead: Future<unknown>;
|
|
47
50
|
protected restartTimes: number[];
|
|
48
|
-
|
|
49
|
-
readonly updateServiceDir: string;
|
|
50
|
-
readonly backupServiceDir: string;
|
|
51
|
+
protected persistentStateDir: string;
|
|
51
52
|
constructor(options: FlightDeckOptions<S>);
|
|
52
53
|
protected startAllServices(): void;
|
|
53
54
|
protected startService(serviceName: S): void;
|
package/dist/lib.js
CHANGED
|
@@ -11,7 +11,12 @@ var __export = (target, all) => {
|
|
|
11
11
|
|
|
12
12
|
// src/flightdeck.lib.ts
|
|
13
13
|
import {execSync, spawn} from "node:child_process";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
rmSync,
|
|
18
|
+
writeFileSync
|
|
19
|
+
} from "node:fs";
|
|
15
20
|
import {createServer} from "node:http";
|
|
16
21
|
import {homedir} from "node:os";
|
|
17
22
|
import {resolve} from "node:path";
|
|
@@ -39,9 +44,7 @@ class FlightDeck {
|
|
|
39
44
|
dead = new Future(() => {
|
|
40
45
|
});
|
|
41
46
|
restartTimes = [];
|
|
42
|
-
|
|
43
|
-
updateServiceDir;
|
|
44
|
-
backupServiceDir;
|
|
47
|
+
persistentStateDir;
|
|
45
48
|
constructor(options) {
|
|
46
49
|
this.options = options;
|
|
47
50
|
const { secret, flightdeckRootDir = resolve(homedir(), `services`) } = options;
|
|
@@ -85,9 +88,10 @@ class FlightDeck {
|
|
|
85
88
|
}));
|
|
86
89
|
this.live.use(Promise.all(this.servicesLive));
|
|
87
90
|
this.dead.use(Promise.all(this.servicesDead));
|
|
88
|
-
this.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
this.persistentStateDir = resolve(flightdeckRootDir, `.state`, options.packageName);
|
|
92
|
+
if (!existsSync(this.persistentStateDir)) {
|
|
93
|
+
mkdirSync(this.persistentStateDir, { recursive: true });
|
|
94
|
+
}
|
|
91
95
|
createServer((req, res) => {
|
|
92
96
|
let data = [];
|
|
93
97
|
req.on(`data`, (chunk) => {
|
|
@@ -109,6 +113,16 @@ class FlightDeck {
|
|
|
109
113
|
{
|
|
110
114
|
res.writeHead(200);
|
|
111
115
|
res.end();
|
|
116
|
+
const installFile = resolve(this.persistentStateDir, `install`);
|
|
117
|
+
const readyFile = resolve(this.persistentStateDir, `ready`);
|
|
118
|
+
if (!existsSync(installFile)) {
|
|
119
|
+
this.logger.info(`Install file does not exist yet. Creating...`);
|
|
120
|
+
writeFileSync(installFile, ``);
|
|
121
|
+
}
|
|
122
|
+
if (existsSync(readyFile)) {
|
|
123
|
+
this.logger.info(`Ready file exists. Removing...`);
|
|
124
|
+
rmSync(readyFile);
|
|
125
|
+
}
|
|
112
126
|
this.getLatestRelease();
|
|
113
127
|
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
114
128
|
this.logger.info(`All services are ready to update!`);
|
|
@@ -162,17 +176,17 @@ class FlightDeck {
|
|
|
162
176
|
throw new Error(`Out of tries...`);
|
|
163
177
|
}
|
|
164
178
|
this.safety++;
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
const readyFile = resolve(this.persistentStateDir, `ready`);
|
|
180
|
+
if (!existsSync(readyFile)) {
|
|
181
|
+
this.logger.info(`Tried to start service but failed: could not find readyFile: ${readyFile}`);
|
|
167
182
|
this.getLatestRelease();
|
|
168
183
|
this.applyUpdate();
|
|
169
184
|
this.startService(serviceName);
|
|
170
185
|
return;
|
|
171
186
|
}
|
|
172
187
|
const [executable, ...args] = this.options.services[serviceName].run;
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
cwd: this.currentServiceDir,
|
|
188
|
+
const serviceProcess = spawn(executable, args, {
|
|
189
|
+
cwd: this.options.flightdeckRootDir,
|
|
176
190
|
env: import.meta.env
|
|
177
191
|
});
|
|
178
192
|
this.services[serviceName] = new ChildSocket(serviceProcess, `${this.options.packageName}::${serviceName}`, console);
|
|
@@ -204,7 +218,8 @@ class FlightDeck {
|
|
|
204
218
|
this.serviceLoggers[serviceName].info(`Will not be restarted.`);
|
|
205
219
|
return;
|
|
206
220
|
}
|
|
207
|
-
const
|
|
221
|
+
const installFile = resolve(this.persistentStateDir, `install`);
|
|
222
|
+
const updatesAreReady = existsSync(installFile);
|
|
208
223
|
if (updatesAreReady) {
|
|
209
224
|
this.serviceLoggers[serviceName].info(`Updating before startup...`);
|
|
210
225
|
this.restartTimes = [];
|
|
@@ -226,32 +241,28 @@ class FlightDeck {
|
|
|
226
241
|
this.safety = 0;
|
|
227
242
|
}
|
|
228
243
|
applyUpdate() {
|
|
229
|
-
this.logger.info(`
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
244
|
+
this.logger.info(`Installing...`);
|
|
245
|
+
try {
|
|
246
|
+
execSync(this.options.scripts.install);
|
|
247
|
+
const installFile = resolve(this.persistentStateDir, `install`);
|
|
248
|
+
if (existsSync(installFile)) {
|
|
249
|
+
rmSync(installFile);
|
|
235
250
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
renameSync(this.currentServiceDir, this.backupServiceDir);
|
|
251
|
+
const readyFile = resolve(this.persistentStateDir, `ready`);
|
|
252
|
+
writeFileSync(readyFile, ``);
|
|
253
|
+
this.logger.info(`Installed!`);
|
|
254
|
+
} catch (thrown) {
|
|
255
|
+
if (thrown instanceof Error) {
|
|
256
|
+
this.logger.error(`Failed to get the latest release: ${thrown.message}`);
|
|
243
257
|
}
|
|
244
|
-
|
|
245
|
-
this.restartTimes = [];
|
|
246
|
-
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate };
|
|
247
|
-
} else {
|
|
248
|
-
this.logger.error(`Tried to apply update but failed: could not find update directory ${this.updateServiceDir}`);
|
|
258
|
+
return;
|
|
249
259
|
}
|
|
250
260
|
}
|
|
251
261
|
getLatestRelease() {
|
|
252
|
-
this.logger.info(`
|
|
262
|
+
this.logger.info(`Downloading...`);
|
|
253
263
|
try {
|
|
254
|
-
execSync(this.options.
|
|
264
|
+
execSync(this.options.scripts.download);
|
|
265
|
+
this.logger.info(`Downloaded!`);
|
|
255
266
|
} catch (thrown) {
|
|
256
267
|
if (thrown instanceof Error) {
|
|
257
268
|
this.logger.error(`Failed to get the latest release: ${thrown.message}`);
|
|
@@ -283,6 +294,7 @@ class FlightDeck {
|
|
|
283
294
|
}
|
|
284
295
|
}
|
|
285
296
|
shutdown() {
|
|
297
|
+
this.logger.info(`Shutting down...`);
|
|
286
298
|
this.servicesShouldRestart = false;
|
|
287
299
|
this.stopAllServices();
|
|
288
300
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flightdeck",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jeremy Banka",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@types/tmp": "0.2.6",
|
|
32
32
|
"concurrently": "9.0.1",
|
|
33
33
|
"tmp": "0.2.3",
|
|
34
|
-
"tsup": "8.
|
|
34
|
+
"tsup": "8.3.0",
|
|
35
35
|
"rimraf": "6.0.1",
|
|
36
36
|
"vitest": "2.1.1"
|
|
37
37
|
},
|
package/src/flightdeck.bin.ts
CHANGED
|
@@ -6,8 +6,8 @@ import type { OptionsGroup } from "comline"
|
|
|
6
6
|
import { cli, optional, parseArrayOption } from "comline"
|
|
7
7
|
import { z } from "zod"
|
|
8
8
|
|
|
9
|
-
import type { FlightDeckOptions } from "
|
|
10
|
-
import { FlightDeck } from "
|
|
9
|
+
import type { FlightDeckOptions } from "./flightdeck.lib"
|
|
10
|
+
import { FlightDeck } from "./flightdeck.lib"
|
|
11
11
|
|
|
12
12
|
const FLIGHTDECK_MANUAL = {
|
|
13
13
|
optionsSchema: z.object({
|
|
@@ -17,7 +17,10 @@ const FLIGHTDECK_MANUAL = {
|
|
|
17
17
|
z.object({ run: z.array(z.string()), waitFor: z.boolean() }),
|
|
18
18
|
),
|
|
19
19
|
flightdeckRootDir: z.string(),
|
|
20
|
-
|
|
20
|
+
scripts: z.object({
|
|
21
|
+
download: z.string(),
|
|
22
|
+
install: z.string(),
|
|
23
|
+
}),
|
|
21
24
|
}),
|
|
22
25
|
options: {
|
|
23
26
|
secret: {
|
|
@@ -45,12 +48,12 @@ const FLIGHTDECK_MANUAL = {
|
|
|
45
48
|
description: `Directory where the service is stored.`,
|
|
46
49
|
example: `--flightdeckRootDir=\"./services/sample/repo/my-app/current\"`,
|
|
47
50
|
},
|
|
48
|
-
|
|
49
|
-
flag: `
|
|
51
|
+
scripts: {
|
|
52
|
+
flag: `r`,
|
|
50
53
|
required: true,
|
|
51
|
-
description: `
|
|
52
|
-
example: `--
|
|
53
|
-
parse:
|
|
54
|
+
description: `Map of scripts to run.`,
|
|
55
|
+
example: `--scripts="{\\"download\\":\\"npm i",\\"install\\":\\"npm run build\\"}"`,
|
|
56
|
+
parse: JSON.parse,
|
|
54
57
|
},
|
|
55
58
|
},
|
|
56
59
|
} satisfies OptionsGroup<FlightDeckOptions>
|
package/src/flightdeck.lib.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { execSync, spawn } from "node:child_process"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs"
|
|
3
9
|
import type { Server } from "node:http"
|
|
4
10
|
import { createServer } from "node:http"
|
|
5
11
|
import { homedir } from "node:os"
|
|
@@ -13,7 +19,10 @@ export type FlightDeckOptions<S extends string = string> = {
|
|
|
13
19
|
secret: string
|
|
14
20
|
packageName: string
|
|
15
21
|
services: { [service in S]: { run: string[]; waitFor: boolean } }
|
|
16
|
-
|
|
22
|
+
scripts: {
|
|
23
|
+
download: string
|
|
24
|
+
install: string
|
|
25
|
+
}
|
|
17
26
|
flightdeckRootDir?: string | undefined
|
|
18
27
|
}
|
|
19
28
|
|
|
@@ -46,9 +55,7 @@ export class FlightDeck<S extends string = string> {
|
|
|
46
55
|
|
|
47
56
|
protected restartTimes: number[] = []
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
public readonly updateServiceDir: string
|
|
51
|
-
public readonly backupServiceDir: string
|
|
58
|
+
protected persistentStateDir: string
|
|
52
59
|
|
|
53
60
|
public constructor(public readonly options: FlightDeckOptions<S>) {
|
|
54
61
|
const { secret, flightdeckRootDir = resolve(homedir(), `services`) } =
|
|
@@ -106,21 +113,14 @@ export class FlightDeck<S extends string = string> {
|
|
|
106
113
|
this.live.use(Promise.all(this.servicesLive))
|
|
107
114
|
this.dead.use(Promise.all(this.servicesDead))
|
|
108
115
|
|
|
109
|
-
this.
|
|
110
|
-
flightdeckRootDir,
|
|
111
|
-
options.packageName,
|
|
112
|
-
`current`,
|
|
113
|
-
)
|
|
114
|
-
this.backupServiceDir = resolve(
|
|
115
|
-
flightdeckRootDir,
|
|
116
|
-
options.packageName,
|
|
117
|
-
`backup`,
|
|
118
|
-
)
|
|
119
|
-
this.updateServiceDir = resolve(
|
|
116
|
+
this.persistentStateDir = resolve(
|
|
120
117
|
flightdeckRootDir,
|
|
118
|
+
`.state`,
|
|
121
119
|
options.packageName,
|
|
122
|
-
`update`,
|
|
123
120
|
)
|
|
121
|
+
if (!existsSync(this.persistentStateDir)) {
|
|
122
|
+
mkdirSync(this.persistentStateDir, { recursive: true })
|
|
123
|
+
}
|
|
124
124
|
|
|
125
125
|
createServer((req, res) => {
|
|
126
126
|
let data: Uint8Array[] = []
|
|
@@ -143,6 +143,24 @@ export class FlightDeck<S extends string = string> {
|
|
|
143
143
|
{
|
|
144
144
|
res.writeHead(200)
|
|
145
145
|
res.end()
|
|
146
|
+
const installFile = resolve(
|
|
147
|
+
this.persistentStateDir,
|
|
148
|
+
`install`,
|
|
149
|
+
)
|
|
150
|
+
const readyFile = resolve(
|
|
151
|
+
this.persistentStateDir,
|
|
152
|
+
`ready`,
|
|
153
|
+
)
|
|
154
|
+
if (!existsSync(installFile)) {
|
|
155
|
+
this.logger.info(
|
|
156
|
+
`Install file does not exist yet. Creating...`,
|
|
157
|
+
)
|
|
158
|
+
writeFileSync(installFile, ``)
|
|
159
|
+
}
|
|
160
|
+
if (existsSync(readyFile)) {
|
|
161
|
+
this.logger.info(`Ready file exists. Removing...`)
|
|
162
|
+
rmSync(readyFile)
|
|
163
|
+
}
|
|
146
164
|
this.getLatestRelease()
|
|
147
165
|
if (
|
|
148
166
|
toEntries(this.servicesReadyToUpdate).every(
|
|
@@ -207,9 +225,10 @@ export class FlightDeck<S extends string = string> {
|
|
|
207
225
|
throw new Error(`Out of tries...`)
|
|
208
226
|
}
|
|
209
227
|
this.safety++
|
|
210
|
-
|
|
228
|
+
const readyFile = resolve(this.persistentStateDir, `ready`)
|
|
229
|
+
if (!existsSync(readyFile)) {
|
|
211
230
|
this.logger.info(
|
|
212
|
-
`Tried to start service but failed: could not find ${
|
|
231
|
+
`Tried to start service but failed: could not find readyFile: ${readyFile}`,
|
|
213
232
|
)
|
|
214
233
|
this.getLatestRelease()
|
|
215
234
|
this.applyUpdate()
|
|
@@ -219,11 +238,8 @@ export class FlightDeck<S extends string = string> {
|
|
|
219
238
|
}
|
|
220
239
|
|
|
221
240
|
const [executable, ...args] = this.options.services[serviceName].run
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
: executable
|
|
225
|
-
const serviceProcess = spawn(program, args, {
|
|
226
|
-
cwd: this.currentServiceDir,
|
|
241
|
+
const serviceProcess = spawn(executable, args, {
|
|
242
|
+
cwd: this.options.flightdeckRootDir,
|
|
227
243
|
env: import.meta.env,
|
|
228
244
|
})
|
|
229
245
|
this.services[serviceName] = new ChildSocket(
|
|
@@ -259,7 +275,8 @@ export class FlightDeck<S extends string = string> {
|
|
|
259
275
|
this.serviceLoggers[serviceName].info(`Will not be restarted.`)
|
|
260
276
|
return
|
|
261
277
|
}
|
|
262
|
-
const
|
|
278
|
+
const installFile = resolve(this.persistentStateDir, `install`)
|
|
279
|
+
const updatesAreReady = existsSync(installFile)
|
|
263
280
|
if (updatesAreReady) {
|
|
264
281
|
this.serviceLoggers[serviceName].info(`Updating before startup...`)
|
|
265
282
|
this.restartTimes = []
|
|
@@ -287,42 +304,31 @@ export class FlightDeck<S extends string = string> {
|
|
|
287
304
|
}
|
|
288
305
|
|
|
289
306
|
protected applyUpdate(): void {
|
|
290
|
-
this.logger.info(`
|
|
291
|
-
if (existsSync(this.updateServiceDir)) {
|
|
292
|
-
const runningServices = toEntries(this.services).filter(
|
|
293
|
-
([, service]) => service,
|
|
294
|
-
)
|
|
295
|
-
if (runningServices.length > 0) {
|
|
296
|
-
this.logger.error(
|
|
297
|
-
`Tried to apply update but failed. The following services are currently running: [${runningServices.map(([serviceName]) => serviceName).join(`, `)}]`,
|
|
298
|
-
)
|
|
299
|
-
return
|
|
300
|
-
}
|
|
307
|
+
this.logger.info(`Installing...`)
|
|
301
308
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
renameSync(this.currentServiceDir, this.backupServiceDir)
|
|
309
|
+
try {
|
|
310
|
+
execSync(this.options.scripts.install)
|
|
311
|
+
const installFile = resolve(this.persistentStateDir, `install`)
|
|
312
|
+
if (existsSync(installFile)) {
|
|
313
|
+
rmSync(installFile)
|
|
309
314
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
this.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
const readyFile = resolve(this.persistentStateDir, `ready`)
|
|
316
|
+
writeFileSync(readyFile, ``)
|
|
317
|
+
this.logger.info(`Installed!`)
|
|
318
|
+
} catch (thrown) {
|
|
319
|
+
if (thrown instanceof Error) {
|
|
320
|
+
this.logger.error(`Failed to get the latest release: ${thrown.message}`)
|
|
321
|
+
}
|
|
322
|
+
return
|
|
318
323
|
}
|
|
319
324
|
}
|
|
320
325
|
|
|
321
326
|
protected getLatestRelease(): void {
|
|
322
|
-
this.logger.info(`
|
|
327
|
+
this.logger.info(`Downloading...`)
|
|
323
328
|
|
|
324
329
|
try {
|
|
325
|
-
execSync(this.options.
|
|
330
|
+
execSync(this.options.scripts.download)
|
|
331
|
+
this.logger.info(`Downloaded!`)
|
|
326
332
|
} catch (thrown) {
|
|
327
333
|
if (thrown instanceof Error) {
|
|
328
334
|
this.logger.error(`Failed to get the latest release: ${thrown.message}`)
|
|
@@ -357,6 +363,7 @@ export class FlightDeck<S extends string = string> {
|
|
|
357
363
|
}
|
|
358
364
|
|
|
359
365
|
public shutdown(): void {
|
|
366
|
+
this.logger.info(`Shutting down...`)
|
|
360
367
|
this.servicesShouldRestart = false
|
|
361
368
|
this.stopAllServices()
|
|
362
369
|
}
|