flightdeck 0.0.2 → 0.0.4
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 +27 -15
- package/dist/flightdeck.bin.js +375 -0
- package/dist/flightdeck.main.schema.json +27 -15
- package/dist/klaxon.bin.js +87 -0
- package/dist/lib.d.ts +102 -25
- package/dist/lib.js +215 -76
- package/package.json +10 -8
- package/src/{bin.ts → flightdeck.bin.ts} +24 -28
- package/src/flightdeck.lib.ts +363 -0
- package/src/klaxon.bin.ts +58 -0
- package/src/klaxon.lib.ts +78 -0
- package/src/lib.ts +4 -1
- package/dist/bin.js +0 -102
- package/src/flightdeck.ts +0 -235
package/dist/lib.d.ts
CHANGED
|
@@ -1,36 +1,113 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Server } from 'node:http';
|
|
2
2
|
import { Future } from 'atom.io/internal';
|
|
3
3
|
import { ChildSocket } from 'atom.io/realtime-server';
|
|
4
4
|
|
|
5
|
-
type FlightDeckOptions = {
|
|
5
|
+
type FlightDeckOptions<S extends string = string> = {
|
|
6
6
|
secret: string;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
7
|
+
packageName: string;
|
|
8
|
+
services: {
|
|
9
|
+
[service in S]: {
|
|
10
|
+
run: string[];
|
|
11
|
+
waitFor: boolean;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
downloadPackageToUpdatesCmd: string[];
|
|
15
|
+
flightdeckRootDir?: string | undefined;
|
|
16
|
+
};
|
|
17
|
+
declare class FlightDeck<S extends string = string> {
|
|
18
|
+
readonly options: FlightDeckOptions<S>;
|
|
19
|
+
protected safety: number;
|
|
20
|
+
protected webhookServer: Server;
|
|
21
|
+
protected services: {
|
|
22
|
+
[service in S]: ChildSocket<{
|
|
23
|
+
updatesReady: [];
|
|
24
|
+
}, {
|
|
25
|
+
readyToUpdate: [];
|
|
26
|
+
alive: [];
|
|
27
|
+
}> | null;
|
|
28
|
+
};
|
|
29
|
+
protected serviceIdx: {
|
|
30
|
+
readonly [service in S]: number;
|
|
31
|
+
};
|
|
32
|
+
defaultServicesReadyToUpdate: {
|
|
33
|
+
readonly [service in S]: boolean;
|
|
34
|
+
};
|
|
35
|
+
servicesReadyToUpdate: {
|
|
36
|
+
[service in S]: boolean;
|
|
37
|
+
};
|
|
38
|
+
servicesShouldRestart: boolean;
|
|
39
|
+
protected logger: Pick<Console, `error` | `info` | `warn`>;
|
|
40
|
+
protected serviceLoggers: {
|
|
41
|
+
readonly [service in S]: Pick<Console, `error` | `info` | `warn`>;
|
|
42
|
+
};
|
|
43
|
+
servicesLive: Future<void>[];
|
|
44
|
+
servicesDead: Future<void>[];
|
|
45
|
+
live: Future<unknown>;
|
|
25
46
|
dead: Future<unknown>;
|
|
47
|
+
protected restartTimes: number[];
|
|
26
48
|
readonly currentServiceDir: string;
|
|
27
49
|
readonly updateServiceDir: string;
|
|
28
50
|
readonly backupServiceDir: string;
|
|
29
|
-
constructor(options: FlightDeckOptions);
|
|
30
|
-
protected
|
|
51
|
+
constructor(options: FlightDeckOptions<S>);
|
|
52
|
+
protected startAllServices(): void;
|
|
53
|
+
protected startService(serviceName: S): void;
|
|
31
54
|
protected applyUpdate(): void;
|
|
32
|
-
protected
|
|
33
|
-
|
|
55
|
+
protected getLatestRelease(): void;
|
|
56
|
+
stopAllServices(): void;
|
|
57
|
+
stopService(serviceName: S): void;
|
|
58
|
+
shutdown(): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type AlertOptions = {
|
|
62
|
+
secret: string;
|
|
63
|
+
endpoint: string;
|
|
64
|
+
};
|
|
65
|
+
declare function alert({ secret, endpoint, }: AlertOptions): Promise<Response>;
|
|
66
|
+
/**
|
|
67
|
+
* @see https://github.com/changesets/action/blob/main/src/run.ts
|
|
68
|
+
*/
|
|
69
|
+
type ChangesetsPublishedPackage = {
|
|
70
|
+
name: string;
|
|
71
|
+
version: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* @see https://github.com/changesets/action/blob/main/src/run.ts
|
|
75
|
+
*/
|
|
76
|
+
type ChangesetsPublishResult = {
|
|
77
|
+
published: true;
|
|
78
|
+
publishedPackages: ChangesetsPublishedPackage[];
|
|
79
|
+
} | {
|
|
80
|
+
published: false;
|
|
81
|
+
};
|
|
82
|
+
type PackageConfig<K extends string> = {
|
|
83
|
+
[key in K]: {
|
|
84
|
+
endpoint: string;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
type SecretsConfig<K extends string> = {
|
|
88
|
+
[key in K]: string;
|
|
89
|
+
};
|
|
90
|
+
type ScrambleOptions<K extends string = string> = {
|
|
91
|
+
packageConfig: PackageConfig<K>;
|
|
92
|
+
secretsConfig: SecretsConfig<K>;
|
|
93
|
+
publishedPackages: ChangesetsPublishedPackage[];
|
|
94
|
+
};
|
|
95
|
+
type ScrambleResult<K extends string = string> = {
|
|
96
|
+
[key in K]: Response;
|
|
97
|
+
};
|
|
98
|
+
declare function scramble<K extends string = string>({ packageConfig, secretsConfig, publishedPackages, }: ScrambleOptions<K>): Promise<ScrambleResult<K>>;
|
|
99
|
+
|
|
100
|
+
type klaxon_lib_AlertOptions = AlertOptions;
|
|
101
|
+
type klaxon_lib_ChangesetsPublishResult = ChangesetsPublishResult;
|
|
102
|
+
type klaxon_lib_ChangesetsPublishedPackage = ChangesetsPublishedPackage;
|
|
103
|
+
type klaxon_lib_PackageConfig<K extends string> = PackageConfig<K>;
|
|
104
|
+
type klaxon_lib_ScrambleOptions<K extends string = string> = ScrambleOptions<K>;
|
|
105
|
+
type klaxon_lib_ScrambleResult<K extends string = string> = ScrambleResult<K>;
|
|
106
|
+
type klaxon_lib_SecretsConfig<K extends string> = SecretsConfig<K>;
|
|
107
|
+
declare const klaxon_lib_alert: typeof alert;
|
|
108
|
+
declare const klaxon_lib_scramble: typeof scramble;
|
|
109
|
+
declare namespace klaxon_lib {
|
|
110
|
+
export { type klaxon_lib_AlertOptions as AlertOptions, type klaxon_lib_ChangesetsPublishResult as ChangesetsPublishResult, type klaxon_lib_ChangesetsPublishedPackage as ChangesetsPublishedPackage, type klaxon_lib_PackageConfig as PackageConfig, type klaxon_lib_ScrambleOptions as ScrambleOptions, type klaxon_lib_ScrambleResult as ScrambleResult, type klaxon_lib_SecretsConfig as SecretsConfig, klaxon_lib_alert as alert, klaxon_lib_scramble as scramble };
|
|
34
111
|
}
|
|
35
112
|
|
|
36
|
-
export { FlightDeck, type FlightDeckOptions };
|
|
113
|
+
export { FlightDeck, type FlightDeckOptions, klaxon_lib as Klaxon };
|
package/dist/lib.js
CHANGED
|
@@ -1,66 +1,129 @@
|
|
|
1
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/flightdeck.lib.ts
|
|
2
13
|
import {execSync, spawn} from "node:child_process";
|
|
3
14
|
import {existsSync, mkdirSync, renameSync, rmSync} from "node:fs";
|
|
4
|
-
import {createServer} from "node:
|
|
15
|
+
import {createServer} from "node:http";
|
|
5
16
|
import {homedir} from "node:os";
|
|
6
17
|
import {resolve} from "node:path";
|
|
7
18
|
import {Future} from "atom.io/internal";
|
|
19
|
+
import {fromEntries, toEntries} from "atom.io/json";
|
|
8
20
|
import {ChildSocket} from "atom.io/realtime-server";
|
|
9
|
-
var safety = 0;
|
|
10
21
|
var PORT = process.env.PORT ?? 8080;
|
|
11
22
|
var ORIGIN = `http://localhost:${PORT}`;
|
|
12
23
|
|
|
13
24
|
class FlightDeck {
|
|
14
25
|
options;
|
|
15
|
-
|
|
16
|
-
return `${this.options.repo}/${this.options.app}`;
|
|
17
|
-
}
|
|
26
|
+
safety = 0;
|
|
18
27
|
webhookServer;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
services;
|
|
29
|
+
serviceIdx;
|
|
30
|
+
defaultServicesReadyToUpdate;
|
|
31
|
+
servicesReadyToUpdate;
|
|
32
|
+
servicesShouldRestart;
|
|
33
|
+
logger;
|
|
34
|
+
serviceLoggers;
|
|
35
|
+
servicesLive;
|
|
36
|
+
servicesDead;
|
|
37
|
+
live = new Future(() => {
|
|
22
38
|
});
|
|
23
39
|
dead = new Future(() => {
|
|
24
40
|
});
|
|
41
|
+
restartTimes = [];
|
|
25
42
|
currentServiceDir;
|
|
26
43
|
updateServiceDir;
|
|
27
44
|
backupServiceDir;
|
|
28
45
|
constructor(options) {
|
|
29
46
|
this.options = options;
|
|
30
|
-
const {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
const { secret, flightdeckRootDir = resolve(homedir(), `services`) } = options;
|
|
48
|
+
const servicesEntries = toEntries(options.services);
|
|
49
|
+
this.services = fromEntries(servicesEntries.map(([serviceName]) => [serviceName, null]));
|
|
50
|
+
this.serviceIdx = fromEntries(servicesEntries.map(([serviceName], idx) => [serviceName, idx]));
|
|
51
|
+
this.defaultServicesReadyToUpdate = fromEntries(servicesEntries.map(([serviceName, { waitFor }]) => [
|
|
52
|
+
serviceName,
|
|
53
|
+
!waitFor
|
|
54
|
+
]));
|
|
55
|
+
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate };
|
|
56
|
+
this.servicesShouldRestart = true;
|
|
57
|
+
this.logger = {
|
|
58
|
+
info: (...args) => {
|
|
59
|
+
console.log(`${this.options.packageName}:`, ...args);
|
|
60
|
+
},
|
|
61
|
+
warn: (...args) => {
|
|
62
|
+
console.warn(`${this.options.packageName}:`, ...args);
|
|
63
|
+
},
|
|
64
|
+
error: (...args) => {
|
|
65
|
+
console.error(`${this.options.packageName}:`, ...args);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
this.serviceLoggers = fromEntries(servicesEntries.map(([serviceName]) => [
|
|
69
|
+
serviceName,
|
|
70
|
+
{
|
|
71
|
+
info: (...args) => {
|
|
72
|
+
console.log(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
73
|
+
},
|
|
74
|
+
warn: (...args) => {
|
|
75
|
+
console.warn(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
76
|
+
},
|
|
77
|
+
error: (...args) => {
|
|
78
|
+
console.error(`${this.options.packageName}::${serviceName}:`, ...args);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]));
|
|
82
|
+
this.servicesLive = servicesEntries.map(() => new Future(() => {
|
|
83
|
+
}));
|
|
84
|
+
this.servicesDead = servicesEntries.map(() => new Future(() => {
|
|
85
|
+
}));
|
|
86
|
+
this.live.use(Promise.all(this.servicesLive));
|
|
87
|
+
this.dead.use(Promise.all(this.servicesDead));
|
|
88
|
+
this.currentServiceDir = resolve(flightdeckRootDir, options.packageName, `current`);
|
|
89
|
+
this.backupServiceDir = resolve(flightdeckRootDir, options.packageName, `backup`);
|
|
90
|
+
this.updateServiceDir = resolve(flightdeckRootDir, options.packageName, `update`);
|
|
37
91
|
createServer((req, res) => {
|
|
38
92
|
let data = [];
|
|
39
93
|
req.on(`data`, (chunk) => {
|
|
40
94
|
data.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
|
|
41
95
|
}).on(`end`, () => {
|
|
42
|
-
console.log(req.headers);
|
|
43
96
|
const authHeader = req.headers.authorization;
|
|
44
97
|
try {
|
|
98
|
+
if (typeof req.url === `undefined`)
|
|
99
|
+
throw 400;
|
|
45
100
|
if (authHeader !== `Bearer ${secret}`)
|
|
46
101
|
throw 401;
|
|
47
102
|
const url = new URL(req.url, ORIGIN);
|
|
48
|
-
|
|
103
|
+
this.logger.info(req.method, url.pathname);
|
|
49
104
|
switch (req.method) {
|
|
50
105
|
case `POST`:
|
|
51
106
|
{
|
|
52
|
-
console.log(`received post, url is ${url.pathname}`);
|
|
53
107
|
switch (url.pathname) {
|
|
54
108
|
case `/`:
|
|
55
109
|
{
|
|
56
110
|
res.writeHead(200);
|
|
57
111
|
res.end();
|
|
58
|
-
this.
|
|
59
|
-
if (this.
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
112
|
+
this.getLatestRelease();
|
|
113
|
+
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
114
|
+
this.logger.info(`All services are ready to update!`);
|
|
115
|
+
this.stopAllServices();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
for (const entry of toEntries(this.services)) {
|
|
119
|
+
const [serviceName, service] = entry;
|
|
120
|
+
if (service) {
|
|
121
|
+
if (this.options.services[serviceName].waitFor) {
|
|
122
|
+
service.emit(`updatesReady`);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
this.startService(serviceName);
|
|
126
|
+
}
|
|
64
127
|
}
|
|
65
128
|
}
|
|
66
129
|
break;
|
|
@@ -73,7 +136,7 @@ class FlightDeck {
|
|
|
73
136
|
throw 405;
|
|
74
137
|
}
|
|
75
138
|
} catch (thrown) {
|
|
76
|
-
|
|
139
|
+
this.logger.error(thrown, req.url);
|
|
77
140
|
if (typeof thrown === `number`) {
|
|
78
141
|
res.writeHead(thrown);
|
|
79
142
|
res.end();
|
|
@@ -83,70 +146,91 @@ class FlightDeck {
|
|
|
83
146
|
}
|
|
84
147
|
});
|
|
85
148
|
}).listen(PORT, () => {
|
|
86
|
-
|
|
149
|
+
this.logger.info(`Server started on port ${PORT}`);
|
|
87
150
|
});
|
|
88
|
-
this.
|
|
151
|
+
this.startAllServices();
|
|
89
152
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
153
|
+
startAllServices() {
|
|
154
|
+
this.logger.info(`Starting all services...`);
|
|
155
|
+
for (const [serviceName] of toEntries(this.services)) {
|
|
156
|
+
this.startService(serviceName);
|
|
94
157
|
}
|
|
158
|
+
}
|
|
159
|
+
startService(serviceName) {
|
|
160
|
+
this.logger.info(`Starting service ${this.options.packageName}::${serviceName}, try ${this.safety}/2...`);
|
|
161
|
+
if (this.safety >= 2) {
|
|
162
|
+
throw new Error(`Out of tries...`);
|
|
163
|
+
}
|
|
164
|
+
this.safety++;
|
|
95
165
|
if (!existsSync(this.currentServiceDir)) {
|
|
96
|
-
|
|
97
|
-
this.
|
|
166
|
+
this.logger.info(`Tried to start service but failed: could not find ${this.currentServiceDir}`);
|
|
167
|
+
this.getLatestRelease();
|
|
98
168
|
this.applyUpdate();
|
|
99
|
-
this.startService();
|
|
169
|
+
this.startService(serviceName);
|
|
100
170
|
return;
|
|
101
171
|
}
|
|
102
|
-
const [executable, ...args] = this.options.
|
|
172
|
+
const [executable, ...args] = this.options.services[serviceName].run;
|
|
103
173
|
const program = executable.startsWith(`./`) ? resolve(this.currentServiceDir, executable) : executable;
|
|
104
174
|
const serviceProcess = spawn(program, args, {
|
|
105
175
|
cwd: this.currentServiceDir,
|
|
106
176
|
env: import.meta.env
|
|
107
177
|
});
|
|
108
|
-
this.
|
|
109
|
-
this.
|
|
110
|
-
|
|
178
|
+
this.services[serviceName] = new ChildSocket(serviceProcess, `${this.options.packageName}::${serviceName}`, console);
|
|
179
|
+
this.services[serviceName].onAny((...messages) => {
|
|
180
|
+
this.logger.info(`\uD83D\uDCAC`, ...messages);
|
|
111
181
|
});
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
182
|
+
this.services[serviceName].on(`readyToUpdate`, () => {
|
|
183
|
+
this.serviceLoggers[serviceName].info(`Ready to update.`);
|
|
184
|
+
this.servicesReadyToUpdate[serviceName] = true;
|
|
185
|
+
if (toEntries(this.servicesReadyToUpdate).every(([, isReady]) => isReady)) {
|
|
186
|
+
this.logger.info(`All services are ready to update.`);
|
|
187
|
+
this.stopAllServices();
|
|
188
|
+
}
|
|
114
189
|
});
|
|
115
|
-
this.
|
|
116
|
-
this.
|
|
117
|
-
this.
|
|
190
|
+
this.services[serviceName].on(`alive`, () => {
|
|
191
|
+
this.servicesLive[this.serviceIdx[serviceName]].use(Promise.resolve());
|
|
192
|
+
this.servicesDead[this.serviceIdx[serviceName]] = new Future(() => {
|
|
118
193
|
});
|
|
194
|
+
if (this.dead.done) {
|
|
195
|
+
this.dead = new Future(() => {
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
this.dead.use(Promise.all(this.servicesDead));
|
|
119
199
|
});
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
this.
|
|
200
|
+
this.services[serviceName].process.on(`close`, (exitCode) => {
|
|
201
|
+
this.serviceLoggers[serviceName].info(`Exited with code ${exitCode}`);
|
|
202
|
+
this.services[serviceName] = null;
|
|
203
|
+
if (!this.servicesShouldRestart) {
|
|
204
|
+
this.serviceLoggers[serviceName].info(`Will not be restarted.`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
123
207
|
const updatesAreReady = existsSync(this.updateServiceDir);
|
|
124
208
|
if (updatesAreReady) {
|
|
125
|
-
|
|
209
|
+
this.serviceLoggers[serviceName].info(`Updating before startup...`);
|
|
126
210
|
this.restartTimes = [];
|
|
127
211
|
this.applyUpdate();
|
|
128
|
-
this.startService();
|
|
212
|
+
this.startService(serviceName);
|
|
129
213
|
} else {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
console.log(`Service ${this.serviceName} crashed too many times. Not restarting.`);
|
|
140
|
-
}
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
const fiveMinutesAgo = now - 5 * 60 * 1000;
|
|
216
|
+
this.restartTimes = this.restartTimes.filter((time) => time > fiveMinutesAgo);
|
|
217
|
+
this.restartTimes.push(now);
|
|
218
|
+
if (this.restartTimes.length < 5) {
|
|
219
|
+
this.serviceLoggers[serviceName].info(`Crashed. Restarting...`);
|
|
220
|
+
this.startService(serviceName);
|
|
221
|
+
} else {
|
|
222
|
+
this.serviceLoggers[serviceName].info(`Crashed 5 times in 5 minutes. Not restarting.`);
|
|
141
223
|
}
|
|
142
224
|
}
|
|
143
225
|
});
|
|
226
|
+
this.safety = 0;
|
|
144
227
|
}
|
|
145
228
|
applyUpdate() {
|
|
146
|
-
|
|
229
|
+
this.logger.info(`Applying update...`);
|
|
147
230
|
if (existsSync(this.updateServiceDir)) {
|
|
148
|
-
|
|
149
|
-
|
|
231
|
+
const runningServices = toEntries(this.services).filter(([, service]) => service);
|
|
232
|
+
if (runningServices.length > 0) {
|
|
233
|
+
this.logger.error(`Tried to apply update but failed. The following services are currently running: [${runningServices.map(([serviceName]) => serviceName).join(`, `)}]`);
|
|
150
234
|
return;
|
|
151
235
|
}
|
|
152
236
|
if (existsSync(this.currentServiceDir)) {
|
|
@@ -159,34 +243,89 @@ class FlightDeck {
|
|
|
159
243
|
}
|
|
160
244
|
renameSync(this.updateServiceDir, this.currentServiceDir);
|
|
161
245
|
this.restartTimes = [];
|
|
246
|
+
this.servicesReadyToUpdate = { ...this.defaultServicesReadyToUpdate };
|
|
162
247
|
} else {
|
|
163
|
-
|
|
248
|
+
this.logger.error(`Tried to apply update but failed: could not find update directory ${this.updateServiceDir}`);
|
|
164
249
|
}
|
|
165
250
|
}
|
|
166
|
-
|
|
167
|
-
|
|
251
|
+
getLatestRelease() {
|
|
252
|
+
this.logger.info(`Getting latest release...`);
|
|
168
253
|
try {
|
|
169
|
-
execSync(this.options.
|
|
254
|
+
execSync(this.options.downloadPackageToUpdatesCmd.join(` `));
|
|
170
255
|
} catch (thrown) {
|
|
171
256
|
if (thrown instanceof Error) {
|
|
172
|
-
|
|
257
|
+
this.logger.error(`Failed to get the latest release: ${thrown.message}`);
|
|
173
258
|
}
|
|
174
259
|
return;
|
|
175
260
|
}
|
|
176
261
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
262
|
+
stopAllServices() {
|
|
263
|
+
this.logger.info(`Stopping all services...`);
|
|
264
|
+
for (const [serviceName] of toEntries(this.services)) {
|
|
265
|
+
this.stopService(serviceName);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
stopService(serviceName) {
|
|
269
|
+
if (this.services[serviceName]) {
|
|
270
|
+
this.serviceLoggers[serviceName].info(`Stopping service...`);
|
|
271
|
+
this.services[serviceName].process.kill();
|
|
272
|
+
this.services[serviceName] = null;
|
|
273
|
+
this.servicesDead[this.serviceIdx[serviceName]].use(Promise.resolve());
|
|
274
|
+
this.servicesLive[this.serviceIdx[serviceName]] = new Future(() => {
|
|
184
275
|
});
|
|
276
|
+
if (this.live.done) {
|
|
277
|
+
this.live = new Future(() => {
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
this.live.use(Promise.all(this.servicesLive));
|
|
185
281
|
} else {
|
|
186
|
-
|
|
282
|
+
this.serviceLoggers[serviceName].error(`Tried to stop service, but it wasn't running.`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
shutdown() {
|
|
286
|
+
this.servicesShouldRestart = false;
|
|
287
|
+
this.stopAllServices();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// src/klaxon.lib.ts
|
|
291
|
+
var exports_klaxon_lib = {};
|
|
292
|
+
__export(exports_klaxon_lib, {
|
|
293
|
+
scramble: () => scramble,
|
|
294
|
+
alert: () => alert
|
|
295
|
+
});
|
|
296
|
+
async function alert({
|
|
297
|
+
secret,
|
|
298
|
+
endpoint
|
|
299
|
+
}) {
|
|
300
|
+
const response = await fetch(endpoint, {
|
|
301
|
+
method: `POST`,
|
|
302
|
+
headers: {
|
|
303
|
+
"Content-Type": `application/json`,
|
|
304
|
+
Authorization: `Bearer ${secret}`
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
return response;
|
|
308
|
+
}
|
|
309
|
+
async function scramble({
|
|
310
|
+
packageConfig,
|
|
311
|
+
secretsConfig,
|
|
312
|
+
publishedPackages
|
|
313
|
+
}) {
|
|
314
|
+
const alertResults = [];
|
|
315
|
+
for (const publishedPackage of publishedPackages) {
|
|
316
|
+
if (publishedPackage.name in packageConfig) {
|
|
317
|
+
const name = publishedPackage.name;
|
|
318
|
+
const { endpoint } = packageConfig[name];
|
|
319
|
+
const secret = secretsConfig[name];
|
|
320
|
+
const alertResultPromise = alert({ secret, endpoint }).then((alertResult) => [name, alertResult]);
|
|
321
|
+
alertResults.push(alertResultPromise);
|
|
187
322
|
}
|
|
188
323
|
}
|
|
324
|
+
const alertResultsResolved = await Promise.all(alertResults);
|
|
325
|
+
const scrambleResult = Object.fromEntries(alertResultsResolved);
|
|
326
|
+
return scrambleResult;
|
|
189
327
|
}
|
|
190
328
|
export {
|
|
329
|
+
exports_klaxon_lib as Klaxon,
|
|
191
330
|
FlightDeck
|
|
192
331
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flightdeck",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jeremy Banka",
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
"main": "dist/lib.js",
|
|
18
18
|
"types": "dist/lib.d.ts",
|
|
19
19
|
"bin": {
|
|
20
|
-
"flightdeck": "./dist/bin.js"
|
|
20
|
+
"flightdeck": "./dist/flightdeck.bin.js",
|
|
21
|
+
"klaxon": "./dist/klaxon.bin.js"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"zod": "3.23.8",
|
|
24
|
-
"atom.io": "0.29.
|
|
25
|
-
"comline": "0.1.
|
|
25
|
+
"atom.io": "0.29.2",
|
|
26
|
+
"comline": "0.1.1"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@types/bun": "1.1.9",
|
|
@@ -32,14 +33,15 @@
|
|
|
32
33
|
"tmp": "0.2.3",
|
|
33
34
|
"tsup": "8.2.4",
|
|
34
35
|
"rimraf": "6.0.1",
|
|
35
|
-
"vitest": "2.
|
|
36
|
+
"vitest": "2.1.1"
|
|
36
37
|
},
|
|
37
38
|
"scripts": {
|
|
38
|
-
"build": "rimraf dist && concurrently \"bun:build:*\" && bun
|
|
39
|
+
"build": "rimraf dist && concurrently \"bun:build:*\" && concurrently \"bun:schema:*\"",
|
|
40
|
+
"build:bin:flightdeck": "bun build --outdir dist --target node --external flightdeck --external atom.io --external comline --external zod -- src/flightdeck.bin.ts",
|
|
41
|
+
"build:bin:klaxon": "bun build --outdir dist --target node --external flightdeck --external atom.io --external comline --external zod -- src/klaxon.bin.ts",
|
|
39
42
|
"build:lib": "bun build --outdir dist --target node --external flightdeck --external atom.io --external comline --external zod -- src/lib.ts ",
|
|
40
|
-
"build:bin": "bun build --outdir dist --target node --external flightdeck --external atom.io --external comline --external zod -- src/bin.ts",
|
|
41
43
|
"build:dts": "tsup",
|
|
42
|
-
"schema": "bun ./src/bin.ts --outdir=dist -- schema",
|
|
44
|
+
"schema:flightdeck": "bun ./src/flightdeck.bin.ts --outdir=dist -- schema",
|
|
43
45
|
"lint:biome": "biome check -- .",
|
|
44
46
|
"lint:eslint": "eslint --flag unstable_ts_config -- .",
|
|
45
47
|
"lint:types": "tsc --noEmit",
|
|
@@ -4,56 +4,52 @@ import * as path from "node:path"
|
|
|
4
4
|
|
|
5
5
|
import type { OptionsGroup } from "comline"
|
|
6
6
|
import { cli, optional, parseArrayOption } from "comline"
|
|
7
|
-
import type { FlightDeckOptions } from "flightdeck"
|
|
8
|
-
import { FlightDeck } from "flightdeck"
|
|
9
7
|
import { z } from "zod"
|
|
10
8
|
|
|
9
|
+
import type { FlightDeckOptions } from "~/packages/flightdeck/src/flightdeck.lib"
|
|
10
|
+
import { FlightDeck } from "~/packages/flightdeck/src/flightdeck.lib"
|
|
11
|
+
|
|
11
12
|
const FLIGHTDECK_MANUAL = {
|
|
12
13
|
optionsSchema: z.object({
|
|
13
14
|
secret: z.string(),
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
packageName: z.string(),
|
|
16
|
+
services: z.record(
|
|
17
|
+
z.object({ run: z.array(z.string()), waitFor: z.boolean() }),
|
|
18
|
+
),
|
|
19
|
+
flightdeckRootDir: z.string(),
|
|
20
|
+
downloadPackageToUpdatesCmd: z.array(z.string()),
|
|
19
21
|
}),
|
|
20
22
|
options: {
|
|
21
23
|
secret: {
|
|
22
|
-
flag: `
|
|
24
|
+
flag: `x`,
|
|
23
25
|
required: true,
|
|
24
26
|
description: `Secret used to authenticate with the service.`,
|
|
25
27
|
example: `--secret=\"secret\"`,
|
|
26
28
|
},
|
|
27
|
-
|
|
28
|
-
flag: `
|
|
29
|
-
required: true,
|
|
30
|
-
description: `Name of the repository.`,
|
|
31
|
-
example: `--repo=\"sample/repo\"`,
|
|
32
|
-
},
|
|
33
|
-
app: {
|
|
34
|
-
flag: `a`,
|
|
29
|
+
packageName: {
|
|
30
|
+
flag: `p`,
|
|
35
31
|
required: true,
|
|
36
|
-
description: `Name of the
|
|
37
|
-
example: `--
|
|
32
|
+
description: `Name of the package.`,
|
|
33
|
+
example: `--packageName=\"my-app\"`,
|
|
38
34
|
},
|
|
39
|
-
|
|
40
|
-
flag: `
|
|
35
|
+
services: {
|
|
36
|
+
flag: `s`,
|
|
41
37
|
required: true,
|
|
42
|
-
description: `
|
|
43
|
-
example: `--
|
|
44
|
-
parse:
|
|
38
|
+
description: `Map of service names to executables.`,
|
|
39
|
+
example: `--services="{\\"frontend\\":{\\"run\\":[\\"./app\\"],\\"waitFor\\":false},\\"backend\\":{\\"run\\":[\\"./backend\\"],\\"waitFor\\":true}}"`,
|
|
40
|
+
parse: JSON.parse,
|
|
45
41
|
},
|
|
46
|
-
|
|
42
|
+
flightdeckRootDir: {
|
|
47
43
|
flag: `d`,
|
|
48
44
|
required: true,
|
|
49
45
|
description: `Directory where the service is stored.`,
|
|
50
|
-
example: `--
|
|
46
|
+
example: `--flightdeckRootDir=\"./services/sample/repo/my-app/current\"`,
|
|
51
47
|
},
|
|
52
|
-
|
|
48
|
+
downloadPackageToUpdatesCmd: {
|
|
53
49
|
flag: `u`,
|
|
54
50
|
required: true,
|
|
55
51
|
description: `Command to update the service.`,
|
|
56
|
-
example: `--
|
|
52
|
+
example: `--downloadPackageToUpdatesCmd=\"./app\"`,
|
|
57
53
|
parse: parseArrayOption,
|
|
58
54
|
},
|
|
59
55
|
},
|
|
@@ -106,7 +102,7 @@ switch (inputs.case) {
|
|
|
106
102
|
default: {
|
|
107
103
|
const flightDeck = new FlightDeck(inputs.opts)
|
|
108
104
|
process.on(`close`, async () => {
|
|
109
|
-
flightDeck.
|
|
105
|
+
flightDeck.stopAllServices()
|
|
110
106
|
await flightDeck.dead
|
|
111
107
|
})
|
|
112
108
|
}
|