@molecule/api-code-sandbox-flyio 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,519 @@
1
+ /**
2
+ * Fly.io Machines sandbox provider configuration and wire types.
3
+ *
4
+ * Wire types mirror the Fly Machines OpenAPI specification
5
+ * (https://docs.machines.dev/openapi.json, servers: `https://api.machines.dev/v1`).
6
+ * Only the fields this provider actually reads or writes are modelled — the real
7
+ * schemas are much larger, and modelling fields we never use would rot silently.
8
+ *
9
+ * @module
10
+ */
11
+ /**
12
+ * Configuration for the Fly.io Machines sandbox provider.
13
+ *
14
+ * Every option is also readable from an environment variable (see
15
+ * {@link ProcessEnv}); explicit config always wins.
16
+ */
17
+ export interface FlyioConfig {
18
+ /**
19
+ * Fly API token (sent as `Authorization: Bearer <token>`). Falls back to
20
+ * `FLY_API_TOKEN`, then `FLY_ACCESS_TOKEN`. Required — the provider throws a
21
+ * named error on the first API call if none is resolvable.
22
+ */
23
+ apiToken?: string;
24
+ /**
25
+ * Machines API base URL, INCLUDING the `/v1` path segment. Defaults to
26
+ * `FLY_API_HOSTNAME` (with `/v1` appended when the value has no path) or
27
+ * `https://api.machines.dev/v1`. From inside a Fly private network the
28
+ * documented internal endpoint is `http://_api.internal:4280/v1`.
29
+ */
30
+ apiUrl?: string;
31
+ /**
32
+ * Fly organization slug that owns the sandbox apps. Falls back to
33
+ * `FLY_ORG_SLUG`, then `personal`. Required for app creation and for
34
+ * {@link FlyioConfig.appPerProject} listing.
35
+ */
36
+ orgSlug?: string;
37
+ /**
38
+ * Shared Fly app that holds every sandbox Machine. Only used when
39
+ * `appPerProject` is `false`. Falls back to `FLY_SANDBOX_APP`.
40
+ *
41
+ * A shared app puts every tenant on ONE 6PN private network, where each
42
+ * sandbox can reach every other sandbox's dev-server ports by private IPv6.
43
+ * That is the same cross-tenant exposure the Docker bond's `bridge` network
44
+ * has, so this mode is REFUSED in production.
45
+ */
46
+ appName?: string;
47
+ /**
48
+ * Name prefix for the per-project Fly app when `appPerProject` is `true`
49
+ * (default). The app is `<appPrefix>-<sanitized projectId>`. Falls back to
50
+ * `FLY_SANDBOX_APP_PREFIX`, then `mol-sandbox`.
51
+ */
52
+ appPrefix?: string;
53
+ /**
54
+ * Create one Fly app — on its own custom 6PN private network — per project,
55
+ * so tenants are network-isolated from each other. Defaults to `true`.
56
+ * Set `false` (or `FLY_SANDBOX_APP_PER_PROJECT=false`) only for a
57
+ * single-tenant deployment; it is refused in production.
58
+ */
59
+ appPerProject?: boolean;
60
+ /**
61
+ * Custom 6PN network name for a per-project app. Defaults to the app name, so
62
+ * every project lands on its own private network. Ignored when
63
+ * `appPerProject` is `false`. A Fly app's network CANNOT be changed after
64
+ * creation.
65
+ */
66
+ network?: string;
67
+ /** Fly region for Machines and volumes (e.g. `iad`). Falls back to `FLY_REGION`, then `iad`. */
68
+ region?: string;
69
+ /**
70
+ * OCI image every sandbox Machine runs. Must be pullable by Fly — a tag in
71
+ * the org's `registry.fly.io` repository or a public registry. A LOCAL
72
+ * `molecule-sandbox:latest` is not reachable by Fly; push it first. Falls
73
+ * back to `FLY_SANDBOX_IMAGE`, then `registry.fly.io/molecule-sandbox:latest`.
74
+ */
75
+ baseImage?: string;
76
+ /** Default vCPU count (`guest.cpus`). Defaults to 1. */
77
+ defaultCpu?: number;
78
+ /** Default `guest.cpu_kind` — `shared` or `performance`. Defaults to `shared`. */
79
+ defaultCpuKind?: string;
80
+ /** Default memory in MB (`guest.memory_mb`). Defaults to 1024. */
81
+ defaultMemoryMB?: number;
82
+ /**
83
+ * Size in GB of the volume created for a sandbox's `/workspace` when
84
+ * `SandboxConfig.volumeName` is set. Fly volumes are sized in whole GB, so the
85
+ * core's `resources.diskMB` is rounded UP to the next GB. Defaults to 10.
86
+ */
87
+ defaultVolumeGB?: number;
88
+ /** Internal port the preview service forwards to (the Vite dev server). Defaults to 5173. */
89
+ previewPort?: number;
90
+ /**
91
+ * Preview URL template. Placeholders `{app}`, `{machineId}` and `{port}` are
92
+ * all replaced globally. Defaults to `https://{app}.fly.dev`.
93
+ *
94
+ * For a control plane that runs on the SAME Fly 6PN, the private form
95
+ * `http://{machineId}.vm.{app}.internal:{port}` reaches the Machine with no
96
+ * public exposure at all — but it does NOT work across custom 6PNs, which is
97
+ * exactly what `appPerProject` creates. See the module `@remarks`.
98
+ */
99
+ previewUrlTemplate?: string;
100
+ /**
101
+ * Attach a public Fly Proxy service (`80` → http, `443` → tls+http) forwarding
102
+ * to {@link FlyioConfig.previewPort}. Defaults to `true`. Set `false` for a
103
+ * fully private sandbox reached only over 6PN/Flycast.
104
+ */
105
+ publicService?: boolean;
106
+ /**
107
+ * Fly Proxy idle behaviour for the preview service. `suspend` (the default)
108
+ * is the scale-to-zero mapping this bond exists for: the proxy suspends an
109
+ * idle Machine and resumes it from its memory snapshot on the next request.
110
+ * `stop` is a full stop (cold boot on wake); `off` never idles the Machine.
111
+ */
112
+ autostop?: 'off' | 'stop' | 'suspend';
113
+ /**
114
+ * Allocate a shared Anycast IPv4 for a newly-created app so `<app>.fly.dev`
115
+ * serves traffic. Defaults to `false` (opt-in). See the module `@remarks` —
116
+ * the accepted `type` values for the IP-assignment endpoint are NOT enumerated
117
+ * in Fly's OpenAPI specification.
118
+ */
119
+ assignSharedIpv4?: boolean;
120
+ /** IP type requested when {@link FlyioConfig.assignSharedIpv4} is on. Defaults to `shared_v4`. */
121
+ ipAssignmentType?: string;
122
+ /** Prefix for the Machine metadata keys this provider owns. Defaults to `molecule-sandbox`. */
123
+ metadataPrefix?: string;
124
+ /** Timeout for a single Machines API request, in ms. Defaults to 30000. */
125
+ requestTimeoutMs?: number;
126
+ /**
127
+ * How long `start()`/`wake()` block waiting for the Machine to actually reach
128
+ * `started`, in seconds. Clamped to Fly's own ceiling — `GET .../wait` "will
129
+ * block for up to 60 seconds". Defaults to 60.
130
+ *
131
+ * Without this wait, `start()` resolves the moment Fly ACCEPTS the request,
132
+ * and the caller's very next `exec` hits a Machine that is not running yet.
133
+ */
134
+ startTimeoutSeconds?: number;
135
+ /**
136
+ * Ports a sandbox Machine may open outbound connections on. Setting this makes
137
+ * the provider apply a Fly **network policy** to every app it provisions;
138
+ * leaving it unset applies no policy at all, and `verifyEgress()` will then
139
+ * observe (correctly) that egress is `open`. Falls back to
140
+ * `FLY_SANDBOX_EGRESS_ALLOWED_PORTS` (`tcp:3128,udp:53`).
141
+ *
142
+ * Read {@link https://fly.io/docs/machines/guides-examples/network-policies/}
143
+ * before choosing a value, because the mechanism is narrower than it looks:
144
+ * rules match protocol and port ONLY — no host, no CIDR, no ranges — so this
145
+ * can never be a host allowlist. `tcp:443` lets a sandbox reach EVERY host on
146
+ * the internet that listens on 443. To get a host allowlist, allow only the
147
+ * port of an egress proxy you control and route sandbox traffic through it.
148
+ *
149
+ * An empty array is rejected rather than treated as "deny all": Fly documents
150
+ * the deny default as a consequence of an `allow` rule existing, and says
151
+ * nothing about a rule with no ports.
152
+ */
153
+ egressAllowedPorts?: FlyNetworkPolicyPort[];
154
+ /** Name of the egress policy this provider owns on each app. Defaults to `molecule-sandbox-egress`. */
155
+ egressPolicyName?: string;
156
+ /**
157
+ * Apps every sandbox must be able to reach ACROSS its per-project 6PN — the
158
+ * tenant Postgres cluster and the control plane's egress proxy. For each one,
159
+ * this provider allocates a **Flycast** private address into the project's own
160
+ * network when the project's app is created, and releases it on `destroy()`.
161
+ * Falls back to `FLY_SANDBOX_PRIVATE_SERVICES` (`<app>:<port>` pairs).
162
+ *
163
+ * Without this, a project with a database DOES NOT WORK on Fly: per-project
164
+ * 6PNs are what stop tenants reaching each other, and the same isolation stops
165
+ * a sandbox reaching the database it is supposed to use. Flycast is the only
166
+ * mechanism Fly documents that grants ONE directed edge without exposing
167
+ * anything publicly — see the `flycast.ts` module description.
168
+ *
169
+ * The declared PORT is load-bearing twice: it is unioned into
170
+ * {@link FlyioConfig.egressAllowedPorts} so the network policy cannot drop the
171
+ * connection, and every `.flycast` URL in a sandbox's environment is checked
172
+ * against it, so a `DATABASE_URL` naming an app nobody declared FAILS the boot
173
+ * instead of timing out inside the user's project.
174
+ *
175
+ * Two things this cannot do for you: the target app needs an `[http_service]`
176
+ * or `[services]` section (Flycast routes through Fly Proxy), and it must bind
177
+ * `0.0.0.0` rather than `fly-local-6pn`
178
+ * (https://fly.io/docs/networking/flycast/).
179
+ *
180
+ * Ignored when `appPerProject` is `false` — a shared app sits on the org's
181
+ * default 6PN, where `<app>.internal` already resolves.
182
+ */
183
+ privateServices?: FlyPrivateService[];
184
+ /**
185
+ * `type` sent to `POST /apps/{app}/ip_assignments` when allocating a Flycast
186
+ * address. Defaults to `private_v6`, the value flyctl passes for
187
+ * `fly ips allocate-v6 --private`. The field has no enum in Fly's OpenAPI
188
+ * specification, so treat the literal as UNVERIFIED and use this to override
189
+ * it if Fly renames it.
190
+ */
191
+ privateIpAssignmentType?: string;
192
+ /**
193
+ * Literal `ip:port` targets `verifyEgress()` attempts raw TCP connections to.
194
+ * IPv6 literals must be bracketed. Falls back to `SANDBOX_EGRESS_PROBE_TARGETS`
195
+ * (the same variable the Docker bond reads), then to one IPv4 and one IPv6
196
+ * anycast resolver on 443.
197
+ */
198
+ egressProbeTargets?: string[];
199
+ /** Per-connection timeout for the egress probe, in ms. Falls back to `SANDBOX_EGRESS_PROBE_TIMEOUT_MS`, then 3000. */
200
+ egressProbeTimeoutMs?: number;
201
+ /**
202
+ * Image the throwaway `verifyEgress()` probe Machine runs. Must contain `node`
203
+ * and `sleep`. Defaults to the configured sandbox base image, so the probe
204
+ * observes egress from the same image real sandboxes run.
205
+ */
206
+ egressProbeImage?: string;
207
+ /**
208
+ * Bucket holding sandbox templates. Falls back to `SANDBOX_TEMPLATE_BUCKET`,
209
+ * then `BUCKET_NAME` (which `fly storage create` sets).
210
+ *
211
+ * Templates are the warm-start capability: Fly cannot commit a running Machine
212
+ * to an image, so a template is a tar archive in S3-compatible object storage.
213
+ * Without a bucket (and both credentials) the template methods throw an
214
+ * actionable error naming these settings, and `SandboxConfig.templateId` fails
215
+ * rather than silently booting the base image.
216
+ */
217
+ templateBucket?: string;
218
+ /**
219
+ * S3 endpoint for {@link FlyioConfig.templateBucket}, e.g. Tigris's
220
+ * `https://t3.storage.dev`. Falls back to `SANDBOX_TEMPLATE_ENDPOINT`, then
221
+ * `AWS_ENDPOINT_URL_S3`. Omit for AWS S3 itself.
222
+ */
223
+ templateEndpoint?: string;
224
+ /**
225
+ * Region used to sign template-store requests. Falls back to
226
+ * `SANDBOX_TEMPLATE_REGION`, then `AWS_REGION`, then `auto` — which Tigris and
227
+ * most S3-compatible stores accept, and which the AWS SDK still requires in
228
+ * order to build a signature.
229
+ */
230
+ templateRegion?: string;
231
+ /** Access key for the template store. Falls back to `SANDBOX_TEMPLATE_ACCESS_KEY_ID`, then `AWS_ACCESS_KEY_ID`. */
232
+ templateAccessKeyId?: string;
233
+ /** Secret key for the template store. Falls back to `SANDBOX_TEMPLATE_SECRET_ACCESS_KEY`, then `AWS_SECRET_ACCESS_KEY`. */
234
+ templateSecretAccessKey?: string;
235
+ /** Session token for temporary template-store credentials. Falls back to `SANDBOX_TEMPLATE_SESSION_TOKEN`, then `AWS_SESSION_TOKEN`. */
236
+ templateSessionToken?: string;
237
+ /**
238
+ * Key prefix every template object lives under. Falls back to
239
+ * `SANDBOX_TEMPLATE_PREFIX`, then `molecule-sandbox-templates`. Give the
240
+ * templates their own prefix (or their own bucket): `removeTemplate` deletes
241
+ * every key under a template's prefix.
242
+ */
243
+ templatePrefix?: string;
244
+ /**
245
+ * Address the template bucket path-style (`https://endpoint/bucket/key`)
246
+ * instead of virtual-host style. Falls back to
247
+ * `SANDBOX_TEMPLATE_FORCE_PATH_STYLE=true`. Needed by stores that do not serve
248
+ * `<bucket>.<endpoint>`; Tigris and AWS S3 do not need it.
249
+ */
250
+ templateForcePathStyle?: boolean;
251
+ /**
252
+ * Lifetime of the presigned capture/restore URL handed to a sandbox, in
253
+ * seconds. Defaults to 3600 and is clamped to AWS's documented 7-day ceiling
254
+ * for a SigV4 presigned URL. It only has to outlast the START of the transfer:
255
+ * S3 "checks the expiration date and time of a signed URL at the time of the
256
+ * HTTP request", so a download already in progress is not cut off.
257
+ */
258
+ templateUrlExpirySeconds?: number;
259
+ /**
260
+ * Wall-clock budget for one capture or restore transfer, in ms. Defaults to
261
+ * 900000 (15 min). Also bounds how long a stale restore lease keeps a template
262
+ * pinned against eviction.
263
+ */
264
+ templateTransferTimeoutMs?: number;
265
+ /**
266
+ * Largest template archive this provider will store, in bytes. Defaults to
267
+ * and is clamped by S3's 5 GB single-`PUT` ceiling, because the sandbox
268
+ * uploads with exactly one presigned `PUT`. The capture refuses before
269
+ * spending the bandwidth.
270
+ */
271
+ templateMaxArchiveBytes?: number;
272
+ }
273
+ /**
274
+ * Environment variables the Fly.io sandbox provider reads. Every one is
275
+ * overridden by the matching {@link FlyioConfig} field.
276
+ */
277
+ export interface ProcessEnv {
278
+ /** Fly API token. Also accepted as `FLY_ACCESS_TOKEN`. */
279
+ FLY_API_TOKEN?: string;
280
+ /** Fly API token (flyctl's variable name). Used when `FLY_API_TOKEN` is unset. */
281
+ FLY_ACCESS_TOKEN?: string;
282
+ /** Machines API base URL. `/v1` is appended when the value carries no path. */
283
+ FLY_API_HOSTNAME?: string;
284
+ /** Fly organization slug owning the sandbox apps (default `personal`). */
285
+ FLY_ORG_SLUG?: string;
286
+ /** Shared Fly app name, used only when `FLY_SANDBOX_APP_PER_PROJECT=false`. */
287
+ FLY_SANDBOX_APP?: string;
288
+ /** Per-project Fly app name prefix (default `mol-sandbox`). */
289
+ FLY_SANDBOX_APP_PREFIX?: string;
290
+ /** `false` disables per-project apps (refused in production). */
291
+ FLY_SANDBOX_APP_PER_PROJECT?: string;
292
+ /** Fly region for Machines and volumes (default `iad`). */
293
+ FLY_REGION?: string;
294
+ /** OCI image for sandbox Machines. */
295
+ FLY_SANDBOX_IMAGE?: string;
296
+ /**
297
+ * Comma-separated `protocol:port` pairs a sandbox may open outbound
298
+ * connections on (e.g. `tcp:3128,udp:53`). Set it to make this provider apply
299
+ * a Fly network policy to every sandbox app; unset means no policy.
300
+ */
301
+ FLY_SANDBOX_EGRESS_ALLOWED_PORTS?: string;
302
+ /**
303
+ * Comma-separated `<fly-app>:<port>` pairs every sandbox must reach across its
304
+ * per-project 6PN, e.g. `molecule-pg-tenant:5432,molecule-api:3129`. Each one
305
+ * gets a Flycast private address allocated into the project's network at app
306
+ * creation and released on destroy. Overridden by `config.privateServices`.
307
+ */
308
+ FLY_SANDBOX_PRIVATE_SERVICES?: string;
309
+ /**
310
+ * Comma-separated literal `ip:port` targets the egress probe attempts. Shared
311
+ * with the Docker bond so a provider swap keeps the same configuration.
312
+ */
313
+ SANDBOX_EGRESS_PROBE_TARGETS?: string;
314
+ /** Per-connection timeout for the egress probe, in ms (default 3000). */
315
+ SANDBOX_EGRESS_PROBE_TIMEOUT_MS?: string;
316
+ /** Bucket holding sandbox templates. Overridden by `config.templateBucket`. */
317
+ SANDBOX_TEMPLATE_BUCKET?: string;
318
+ /** Bucket name as exported by `fly storage create`. Used when `SANDBOX_TEMPLATE_BUCKET` is unset. */
319
+ BUCKET_NAME?: string;
320
+ /** S3 endpoint for the template bucket. Overridden by `config.templateEndpoint`. */
321
+ SANDBOX_TEMPLATE_ENDPOINT?: string;
322
+ /** S3 endpoint as exported by `fly storage create`. Used when `SANDBOX_TEMPLATE_ENDPOINT` is unset. */
323
+ AWS_ENDPOINT_URL_S3?: string;
324
+ /** Signing region for the template store (default `auto`). Overridden by `config.templateRegion`. */
325
+ SANDBOX_TEMPLATE_REGION?: string;
326
+ /** Signing region, standard AWS variable. Used when `SANDBOX_TEMPLATE_REGION` is unset. */
327
+ AWS_REGION?: string;
328
+ /** Access key for the template store. Overridden by `config.templateAccessKeyId`. */
329
+ SANDBOX_TEMPLATE_ACCESS_KEY_ID?: string;
330
+ /** Access key, standard AWS variable (also what `fly storage create` sets). */
331
+ AWS_ACCESS_KEY_ID?: string;
332
+ /** Secret key for the template store. Overridden by `config.templateSecretAccessKey`. */
333
+ SANDBOX_TEMPLATE_SECRET_ACCESS_KEY?: string;
334
+ /** Secret key, standard AWS variable (also what `fly storage create` sets). */
335
+ AWS_SECRET_ACCESS_KEY?: string;
336
+ /** Session token for the template store. Overridden by `config.templateSessionToken`. */
337
+ SANDBOX_TEMPLATE_SESSION_TOKEN?: string;
338
+ /** Session token, standard AWS variable. */
339
+ AWS_SESSION_TOKEN?: string;
340
+ /** Key prefix for template objects (default `molecule-sandbox-templates`). */
341
+ SANDBOX_TEMPLATE_PREFIX?: string;
342
+ /** `true` addresses the template bucket path-style rather than virtual-host style. */
343
+ SANDBOX_TEMPLATE_FORCE_PATH_STYLE?: string;
344
+ }
345
+ /**
346
+ * One app made reachable from every sandbox's per-project 6PN through a Flycast
347
+ * private address.
348
+ *
349
+ * See the `flycast.ts` module description for why this exists at all: apps on
350
+ * separate 6PNs "can never communicate unless explicitly configured to do so",
351
+ * and this declaration IS that explicit configuration.
352
+ */
353
+ export interface FlyPrivateService {
354
+ /**
355
+ * The Fly app to allocate a Flycast address ON. The sandbox reaches it at
356
+ * `<app>.flycast`. It must already exist, must carry a services block, and
357
+ * must be in the same organization.
358
+ */
359
+ app: string;
360
+ /**
361
+ * The TCP port a sandbox dials it at. Unioned into the egress network policy,
362
+ * and checked against every `.flycast` URL in a sandbox's environment.
363
+ */
364
+ port: number;
365
+ }
366
+ /**
367
+ * An IP assignment, as returned by `POST`/`GET /v1/apps/{app}/ip_assignments`
368
+ * (`IPAssignment` in https://docs.machines.dev/openapi.json).
369
+ *
370
+ * Note what is NOT here: the assignment's **network**. Fly's own schema is
371
+ * `{created_at, ip, region, service_name, shared}`, so a listing cannot tell you
372
+ * which 6PN a private address serves — which is why this provider records the
373
+ * addresses it allocated in the sandbox Machine's metadata instead of
374
+ * rediscovering them.
375
+ */
376
+ export interface FlyIpAssignment {
377
+ /** The allocated address. Required for a later `DELETE .../ip_assignments/{ip}`. */
378
+ ip?: string;
379
+ /** Region the address was allocated in, when Fly reports one. */
380
+ region?: string;
381
+ /** Service the address is bound to, when the assignment names one. */
382
+ service_name?: string;
383
+ /** Whether the address is shared (Anycast v4) rather than dedicated. */
384
+ shared?: boolean;
385
+ }
386
+ /**
387
+ * One protocol/port pair in a Fly network-policy rule.
388
+ *
389
+ * Fly matches on protocol and port only — there is no host, IP, CIDR or
390
+ * port-range matching. See
391
+ * https://fly.io/docs/machines/guides-examples/network-policies/.
392
+ */
393
+ export interface FlyNetworkPolicyPort {
394
+ /** `tcp` or `udp` — the only documented values. */
395
+ protocol: 'tcp' | 'udp';
396
+ /** A single port. Ranges are not supported. */
397
+ port: number;
398
+ }
399
+ /**
400
+ * One rule in a Fly network policy. `allow` is the only documented action:
401
+ * "Once you create a rule for a given direction, the default for that direction
402
+ * becomes drop."
403
+ */
404
+ export interface FlyNetworkPolicyRule {
405
+ /** Only `allow` is supported by Fly. */
406
+ action: 'allow';
407
+ /** Traffic direction the rule (and its implied deny default) applies to. */
408
+ direction: 'ingress' | 'egress';
409
+ /** Ports this rule permits. */
410
+ ports: FlyNetworkPolicyPort[];
411
+ }
412
+ /**
413
+ * Which Machines in the app a policy applies to. Documented criteria combine
414
+ * with AND, so this provider uses `{ all: true }` alone.
415
+ */
416
+ export interface FlyNetworkPolicySelector {
417
+ /** Match every Machine in the app. */
418
+ all?: boolean;
419
+ /** Match specific Machine ids. */
420
+ machines?: Array<{
421
+ id: string;
422
+ }>;
423
+ /** Match Machines carrying these metadata keys. */
424
+ metadata?: Record<string, string>;
425
+ }
426
+ /**
427
+ * A Fly network policy, as accepted by
428
+ * `POST /v1/apps/{app}/network_policies`. The endpoint is documented in Fly's
429
+ * guide and announcement but is NOT in its OpenAPI specification, so only the
430
+ * fields those two documents show are modelled.
431
+ */
432
+ export interface FlyNetworkPolicy {
433
+ /** Present to UPDATE an existing policy; omitted to create one. */
434
+ id?: string;
435
+ /** Policy name, unique per app in practice. */
436
+ name: string;
437
+ /** Machines the policy applies to. */
438
+ selector: FlyNetworkPolicySelector;
439
+ /** The allow rules. */
440
+ rules: FlyNetworkPolicyRule[];
441
+ }
442
+ /**
443
+ * A Fly Machine state, as documented at https://fly.io/docs/machines/machine-states/.
444
+ * Modelled as a union of the documented values plus `string` so an unrecognized
445
+ * future state is carried through rather than crashing the mapper.
446
+ */
447
+ export type FlyMachineState = 'created' | 'creating' | 'starting' | 'started' | 'stopping' | 'stopped' | 'suspending' | 'suspended' | 'restarting' | 'updating' | 'replacing' | 'replaced' | 'migrated' | 'destroying' | 'destroyed' | 'failed' | 'launch_failed' | (string & {});
448
+ /** A Fly Machine, as returned by the Machines API (fields this provider reads). */
449
+ export interface FlyMachine {
450
+ id: string;
451
+ name?: string;
452
+ /** Optional so a response that omits it degrades to `created` rather than crashing the mapper. */
453
+ state?: FlyMachineState;
454
+ region?: string;
455
+ private_ip?: string;
456
+ config?: FlyMachineConfig;
457
+ }
458
+ /** A Machine as returned by the org-wide list endpoint (`GET /orgs/{org}/machines`). */
459
+ export interface FlyOrgMachine extends FlyMachine {
460
+ app_name: string;
461
+ }
462
+ /** Machine configuration (`fly.MachineConfig`) — the subset this provider writes. */
463
+ export interface FlyMachineConfig {
464
+ image: string;
465
+ env?: Record<string, string>;
466
+ metadata?: Record<string, string>;
467
+ guest?: {
468
+ cpus?: number;
469
+ cpu_kind?: string;
470
+ memory_mb?: number;
471
+ };
472
+ mounts?: Array<{
473
+ volume?: string;
474
+ name?: string;
475
+ path: string;
476
+ }>;
477
+ services?: FlyMachineService[];
478
+ restart?: {
479
+ policy?: 'no' | 'always' | 'on-failure' | 'spot-price';
480
+ max_retries?: number;
481
+ };
482
+ auto_destroy?: boolean;
483
+ init?: {
484
+ exec?: string[];
485
+ entrypoint?: string[];
486
+ cmd?: string[];
487
+ tty?: boolean;
488
+ };
489
+ }
490
+ /** A Fly Proxy service attached to a Machine (`fly.MachineService`). */
491
+ export interface FlyMachineService {
492
+ protocol: string;
493
+ internal_port: number;
494
+ ports: Array<{
495
+ port: number;
496
+ handlers?: string[];
497
+ force_https?: boolean;
498
+ }>;
499
+ autostart?: boolean;
500
+ autostop?: 'off' | 'stop' | 'suspend';
501
+ min_machines_running?: number;
502
+ }
503
+ /** A Fly volume (fields this provider reads). */
504
+ export interface FlyVolume {
505
+ id: string;
506
+ name: string;
507
+ state?: string;
508
+ size_gb?: number;
509
+ region?: string;
510
+ attached_machine_id?: string;
511
+ }
512
+ /** Response body of `POST /v1/apps/{app}/machines/{id}/exec` (`flydv1.ExecResponse`). */
513
+ export interface FlyExecResponse {
514
+ stdout?: string;
515
+ stderr?: string;
516
+ exit_code?: number;
517
+ exit_signal?: number;
518
+ }
519
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Fly.io Machines sandbox provider configuration and wire types.
3
+ *
4
+ * Wire types mirror the Fly Machines OpenAPI specification
5
+ * (https://docs.machines.dev/openapi.json, servers: `https://api.machines.dev/v1`).
6
+ * Only the fields this provider actually reads or writes are modelled — the real
7
+ * schemas are much larger, and modelling fields we never use would rot silently.
8
+ *
9
+ * @module
10
+ */
11
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shell-quoting and environment helpers shared by the exec and file paths.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Wraps a string in single quotes, escaping any single quotes it contains.
8
+ *
9
+ * Every command this provider runs goes through `sh -c`, so an unquoted path or
10
+ * value is a command-injection vector. Double quotes are NOT sufficient — `$()`,
11
+ * backticks and `!` still expand inside them.
12
+ * @param value - The raw string to embed in a shell command.
13
+ * @returns A single-quoted, shell-safe token.
14
+ */
15
+ export declare function shellQuote(value: string): string;
16
+ /**
17
+ * Renders `ExecOptions.env` as `export` statements to prepend to a script.
18
+ *
19
+ * Fly's exec endpoint takes no environment map (only `command`, `stdin`,
20
+ * `container` and `timeout`), so per-command environment has to be set inside
21
+ * the shell script itself.
22
+ * @param env - Environment variables to set for the command.
23
+ * @returns One `export NAME='value'` line per variable, in insertion order.
24
+ * @throws {Error} When a name is not a valid shell identifier. Silently dropping it
25
+ * would make the command run with the variable unset, which surfaces far from
26
+ * the cause.
27
+ */
28
+ export declare function renderEnvExports(env: Record<string, string> | undefined): string[];
29
+ //# sourceMappingURL=utilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,MAAM,EAAE,CAgBlF"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Shell-quoting and environment helpers shared by the exec and file paths.
3
+ *
4
+ * @module
5
+ */
6
+ import { t } from '@molecule/api-i18n';
7
+ /** Environment variable names must be valid shell identifiers to be `export`able. */
8
+ const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
9
+ /**
10
+ * Wraps a string in single quotes, escaping any single quotes it contains.
11
+ *
12
+ * Every command this provider runs goes through `sh -c`, so an unquoted path or
13
+ * value is a command-injection vector. Double quotes are NOT sufficient — `$()`,
14
+ * backticks and `!` still expand inside them.
15
+ * @param value - The raw string to embed in a shell command.
16
+ * @returns A single-quoted, shell-safe token.
17
+ */
18
+ export function shellQuote(value) {
19
+ return `'${value.replace(/'/g, `'\\''`)}'`;
20
+ }
21
+ /**
22
+ * Renders `ExecOptions.env` as `export` statements to prepend to a script.
23
+ *
24
+ * Fly's exec endpoint takes no environment map (only `command`, `stdin`,
25
+ * `container` and `timeout`), so per-command environment has to be set inside
26
+ * the shell script itself.
27
+ * @param env - Environment variables to set for the command.
28
+ * @returns One `export NAME='value'` line per variable, in insertion order.
29
+ * @throws {Error} When a name is not a valid shell identifier. Silently dropping it
30
+ * would make the command run with the variable unset, which surfaces far from
31
+ * the cause.
32
+ */
33
+ export function renderEnvExports(env) {
34
+ if (!env)
35
+ return [];
36
+ return Object.entries(env).map(([name, value]) => {
37
+ if (!ENV_NAME_PATTERN.test(name)) {
38
+ throw new Error(t('codeSandbox.flyio.error.badEnvName', { name }, {
39
+ defaultValue: `Invalid environment variable name "${name}" — must match [A-Za-z_][A-Za-z0-9_]*.`,
40
+ }));
41
+ }
42
+ return `export ${name}=${shellQuote(value)}`;
43
+ });
44
+ }