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/lib.d.ts CHANGED
@@ -1,36 +1,113 @@
1
- import { Http2Server } from 'node:http2';
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
- repo: string;
8
- app: string;
9
- runCmd: string[];
10
- updateCmd: string[];
11
- serviceDir?: string | undefined;
12
- };
13
- declare class FlightDeck {
14
- readonly options: FlightDeckOptions;
15
- get serviceName(): string;
16
- protected webhookServer: Http2Server;
17
- protected service: ChildSocket<{
18
- updatesReady: [];
19
- }, {
20
- readyToUpdate: [];
21
- alive: [];
22
- }> | null;
23
- protected restartTimes: number[];
24
- alive: Future<unknown>;
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 startService(): void;
51
+ constructor(options: FlightDeckOptions<S>);
52
+ protected startAllServices(): void;
53
+ protected startService(serviceName: S): void;
31
54
  protected applyUpdate(): void;
32
- protected fetchLatestRelease(): void;
33
- stopService(): void;
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
- // src/flightdeck.ts
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:http2";
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
- get serviceName() {
16
- return `${this.options.repo}/${this.options.app}`;
17
- }
26
+ safety = 0;
18
27
  webhookServer;
19
- service = null;
20
- restartTimes = [];
21
- alive = new Future(() => {
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
- secret,
32
- serviceDir = resolve(homedir(), `services`, `sample/repo`, `my-app`, `current`)
33
- } = options;
34
- this.currentServiceDir = resolve(serviceDir, `current`);
35
- this.backupServiceDir = resolve(serviceDir, `backup`);
36
- this.updateServiceDir = resolve(serviceDir, `update`);
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
- console.log(req.method, url.pathname);
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.fetchLatestRelease();
59
- if (this.service) {
60
- this.service.emit(`updatesReady`);
61
- } else {
62
- this.applyUpdate();
63
- this.startService();
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
- console.error(thrown, req.url);
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
- console.log(`Server started on port ${PORT}`);
149
+ this.logger.info(`Server started on port ${PORT}`);
87
150
  });
88
- this.startService();
151
+ this.startAllServices();
89
152
  }
90
- startService() {
91
- safety++;
92
- if (safety > 10) {
93
- throw new Error(`safety exceeded`);
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
- console.log(`Tried to start service but failed: Service ${this.serviceName} is not yet installed.`);
97
- this.fetchLatestRelease();
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.runCmd;
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.service = new ChildSocket(serviceProcess, this.serviceName, console);
109
- this.service.onAny((...messages) => {
110
- console.log(`\uD83D\uDEF0 `, ...messages);
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.service.on(`readyToUpdate`, () => {
113
- this.stopService();
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.service.on(`alive`, () => {
116
- this.alive.use(Promise.resolve());
117
- this.dead = new Future(() => {
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.service.process.on(`close`, (exitCode) => {
121
- console.log(`Service ${this.serviceName} exited with code ${exitCode}`);
122
- this.service = null;
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
- console.log(`Updates are ready; applying and restarting...`);
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
- if (exitCode !== 0) {
131
- const now = Date.now();
132
- const fiveMinutesAgo = now - 5 * 60 * 1000;
133
- this.restartTimes = this.restartTimes.filter((time) => time > fiveMinutesAgo);
134
- this.restartTimes.push(now);
135
- if (this.restartTimes.length < 5) {
136
- console.log(`Service ${this.serviceName} crashed. Restarting...`);
137
- this.startService();
138
- } else {
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
- console.log(`Installing latest version of service ${this.serviceName}...`);
229
+ this.logger.info(`Applying update...`);
147
230
  if (existsSync(this.updateServiceDir)) {
148
- if (this.service) {
149
- console.log(`Tried to apply update but failed: Service ${this.serviceName} is currently running.`);
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
- console.log(`Service ${this.serviceName} is already up to date.`);
248
+ this.logger.error(`Tried to apply update but failed: could not find update directory ${this.updateServiceDir}`);
164
249
  }
165
250
  }
166
- fetchLatestRelease() {
167
- console.log(`Downloading latest version of service ${this.serviceName}...`);
251
+ getLatestRelease() {
252
+ this.logger.info(`Getting latest release...`);
168
253
  try {
169
- execSync(this.options.updateCmd.join(` `));
254
+ execSync(this.options.downloadPackageToUpdatesCmd.join(` `));
170
255
  } catch (thrown) {
171
256
  if (thrown instanceof Error) {
172
- console.error(`Failed to fetch the latest release: ${thrown.message}`);
257
+ this.logger.error(`Failed to get the latest release: ${thrown.message}`);
173
258
  }
174
259
  return;
175
260
  }
176
261
  }
177
- stopService() {
178
- if (this.service) {
179
- console.log(`Stopping service ${this.serviceName}...`);
180
- this.service.process.kill();
181
- this.service = null;
182
- this.dead.use(Promise.resolve());
183
- this.alive = new Future(() => {
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
- console.error(`Failed to stop service ${this.serviceName}: Service is not running.`);
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.2",
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.1",
25
- "comline": "0.1.0"
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.0.5"
36
+ "vitest": "2.1.1"
36
37
  },
37
38
  "scripts": {
38
- "build": "rimraf dist && concurrently \"bun:build:*\" && bun run schema",
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
- repo: z.string(),
15
- app: z.string(),
16
- runCmd: z.array(z.string()),
17
- serviceDir: z.string(),
18
- updateCmd: z.array(z.string()),
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: `s`,
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
- repo: {
28
- flag: `r`,
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 application.`,
37
- example: `--app=\"my-app\"`,
32
+ description: `Name of the package.`,
33
+ example: `--packageName=\"my-app\"`,
38
34
  },
39
- runCmd: {
40
- flag: `r`,
35
+ services: {
36
+ flag: `s`,
41
37
  required: true,
42
- description: `Command to run the application.`,
43
- example: `--runCmd=\"./app\"`,
44
- parse: parseArrayOption,
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
- serviceDir: {
42
+ flightdeckRootDir: {
47
43
  flag: `d`,
48
44
  required: true,
49
45
  description: `Directory where the service is stored.`,
50
- example: `--serviceDir=\"./services/sample/repo/my-app/current\"`,
46
+ example: `--flightdeckRootDir=\"./services/sample/repo/my-app/current\"`,
51
47
  },
52
- updateCmd: {
48
+ downloadPackageToUpdatesCmd: {
53
49
  flag: `u`,
54
50
  required: true,
55
51
  description: `Command to update the service.`,
56
- example: `--updateCmd=\"./app\"`,
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.stopService()
105
+ flightDeck.stopAllServices()
110
106
  await flightDeck.dead
111
107
  })
112
108
  }