flightdeck 0.0.2 → 0.0.3

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