@alchemy.run/fly 2.0.0-beta.84 → 2.0.0-beta.86
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/lib/App.d.ts +27 -5
- package/lib/App.d.ts.map +1 -1
- package/lib/App.js +29 -8
- package/lib/App.js.map +1 -1
- package/lib/AuthProvider.d.ts +1 -1
- package/lib/AuthProvider.d.ts.map +1 -1
- package/lib/AuthProvider.js.map +1 -1
- package/lib/BindService.d.ts +7 -7
- package/lib/BindService.d.ts.map +1 -1
- package/lib/BindService.js +21 -28
- package/lib/BindService.js.map +1 -1
- package/lib/Bootstrap.d.ts.map +1 -1
- package/lib/Bootstrap.js +2 -1
- package/lib/Bootstrap.js.map +1 -1
- package/lib/Bucket.d.ts +38 -4
- package/lib/Bucket.d.ts.map +1 -1
- package/lib/Bucket.js +190 -37
- package/lib/Bucket.js.map +1 -1
- package/lib/BucketBinding.d.ts.map +1 -1
- package/lib/BucketBinding.js +1 -1
- package/lib/BucketBinding.js.map +1 -1
- package/lib/Catalog.d.ts.map +1 -1
- package/lib/Catalog.js.map +1 -1
- package/lib/Certificate.d.ts +25 -1
- package/lib/Certificate.d.ts.map +1 -1
- package/lib/Certificate.js +22 -4
- package/lib/Certificate.js.map +1 -1
- package/lib/Checkpoint.d.ts.map +1 -1
- package/lib/Checkpoint.js.map +1 -1
- package/lib/CheckpointHttp.d.ts.map +1 -1
- package/lib/CheckpointHttp.js.map +1 -1
- package/lib/ConnectPostgres.d.ts +1 -1
- package/lib/ConnectPostgres.d.ts.map +1 -1
- package/lib/ConnectPostgres.js.map +1 -1
- package/lib/ConnectPostgresHttp.d.ts.map +1 -1
- package/lib/ConnectPostgresHttp.js +1 -1
- package/lib/ConnectPostgresHttp.js.map +1 -1
- package/lib/Contributions.d.ts +1 -1
- package/lib/Contributions.d.ts.map +1 -1
- package/lib/Contributions.js.map +1 -1
- package/lib/Credentials.d.ts +5 -4
- package/lib/Credentials.d.ts.map +1 -1
- package/lib/Credentials.js +18 -18
- package/lib/Credentials.js.map +1 -1
- package/lib/Decrypt.d.ts.map +1 -1
- package/lib/Decrypt.js.map +1 -1
- package/lib/DecryptHttp.d.ts.map +1 -1
- package/lib/DecryptHttp.js.map +1 -1
- package/lib/DeleteObject.d.ts.map +1 -1
- package/lib/DeleteObject.js.map +1 -1
- package/lib/DeleteObjectHttp.d.ts.map +1 -1
- package/lib/DeleteObjectHttp.js.map +1 -1
- package/lib/Deployment.d.ts.map +1 -1
- package/lib/Deployment.js.map +1 -1
- package/lib/DeploymentImages.d.ts.map +1 -1
- package/lib/DeploymentImages.js +1 -1
- package/lib/DeploymentImages.js.map +1 -1
- package/lib/DeploymentState.d.ts.map +1 -1
- package/lib/DeploymentState.js.map +1 -1
- package/lib/Encrypt.d.ts.map +1 -1
- package/lib/Encrypt.js.map +1 -1
- package/lib/EncryptHttp.d.ts.map +1 -1
- package/lib/EncryptHttp.js.map +1 -1
- package/lib/Environment.d.ts.map +1 -1
- package/lib/Environment.js.map +1 -1
- package/lib/Errors.d.ts +1 -1
- package/lib/Errors.d.ts.map +1 -1
- package/lib/Errors.js.map +1 -1
- package/lib/Exec.d.ts.map +1 -1
- package/lib/Exec.js.map +1 -1
- package/lib/ExecHttp.d.ts.map +1 -1
- package/lib/ExecHttp.js.map +1 -1
- package/lib/GetObject.d.ts.map +1 -1
- package/lib/GetObject.js.map +1 -1
- package/lib/GetObjectHttp.d.ts.map +1 -1
- package/lib/GetObjectHttp.js.map +1 -1
- package/lib/GetSecret.d.ts.map +1 -1
- package/lib/GetSecret.js.map +1 -1
- package/lib/GetSecretHttp.d.ts.map +1 -1
- package/lib/GetSecretHttp.js.map +1 -1
- package/lib/HeadObject.d.ts.map +1 -1
- package/lib/HeadObject.js.map +1 -1
- package/lib/HeadObjectHttp.d.ts.map +1 -1
- package/lib/HeadObjectHttp.js.map +1 -1
- package/lib/IpAssignment.d.ts +24 -1
- package/lib/IpAssignment.d.ts.map +1 -1
- package/lib/IpAssignment.js +21 -4
- package/lib/IpAssignment.js.map +1 -1
- package/lib/ListObjectsV2.d.ts.map +1 -1
- package/lib/ListObjectsV2.js.map +1 -1
- package/lib/ListObjectsV2Http.d.ts.map +1 -1
- package/lib/ListObjectsV2Http.js.map +1 -1
- package/lib/ListSecrets.d.ts.map +1 -1
- package/lib/ListSecrets.js.map +1 -1
- package/lib/ListSecretsHttp.d.ts.map +1 -1
- package/lib/ListSecretsHttp.js.map +1 -1
- package/lib/Machine.d.ts +27 -2
- package/lib/Machine.d.ts.map +1 -1
- package/lib/Machine.js +18 -3
- package/lib/Machine.js.map +1 -1
- package/lib/MachineContainers.d.ts.map +1 -1
- package/lib/MachineContainers.js.map +1 -1
- package/lib/Metadata.d.ts.map +1 -1
- package/lib/Metadata.js.map +1 -1
- package/lib/MountVolume.d.ts +7 -2
- package/lib/MountVolume.d.ts.map +1 -1
- package/lib/MountVolume.js.map +1 -1
- package/lib/Postgres.d.ts +34 -2
- package/lib/Postgres.d.ts.map +1 -1
- package/lib/Postgres.js +26 -6
- package/lib/Postgres.js.map +1 -1
- package/lib/PostgresMigrations.d.ts +7 -22
- package/lib/PostgresMigrations.d.ts.map +1 -1
- package/lib/PostgresMigrations.js +37 -89
- package/lib/PostgresMigrations.js.map +1 -1
- package/lib/Providers.d.ts +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js.map +1 -1
- package/lib/PutObject.d.ts.map +1 -1
- package/lib/PutObject.js.map +1 -1
- package/lib/PutObjectHttp.d.ts.map +1 -1
- package/lib/PutObjectHttp.js.map +1 -1
- package/lib/ReadRedis.d.ts.map +1 -1
- package/lib/ReadRedis.js.map +1 -1
- package/lib/ReadRedisHttp.d.ts.map +1 -1
- package/lib/ReadRedisHttp.js.map +1 -1
- package/lib/ReadWriteRedis.d.ts.map +1 -1
- package/lib/ReadWriteRedis.js.map +1 -1
- package/lib/ReadWriteRedisHttp.d.ts.map +1 -1
- package/lib/ReadWriteRedisHttp.js.map +1 -1
- package/lib/Redis.d.ts +23 -3
- package/lib/Redis.d.ts.map +1 -1
- package/lib/Redis.js +37 -6
- package/lib/Redis.js.map +1 -1
- package/lib/RedisBinding.d.ts.map +1 -1
- package/lib/RedisBinding.js +1 -1
- package/lib/RedisBinding.js.map +1 -1
- package/lib/RedisHttp.d.ts +1 -1
- package/lib/RedisHttp.d.ts.map +1 -1
- package/lib/RedisHttp.js +4 -1
- package/lib/RedisHttp.js.map +1 -1
- package/lib/Region.d.ts.map +1 -1
- package/lib/Region.js.map +1 -1
- package/lib/Secret.d.ts +23 -2
- package/lib/Secret.d.ts.map +1 -1
- package/lib/Secret.js +17 -2
- package/lib/Secret.js.map +1 -1
- package/lib/SecretHttp.d.ts.map +1 -1
- package/lib/SecretHttp.js.map +1 -1
- package/lib/SecretKey.d.ts +23 -2
- package/lib/SecretKey.d.ts.map +1 -1
- package/lib/SecretKey.js +17 -2
- package/lib/SecretKey.js.map +1 -1
- package/lib/SecretKeyHttp.d.ts.map +1 -1
- package/lib/SecretKeyHttp.js.map +1 -1
- package/lib/Service.d.ts +104 -34
- package/lib/Service.d.ts.map +1 -1
- package/lib/Service.js +301 -54
- package/lib/Service.js.map +1 -1
- package/lib/Sign.d.ts.map +1 -1
- package/lib/Sign.js.map +1 -1
- package/lib/SignHttp.d.ts.map +1 -1
- package/lib/SignHttp.js.map +1 -1
- package/lib/Sprite.d.ts +24 -5
- package/lib/Sprite.d.ts.map +1 -1
- package/lib/Sprite.js +22 -5
- package/lib/Sprite.js.map +1 -1
- package/lib/SpriteHttp.d.ts.map +1 -1
- package/lib/SpriteHttp.js.map +1 -1
- package/lib/Verify.d.ts.map +1 -1
- package/lib/Verify.js.map +1 -1
- package/lib/VerifyHttp.d.ts.map +1 -1
- package/lib/VerifyHttp.js.map +1 -1
- package/lib/Volume.d.ts +4 -4
- package/lib/Volume.d.ts.map +1 -1
- package/lib/Volume.js +21 -5
- package/lib/Volume.js.map +1 -1
- package/lib/VolumeSnapshot.d.ts +32 -1
- package/lib/VolumeSnapshot.d.ts.map +1 -1
- package/lib/VolumeSnapshot.js +24 -1
- package/lib/VolumeSnapshot.js.map +1 -1
- package/lib/Website/AssetDeployment.d.ts.map +1 -1
- package/lib/Website/AssetDeployment.js +9 -9
- package/lib/Website/AssetDeployment.js.map +1 -1
- package/lib/Website/Astro.d.ts +8 -6
- package/lib/Website/Astro.d.ts.map +1 -1
- package/lib/Website/Astro.js +12 -13
- package/lib/Website/Astro.js.map +1 -1
- package/lib/Website/Foldkit.d.ts.map +1 -1
- package/lib/Website/Foldkit.js.map +1 -1
- package/lib/Website/FrameworkSite.d.ts +39 -2
- package/lib/Website/FrameworkSite.d.ts.map +1 -1
- package/lib/Website/FrameworkSite.js +46 -3
- package/lib/Website/FrameworkSite.js.map +1 -1
- package/lib/Website/Nextjs.d.ts.map +1 -1
- package/lib/Website/Nextjs.js.map +1 -1
- package/lib/Website/Nuxt.d.ts.map +1 -1
- package/lib/Website/Nuxt.js.map +1 -1
- package/lib/Website/Octane.d.ts.map +1 -1
- package/lib/Website/Octane.js.map +1 -1
- package/lib/Website/ReactRouter.d.ts.map +1 -1
- package/lib/Website/ReactRouter.js.map +1 -1
- package/lib/Website/SolidStart.d.ts.map +1 -1
- package/lib/Website/SolidStart.js.map +1 -1
- package/lib/Website/StaticSite.d.ts +10 -3
- package/lib/Website/StaticSite.d.ts.map +1 -1
- package/lib/Website/StaticSite.js +16 -4
- package/lib/Website/StaticSite.js.map +1 -1
- package/lib/Website/SvelteKit.d.ts.map +1 -1
- package/lib/Website/SvelteKit.js.map +1 -1
- package/lib/Website/TanStackStart.d.ts.map +1 -1
- package/lib/Website/TanStackStart.js.map +1 -1
- package/lib/Website/Vinext.d.ts.map +1 -1
- package/lib/Website/Vinext.js.map +1 -1
- package/lib/Website/Vite.d.ts +9 -0
- package/lib/Website/Vite.d.ts.map +1 -1
- package/lib/Website/Vite.js +9 -0
- package/lib/Website/Vite.js.map +1 -1
- package/lib/Website/Vocs.d.ts.map +1 -1
- package/lib/Website/Vocs.js.map +1 -1
- package/lib/Website/Waku.d.ts.map +1 -1
- package/lib/Website/Waku.js.map +1 -1
- package/lib/Website/index.d.ts.map +1 -1
- package/lib/Website/index.js.map +1 -1
- package/lib/WriteCertificates.d.ts.map +1 -1
- package/lib/WriteCertificates.js.map +1 -1
- package/lib/WriteCertificatesHttp.d.ts.map +1 -1
- package/lib/WriteCertificatesHttp.js +2 -2
- package/lib/WriteCertificatesHttp.js.map +1 -1
- package/lib/WriteRedis.d.ts.map +1 -1
- package/lib/WriteRedis.js.map +1 -1
- package/lib/WriteRedisHttp.d.ts.map +1 -1
- package/lib/WriteRedisHttp.js.map +1 -1
- package/lib/WriteSecret.d.ts.map +1 -1
- package/lib/WriteSecret.js.map +1 -1
- package/lib/WriteSecretHttp.d.ts.map +1 -1
- package/lib/WriteSecretHttp.js.map +1 -1
- package/lib/bluegreen.d.ts +5 -4
- package/lib/bluegreen.d.ts.map +1 -1
- package/lib/bluegreen.js +7 -5
- package/lib/bluegreen.js.map +1 -1
- package/lib/hosted.d.ts +19 -4
- package/lib/hosted.d.ts.map +1 -1
- package/lib/hosted.js +101 -11
- package/lib/hosted.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/internal/Oidc.d.ts +15 -0
- package/lib/internal/Oidc.d.ts.map +1 -0
- package/lib/internal/Oidc.js +73 -0
- package/lib/internal/Oidc.js.map +1 -0
- package/lib/internal/RemoteBuilder.d.ts.map +1 -1
- package/lib/internal/RemoteBuilder.js +21 -8
- package/lib/internal/RemoteBuilder.js.map +1 -1
- package/lib/leases.d.ts +6 -2
- package/lib/leases.d.ts.map +1 -1
- package/lib/leases.js +25 -5
- package/lib/leases.js.map +1 -1
- package/lib/ports.d.ts.map +1 -1
- package/lib/ports.js.map +1 -1
- package/lib/replicas.d.ts +39 -27
- package/lib/replicas.d.ts.map +1 -1
- package/lib/replicas.js +63 -8
- package/lib/replicas.js.map +1 -1
- package/lib/rpc.d.ts +14 -12
- package/lib/rpc.d.ts.map +1 -1
- package/lib/rpc.js +38 -40
- package/lib/rpc.js.map +1 -1
- package/package.json +15 -15
- package/src/App.ts +41 -10
- package/src/BindService.ts +28 -36
- package/src/Bootstrap.ts +2 -0
- package/src/Bucket.ts +278 -45
- package/src/BucketBinding.ts +1 -1
- package/src/Certificate.ts +36 -5
- package/src/ConnectPostgres.ts +1 -1
- package/src/ConnectPostgresHttp.ts +1 -1
- package/src/Credentials.ts +18 -41
- package/src/DeploymentImages.ts +1 -1
- package/src/Errors.ts +1 -1
- package/src/IpAssignment.ts +35 -5
- package/src/Machine.ts +39 -4
- package/src/MountVolume.ts +5 -2
- package/src/Postgres.ts +50 -8
- package/src/PostgresMigrations.ts +39 -99
- package/src/Redis.ts +54 -9
- package/src/RedisBinding.ts +1 -1
- package/src/RedisHttp.ts +4 -1
- package/src/Secret.ts +32 -4
- package/src/SecretKey.ts +32 -4
- package/src/Service.ts +441 -88
- package/src/Sprite.ts +45 -17
- package/src/Volume.ts +30 -6
- package/src/VolumeSnapshot.ts +40 -2
- package/src/Website/AssetDeployment.ts +13 -15
- package/src/Website/Astro.ts +20 -15
- package/src/Website/FrameworkSite.ts +92 -6
- package/src/Website/StaticSite.ts +27 -7
- package/src/Website/Vite.ts +9 -0
- package/src/WriteCertificatesHttp.ts +2 -2
- package/src/bluegreen.ts +11 -5
- package/src/hosted.ts +121 -17
- package/src/internal/Oidc.ts +99 -0
- package/src/internal/RemoteBuilder.ts +37 -19
- package/src/leases.ts +35 -7
- package/src/replicas.ts +70 -9
- package/src/rpc.ts +37 -40
package/src/Sprite.ts
CHANGED
|
@@ -5,7 +5,7 @@ import * as Bundle from "alchemy/Bundle/Bundle";
|
|
|
5
5
|
import { isResolved } from "alchemy/Diff";
|
|
6
6
|
import { Platform, type Main, type PlatformProps } from "alchemy/Platform";
|
|
7
7
|
import * as Provider from "alchemy/Provider";
|
|
8
|
-
import type { Resource, ResourceBinding } from "alchemy/Resource";
|
|
8
|
+
import type { LookupMethod, Resource, ResourceBinding } from "alchemy/Resource";
|
|
9
9
|
import type { ServerHost } from "alchemy/Runtime/Process";
|
|
10
10
|
import { Stack } from "alchemy/Stack";
|
|
11
11
|
import { createInternalTags } from "alchemy/Tags";
|
|
@@ -119,9 +119,19 @@ export type Sprite = Resource<
|
|
|
119
119
|
};
|
|
120
120
|
},
|
|
121
121
|
SpriteBinding,
|
|
122
|
-
Providers
|
|
122
|
+
Providers,
|
|
123
|
+
SpriteLookup
|
|
123
124
|
>;
|
|
124
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Props for {@link Sprite.lookup}: find a Sprite this stack doesn't
|
|
128
|
+
* manage by its name.
|
|
129
|
+
*/
|
|
130
|
+
export interface SpriteLookup {
|
|
131
|
+
/** Sprite name (unique per org). */
|
|
132
|
+
name: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
125
135
|
export const isSprite = (value: unknown): value is Sprite =>
|
|
126
136
|
typeof value === "object" && value !== null && (value as { Type?: string }).Type === "Fly.Sprite";
|
|
127
137
|
|
|
@@ -307,29 +317,38 @@ export type SpriteRuntimeContext = FlyHostRuntimeContext;
|
|
|
307
317
|
* ) {}
|
|
308
318
|
* ```
|
|
309
319
|
*
|
|
320
|
+
* ### Looking up an existing Sprite
|
|
321
|
+
* `Sprite.lookup` references a Sprite Alchemy doesn't manage (created with
|
|
322
|
+
* `sprite create` or by another stack). It is read during plan/deploy,
|
|
323
|
+
* never created, updated or deleted. `code.hash` is empty: the Sprite's
|
|
324
|
+
* program isn't Alchemy's.
|
|
325
|
+
*
|
|
326
|
+
* **Example:** Reference an existing Sprite
|
|
327
|
+
* ```typescript
|
|
328
|
+
* const box = Fly.Sprite.lookup({ name: "my-box" });
|
|
329
|
+
* ```
|
|
330
|
+
*
|
|
310
331
|
* @resource
|
|
311
332
|
* @product Sprite
|
|
312
333
|
*/
|
|
313
|
-
export const Sprite: Platform<Sprite, SpriteServices, SpriteShape, SpriteRuntimeContext>
|
|
314
|
-
"Fly.Sprite",
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
},
|
|
318
|
-
);
|
|
334
|
+
export const Sprite: Platform<Sprite, SpriteServices, SpriteShape, SpriteRuntimeContext> &
|
|
335
|
+
LookupMethod<Sprite> = Platform("Fly.Sprite", {
|
|
336
|
+
createRuntimeContext: createFlyHostRuntimeContext("Fly.Sprite"),
|
|
337
|
+
});
|
|
319
338
|
|
|
320
339
|
export class SpriteNotCreated extends Data.TaggedError("Fly.SpriteNotCreated")<{
|
|
321
|
-
|
|
340
|
+
resourceName: string;
|
|
322
341
|
}> {}
|
|
323
342
|
|
|
324
343
|
export class SpriteExecFailed extends Data.TaggedError("Fly.SpriteExecFailed")<{
|
|
325
|
-
|
|
344
|
+
resourceName: string;
|
|
326
345
|
cmd: string;
|
|
327
346
|
exitCode: number | undefined;
|
|
328
347
|
stderr: string | undefined;
|
|
329
348
|
}> {}
|
|
330
349
|
|
|
331
350
|
class SpritePending extends Data.TaggedError("Fly.SpritePending")<{
|
|
332
|
-
|
|
351
|
+
resourceName: string;
|
|
333
352
|
}> {}
|
|
334
353
|
|
|
335
354
|
const toStatus = (status: string | undefined): SpriteStatus =>
|
|
@@ -368,12 +387,14 @@ const ownershipLabels = (id: string) =>
|
|
|
368
387
|
});
|
|
369
388
|
|
|
370
389
|
const getByName = (name: string) =>
|
|
371
|
-
sprites.getSprite({ name }).pipe(Effect.catchTag("NotFound", () => Effect.
|
|
390
|
+
sprites.getSprite({ name }).pipe(Effect.catchTag("NotFound", () => Effect.undefined));
|
|
372
391
|
|
|
373
392
|
const waitForSprite = (name: string) =>
|
|
374
393
|
getByName(name).pipe(
|
|
375
394
|
Effect.flatMap((found) =>
|
|
376
|
-
found !== undefined
|
|
395
|
+
found !== undefined
|
|
396
|
+
? Effect.succeed(found)
|
|
397
|
+
: Effect.fail(new SpritePending({ resourceName: name })),
|
|
377
398
|
),
|
|
378
399
|
Effect.retry({
|
|
379
400
|
while: (e) => e._tag === "Fly.SpritePending",
|
|
@@ -458,7 +479,7 @@ const runExec = (input: { name: string; cmd: string[]; env?: string[] }) =>
|
|
|
458
479
|
);
|
|
459
480
|
if ((result.exit_code ?? 0) !== 0) {
|
|
460
481
|
return yield* new SpriteExecFailed({
|
|
461
|
-
|
|
482
|
+
resourceName: input.name,
|
|
462
483
|
cmd: input.cmd.join(" "),
|
|
463
484
|
exitCode: result.exit_code,
|
|
464
485
|
stderr: result.stderr,
|
|
@@ -573,15 +594,22 @@ export const SpriteProvider = () =>
|
|
|
573
594
|
return undefined;
|
|
574
595
|
}),
|
|
575
596
|
|
|
597
|
+
lookup: Effect.fn(function* ({ name }) {
|
|
598
|
+
const found = yield* getByName(name).pipe(
|
|
599
|
+
Effect.catchTag("SpritesNotEnabled", () => Effect.undefined),
|
|
600
|
+
);
|
|
601
|
+
return found === undefined ? undefined : toAttrs(found, name, "");
|
|
602
|
+
}),
|
|
603
|
+
|
|
576
604
|
read: Effect.fn(function* ({ id, olds, output }) {
|
|
577
605
|
const name = yield* resolveSpriteName(id, olds?.name, output?.name);
|
|
578
606
|
const found = yield* (
|
|
579
|
-
output?.name !== undefined ? getByName(output.name) : Effect.
|
|
607
|
+
output?.name !== undefined ? getByName(output.name) : Effect.undefined
|
|
580
608
|
).pipe(
|
|
581
609
|
Effect.flatMap((existing) =>
|
|
582
610
|
existing !== undefined ? Effect.succeed(existing) : getByName(name),
|
|
583
611
|
),
|
|
584
|
-
Effect.catchTag("SpritesNotEnabled", () => Effect.
|
|
612
|
+
Effect.catchTag("SpritesNotEnabled", () => Effect.undefined),
|
|
585
613
|
);
|
|
586
614
|
if (found === undefined) return undefined;
|
|
587
615
|
const attrs = toAttrs(found, name, output?.code.hash ?? "");
|
|
@@ -628,7 +656,7 @@ export const SpriteProvider = () =>
|
|
|
628
656
|
}
|
|
629
657
|
|
|
630
658
|
if (current === undefined) {
|
|
631
|
-
return yield* new SpriteNotCreated({ name });
|
|
659
|
+
return yield* new SpriteNotCreated({ resourceName: name });
|
|
632
660
|
}
|
|
633
661
|
// Narrowed once so the `NotFound` fallbacks below keep the
|
|
634
662
|
// non-optional type instead of widening back to `| undefined`.
|
package/src/Volume.ts
CHANGED
|
@@ -16,7 +16,7 @@ export const DEFAULT_VOLUME_REGION = "iad";
|
|
|
16
16
|
export const MIN_SIZE_GB = 1;
|
|
17
17
|
|
|
18
18
|
export class VolumeNotCreated extends Data.TaggedError("Fly.VolumeNotCreated")<{
|
|
19
|
-
|
|
19
|
+
resourceName: string;
|
|
20
20
|
appName: string;
|
|
21
21
|
}> {}
|
|
22
22
|
|
|
@@ -39,7 +39,7 @@ const transientState = (state: string | undefined) =>
|
|
|
39
39
|
export const getVolumeById = (appName: string, volumeId: string) =>
|
|
40
40
|
machines.getVolumeById({ app_name: appName, volume_id: volumeId }).pipe(
|
|
41
41
|
Effect.map((volume) => (destroying(volume.state) ? undefined : volume)),
|
|
42
|
-
Effect.catchTag("NotFound", () => Effect.
|
|
42
|
+
Effect.catchTag("NotFound", () => Effect.undefined),
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
export const listVolumesByApp = (appName: string) =>
|
|
@@ -66,7 +66,7 @@ export const listVolumeGroup = (appName: string, name: string, region: string) =
|
|
|
66
66
|
export const waitUntilVolumeReady = (appName: string, volumeId: string) =>
|
|
67
67
|
getVolumeById(appName, volumeId).pipe(
|
|
68
68
|
Effect.flatMap((volume) => {
|
|
69
|
-
if (volume === undefined) return Effect.
|
|
69
|
+
if (volume === undefined) return Effect.undefined;
|
|
70
70
|
if (transientState(volume.state)) {
|
|
71
71
|
return Effect.fail(
|
|
72
72
|
new VolumePending({
|
|
@@ -116,6 +116,11 @@ export const createVolume = Effect.fn(function* (input: {
|
|
|
116
116
|
disk: DiskSpec;
|
|
117
117
|
}) {
|
|
118
118
|
const sizeGb = Math.max(input.disk.sizeGb, MIN_SIZE_GB);
|
|
119
|
+
// The volumes this group held before the create: a storage host that fails
|
|
120
|
+
// with an unknown outcome may still have made one.
|
|
121
|
+
const before = new Set(
|
|
122
|
+
(yield* listVolumeGroup(input.appName, input.name, input.region)).map((volume) => volume.id),
|
|
123
|
+
);
|
|
119
124
|
const created = yield* machines
|
|
120
125
|
.createVolume({
|
|
121
126
|
app_name: input.appName,
|
|
@@ -130,7 +135,26 @@ export const createVolume = Effect.fn(function* (input: {
|
|
|
130
135
|
source_volume_id: input.disk.sourceVolumeId,
|
|
131
136
|
require_unique_zone: input.disk.requireUniqueZone,
|
|
132
137
|
})
|
|
133
|
-
.pipe(
|
|
138
|
+
.pipe(
|
|
139
|
+
Effect.catchTags({
|
|
140
|
+
Conflict: () => Effect.undefined,
|
|
141
|
+
StorageHostCapacity: (error) =>
|
|
142
|
+
Effect.gen(function* () {
|
|
143
|
+
// Its outcome is unknown: a single new volume in the group is the
|
|
144
|
+
// one this call made. Otherwise create again after a pause.
|
|
145
|
+
yield* Effect.sleep(Duration.seconds(5));
|
|
146
|
+
const fresh = (yield* listVolumeGroup(input.appName, input.name, input.region)).filter(
|
|
147
|
+
(volume) => volume.id !== undefined && !before.has(volume.id),
|
|
148
|
+
);
|
|
149
|
+
return fresh.length === 1 ? fresh[0] : yield* error;
|
|
150
|
+
}),
|
|
151
|
+
}),
|
|
152
|
+
Effect.retry({
|
|
153
|
+
while: (error) => error._tag === "StorageHostCapacity",
|
|
154
|
+
schedule: Schedule.spaced(Duration.seconds(5)),
|
|
155
|
+
times: 6,
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
134
158
|
if (created?.id !== undefined && created.id.length > 0) {
|
|
135
159
|
return (yield* waitUntilVolumeReady(input.appName, created.id)) ?? created;
|
|
136
160
|
}
|
|
@@ -138,7 +162,7 @@ export const createVolume = Effect.fn(function* (input: {
|
|
|
138
162
|
const hit = group.at(-1);
|
|
139
163
|
if (hit === undefined || hit.id === undefined) {
|
|
140
164
|
return yield* new VolumeNotCreated({
|
|
141
|
-
|
|
165
|
+
resourceName: input.name,
|
|
142
166
|
appName: input.appName,
|
|
143
167
|
});
|
|
144
168
|
}
|
|
@@ -211,7 +235,7 @@ export const ensureVolumeGroup = Effect.fn(function* (input: {
|
|
|
211
235
|
}
|
|
212
236
|
if (group.length < input.count) {
|
|
213
237
|
return yield* new VolumeNotCreated({
|
|
214
|
-
|
|
238
|
+
resourceName: input.name,
|
|
215
239
|
appName: input.appName,
|
|
216
240
|
});
|
|
217
241
|
}
|
package/src/VolumeSnapshot.ts
CHANGED
|
@@ -56,9 +56,23 @@ export type VolumeSnapshot = Resource<
|
|
|
56
56
|
createdAt: string | undefined;
|
|
57
57
|
},
|
|
58
58
|
never,
|
|
59
|
-
Providers
|
|
59
|
+
Providers,
|
|
60
|
+
VolumeSnapshotLookup
|
|
60
61
|
>;
|
|
61
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Props for {@link VolumeSnapshot.lookup}: find a snapshot this stack
|
|
65
|
+
* doesn't manage by its App, Volume and id.
|
|
66
|
+
*/
|
|
67
|
+
export interface VolumeSnapshotLookup {
|
|
68
|
+
/** Fly App name the Volume belongs to. */
|
|
69
|
+
appName: string;
|
|
70
|
+
/** Fly Volume id (`vol_…`). */
|
|
71
|
+
volumeId: string;
|
|
72
|
+
/** Fly snapshot id (`vs_…`). */
|
|
73
|
+
snapshotId: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
62
76
|
/**
|
|
63
77
|
* A Fly.VolumeSnapshot is an on-demand snapshot of a mounted disk.
|
|
64
78
|
*
|
|
@@ -127,6 +141,25 @@ export type VolumeSnapshot = Resource<
|
|
|
127
141
|
* ) {}
|
|
128
142
|
* ```
|
|
129
143
|
*
|
|
144
|
+
* ### Looking up an existing snapshot
|
|
145
|
+
* `VolumeSnapshot.lookup` references a snapshot Alchemy doesn't manage
|
|
146
|
+
* (taken by Fly's daily schedule or by another stack). It is read during
|
|
147
|
+
* plan/deploy and never created or deleted.
|
|
148
|
+
*
|
|
149
|
+
* **Example:** Restore a disk from an existing snapshot
|
|
150
|
+
* ```typescript
|
|
151
|
+
* const snapshot = Fly.VolumeSnapshot.lookup({
|
|
152
|
+
* appName: "my-app",
|
|
153
|
+
* volumeId: "vol_123",
|
|
154
|
+
* snapshotId: "vs_456",
|
|
155
|
+
* });
|
|
156
|
+
* const disk = yield* Fly.MountVolume({
|
|
157
|
+
* path: "/data",
|
|
158
|
+
* sizeGb: 1,
|
|
159
|
+
* snapshotId: snapshot.snapshotId,
|
|
160
|
+
* });
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
130
163
|
* @resource
|
|
131
164
|
* @product Volume
|
|
132
165
|
*/
|
|
@@ -214,7 +247,7 @@ const waitForNewSnapshot = (appName: string, volumeId: string, knownIds: Readonl
|
|
|
214
247
|
times: 8,
|
|
215
248
|
schedule: backoff,
|
|
216
249
|
}),
|
|
217
|
-
Effect.catchTag("Fly.VolumeSnapshotPending", () => Effect.
|
|
250
|
+
Effect.catchTag("Fly.VolumeSnapshotPending", () => Effect.undefined),
|
|
218
251
|
);
|
|
219
252
|
|
|
220
253
|
export const VolumeSnapshotProvider = () =>
|
|
@@ -235,6 +268,11 @@ export const VolumeSnapshotProvider = () =>
|
|
|
235
268
|
return undefined;
|
|
236
269
|
}),
|
|
237
270
|
|
|
271
|
+
lookup: Effect.fn(function* ({ appName, volumeId, snapshotId }) {
|
|
272
|
+
const found = yield* findById(appName, volumeId, snapshotId);
|
|
273
|
+
return found === undefined ? undefined : toAttrs(appName, volumeId, found);
|
|
274
|
+
}),
|
|
275
|
+
|
|
238
276
|
read: Effect.fn(function* ({ olds, output }) {
|
|
239
277
|
const appName = output?.appName ?? appNameOf(olds?.app);
|
|
240
278
|
const volumeId = output?.volumeId ?? volumeIdOf(olds?.volumeId);
|
|
@@ -95,7 +95,7 @@ const scopeOf = (bucket: Bucket) =>
|
|
|
95
95
|
endpoint === undefined
|
|
96
96
|
) {
|
|
97
97
|
return yield* new TigrisCredentialsMissing({
|
|
98
|
-
|
|
98
|
+
resourceName: bucketName ?? bucket.LogicalId,
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
101
|
return { bucketName, accessKeyId, secretAccessKey, endpoint, region };
|
|
@@ -131,14 +131,14 @@ const walkFiles = Effect.fn(function* (root: string) {
|
|
|
131
131
|
const path = yield* Path.Path;
|
|
132
132
|
if (!(yield* fs.exists(root))) return [] as string[];
|
|
133
133
|
const names = yield* fs.readDirectory(root, { recursive: true });
|
|
134
|
-
const files = yield* Effect.
|
|
135
|
-
names
|
|
134
|
+
const files = yield* Effect.forEach(
|
|
135
|
+
names,
|
|
136
|
+
(name) =>
|
|
136
137
|
Effect.gen(function* () {
|
|
137
138
|
const full = path.join(root, name);
|
|
138
139
|
const stat = yield* fs.stat(full);
|
|
139
140
|
return stat.type === "File" ? name.replaceAll("\\", "/") : undefined;
|
|
140
141
|
}),
|
|
141
|
-
),
|
|
142
142
|
{ concurrency: "unbounded" },
|
|
143
143
|
);
|
|
144
144
|
return files
|
|
@@ -213,8 +213,9 @@ export const AssetDeploymentProvider = () =>
|
|
|
213
213
|
: path.resolve(initialCwd, news.sourcePath);
|
|
214
214
|
const files = yield* walkFiles(root);
|
|
215
215
|
const observed = yield* listObserved(scope, prefix);
|
|
216
|
-
const prepared = yield* Effect.
|
|
217
|
-
files
|
|
216
|
+
const prepared = yield* Effect.forEach(
|
|
217
|
+
files,
|
|
218
|
+
(relative) =>
|
|
218
219
|
Effect.gen(function* () {
|
|
219
220
|
const body = yield* fs.readFile(path.join(root, relative));
|
|
220
221
|
const key = prefix.length > 0 ? `${prefix}/${relative}` : relative;
|
|
@@ -222,7 +223,6 @@ export const AssetDeploymentProvider = () =>
|
|
|
222
223
|
const cacheControl = cacheControlOf(relative);
|
|
223
224
|
return { relative, key, body, contentType, cacheControl };
|
|
224
225
|
}),
|
|
225
|
-
),
|
|
226
226
|
{ concurrency: "unbounded" },
|
|
227
227
|
);
|
|
228
228
|
const version = yield* Effect.sync(() => {
|
|
@@ -258,10 +258,9 @@ export const AssetDeploymentProvider = () =>
|
|
|
258
258
|
);
|
|
259
259
|
|
|
260
260
|
if (news.purge ?? true) {
|
|
261
|
-
yield* Effect.
|
|
262
|
-
[...observed.keys()]
|
|
263
|
-
|
|
264
|
-
.map((key) => deleteObject(scope, key)),
|
|
261
|
+
yield* Effect.forEach(
|
|
262
|
+
[...observed.keys()].filter((key) => !desired.has(key)),
|
|
263
|
+
(key) => deleteObject(scope, key),
|
|
265
264
|
{ concurrency: s3Concurrency },
|
|
266
265
|
);
|
|
267
266
|
}
|
|
@@ -283,10 +282,9 @@ export const AssetDeploymentProvider = () =>
|
|
|
283
282
|
if (!(olds.purge ?? true)) return;
|
|
284
283
|
const scope = yield* scopeOf(olds.bucket);
|
|
285
284
|
const observed = yield* listObserved(scope, output.prefix);
|
|
286
|
-
yield* Effect.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
285
|
+
yield* Effect.forEach([...observed.keys()], (key) => deleteObject(scope, key), {
|
|
286
|
+
concurrency: s3Concurrency,
|
|
287
|
+
});
|
|
290
288
|
},
|
|
291
289
|
Effect.catchTag("NoSuchBucket", () => Effect.void),
|
|
292
290
|
),
|
package/src/Website/Astro.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveAstroOutput } from "alchemy/Website/AstroOutput";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
1
3
|
import { frameworkSite, staticConfigFromAssets, type FrameworkSiteProps } from "./FrameworkSite.ts";
|
|
2
4
|
|
|
3
5
|
/** The framework-integration package that drives the Astro build. */
|
|
@@ -21,8 +23,9 @@ export interface AstroProps extends FrameworkSiteProps {
|
|
|
21
23
|
* Astro output target. `"server"` renders pages on demand; individual
|
|
22
24
|
* pages opt into prerendering with `export const prerender = true`.
|
|
23
25
|
* `"static"` prerenders every page at build time and deploys
|
|
24
|
-
* assets-only (no SSR server).
|
|
25
|
-
*
|
|
26
|
+
* assets-only (no SSR server). Overrides the `output` in your
|
|
27
|
+
* `astro.config.*`; leave it unset to use the file's value.
|
|
28
|
+
* @default the config file's `output`, else `"server"`
|
|
26
29
|
*/
|
|
27
30
|
output?: "server" | "static";
|
|
28
31
|
/** Source directory, relative to `rootDir`. @default "./src" */
|
|
@@ -41,9 +44,9 @@ export interface AstroProps extends FrameworkSiteProps {
|
|
|
41
44
|
* running the Node adapter's serve entry (static files first, then the
|
|
42
45
|
* framework handler) on port 3000.
|
|
43
46
|
*
|
|
44
|
-
* Pages render on demand by default (`output: "server"`).
|
|
45
|
-
* `astro: { output: "static" }`
|
|
46
|
-
* is assets-only.
|
|
47
|
+
* Pages render on demand by default (`output: "server"`). When your
|
|
48
|
+
* `astro.config.*` or `astro: { output: "static" }` declares a static
|
|
49
|
+
* site, every page is prerendered and the deploy is assets-only.
|
|
47
50
|
*
|
|
48
51
|
*
|
|
49
52
|
* ### Creating Astro Sites
|
|
@@ -75,13 +78,15 @@ export interface AstroProps extends FrameworkSiteProps {
|
|
|
75
78
|
* @resource
|
|
76
79
|
* @product Website
|
|
77
80
|
*/
|
|
78
|
-
export const Astro = (id: string, props: AstroProps = {}) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
81
|
+
export const Astro = (id: string, props: AstroProps = {}) =>
|
|
82
|
+
resolveAstroOutput({ rootDir: props.rootDir, output: props.astro?.output }).pipe(
|
|
83
|
+
Effect.flatMap((output) =>
|
|
84
|
+
frameworkSite(id, props, {
|
|
85
|
+
name: "Astro",
|
|
86
|
+
framework: ASTRO_FRAMEWORK_SPECIFIER,
|
|
87
|
+
target: ASTRO_NODE_TARGET_SPECIFIER,
|
|
88
|
+
options: { astro: { ...props.astro, output } },
|
|
89
|
+
static: output === "static" ? staticConfigFromAssets(props.assets) : undefined,
|
|
90
|
+
}),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AlchemyContext } from "alchemy/AlchemyContext";
|
|
2
2
|
import type { MemoOptions } from "alchemy/Command/Memo";
|
|
3
|
+
import * as DNS from "alchemy/DNS/Adapter";
|
|
4
|
+
import type { Input } from "alchemy/Input";
|
|
3
5
|
import * as Namespace from "alchemy/Namespace";
|
|
4
6
|
import * as Output from "alchemy/Output";
|
|
5
7
|
import { ProviderModePolicy } from "alchemy/ProviderMode";
|
|
@@ -18,7 +20,7 @@ import * as Path from "effect/Path";
|
|
|
18
20
|
import * as Redacted from "effect/Redacted";
|
|
19
21
|
import { App } from "../App.ts";
|
|
20
22
|
import { Bucket } from "../Bucket.ts";
|
|
21
|
-
import { Certificate } from "../Certificate.ts";
|
|
23
|
+
import { Certificate, type CertificateDnsRequirements } from "../Certificate.ts";
|
|
22
24
|
import { IpAssignment } from "../IpAssignment.ts";
|
|
23
25
|
import type { Providers } from "../Providers.ts";
|
|
24
26
|
import { Service, type ServiceProps } from "../Service.ts";
|
|
@@ -33,6 +35,80 @@ export type Ref<T> = T | Effect.Effect<T, never, Providers>;
|
|
|
33
35
|
export type { ServerDevProps, WebsiteAssetsProps, WebsiteNotFoundHandling };
|
|
34
36
|
export { staticConfigFromAssets };
|
|
35
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Custom hostname of a Fly website: the hostname itself, or
|
|
40
|
+
* `{ name, dns }` to also publish its DNS records.
|
|
41
|
+
*/
|
|
42
|
+
export type WebsiteDomain =
|
|
43
|
+
| string
|
|
44
|
+
| {
|
|
45
|
+
/** Hostname, e.g. `www.example.com`. */
|
|
46
|
+
name: string;
|
|
47
|
+
/**
|
|
48
|
+
* DNS host that publishes the hostname's records (see
|
|
49
|
+
* [DNS Adapters](/infrastructure-as-code/dns-adapters)), e.g.
|
|
50
|
+
* `Cloudflare.DNS.Adapter()`: the ACME challenge `CNAME` Fly lists in
|
|
51
|
+
* the Certificate's `dnsRequirements`, and `A` / `AAAA` records at
|
|
52
|
+
* the site's addresses. Omitted: publish the records yourself.
|
|
53
|
+
*/
|
|
54
|
+
dns?: DNS.DnsConfig;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** The hostname of a {@link WebsiteDomain}. */
|
|
58
|
+
export const websiteDomainName = (domain: WebsiteDomain | undefined): string | undefined =>
|
|
59
|
+
domain === undefined || typeof domain === "string" ? domain : domain.name;
|
|
60
|
+
|
|
61
|
+
const acmeChallengeRecords = (
|
|
62
|
+
hostname: string,
|
|
63
|
+
requirements: CertificateDnsRequirements | undefined,
|
|
64
|
+
): DNS.DnsRecord[] => {
|
|
65
|
+
const challenge = requirements?.acmeChallenge;
|
|
66
|
+
if (challenge?.target === undefined || challenge.target.length === 0) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const expected = `_acme-challenge.${hostname}`;
|
|
70
|
+
const name =
|
|
71
|
+
challenge.name !== undefined &&
|
|
72
|
+
DNS.normalizeDnsName(challenge.name).endsWith(DNS.normalizeDnsName(hostname))
|
|
73
|
+
? challenge.name
|
|
74
|
+
: expected;
|
|
75
|
+
return [{ name, type: "CNAME", value: challenge.target }];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Publish a Fly website's records through `domain.dns`: the Certificate's
|
|
80
|
+
* ACME challenge `CNAME` (`CertificateValidation`) and `A` / `AAAA`
|
|
81
|
+
* records at the site's shared IPv4 plus any IPv6 Fly lists for the
|
|
82
|
+
* hostname (`Domain`). No-op for a plain hostname or an object without
|
|
83
|
+
* `dns`.
|
|
84
|
+
*/
|
|
85
|
+
export const publishWebsiteDomainDns = Effect.fn("Fly.Website.publishDomainDns")(function* (args: {
|
|
86
|
+
readonly domain: WebsiteDomain;
|
|
87
|
+
readonly certificate: Certificate;
|
|
88
|
+
readonly ip: IpAssignment;
|
|
89
|
+
}) {
|
|
90
|
+
if (typeof args.domain === "string" || args.domain.dns === undefined) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const dns = yield* DNS.resolve(args.domain.dns);
|
|
94
|
+
const name = args.domain.name;
|
|
95
|
+
yield* dns.records("CertificateValidation", {
|
|
96
|
+
records: Output.map(args.certificate.dnsRequirements, (requirements) =>
|
|
97
|
+
acmeChallengeRecords(name, requirements),
|
|
98
|
+
) as unknown as Input<DNS.DnsRecord[]>,
|
|
99
|
+
});
|
|
100
|
+
yield* dns.alias("Domain", {
|
|
101
|
+
name,
|
|
102
|
+
target: {
|
|
103
|
+
ipv4: Output.map(args.ip.ip, (ip) => [ip]) as unknown as Input<string[]>,
|
|
104
|
+
ipv6: Output.map(
|
|
105
|
+
args.certificate.dnsRequirements,
|
|
106
|
+
(requirements) => requirements?.aaaa ?? [],
|
|
107
|
+
) as unknown as Input<string[]>,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
36
112
|
/**
|
|
37
113
|
* Props shared by every Fly framework website composite.
|
|
38
114
|
*/
|
|
@@ -78,9 +154,15 @@ export interface FrameworkSiteProps {
|
|
|
78
154
|
dev?: ServerDevProps;
|
|
79
155
|
/**
|
|
80
156
|
* Optional custom hostname. Requests ACME (`Fly.Certificate`) on the
|
|
81
|
-
* App
|
|
157
|
+
* App and `url` becomes `https://<domain>`.
|
|
158
|
+
*
|
|
159
|
+
* A string (or `{ name }`) leaves DNS to you. Set `dns` (e.g.
|
|
160
|
+
* `{ name: "www.example.com", dns: Cloudflare.DNS.Adapter() }`) to
|
|
161
|
+
* publish the ACME challenge `CNAME` and the `A` / `AAAA` records
|
|
162
|
+
* through that DNS host. See
|
|
163
|
+
* [DNS Adapters](/infrastructure-as-code/dns-adapters).
|
|
82
164
|
*/
|
|
83
|
-
domain?:
|
|
165
|
+
domain?: WebsiteDomain;
|
|
84
166
|
/**
|
|
85
167
|
* User-defined tags. Accepted for API parity; Fly Services do not
|
|
86
168
|
* surface resource tags.
|
|
@@ -327,16 +409,20 @@ const runFrameworkSite = Effect.fn("Fly.Website.FrameworkSite")(function* (
|
|
|
327
409
|
: undefined,
|
|
328
410
|
});
|
|
329
411
|
|
|
412
|
+
const domainName = websiteDomainName(props.domain);
|
|
330
413
|
const certificate =
|
|
331
|
-
|
|
414
|
+
domainName !== undefined
|
|
332
415
|
? yield* Certificate("Certificate", {
|
|
333
416
|
app,
|
|
334
|
-
hostname:
|
|
417
|
+
hostname: domainName,
|
|
335
418
|
kind: "acme",
|
|
336
419
|
})
|
|
337
420
|
: undefined;
|
|
421
|
+
if (props.domain !== undefined && certificate !== undefined) {
|
|
422
|
+
yield* publishWebsiteDomainDns({ domain: props.domain, certificate, ip });
|
|
423
|
+
}
|
|
338
424
|
|
|
339
|
-
const url =
|
|
425
|
+
const url = domainName !== undefined ? `https://${domainName}` : app.url;
|
|
340
426
|
|
|
341
427
|
return { url, app, service, ip, certificate };
|
|
342
428
|
});
|
|
@@ -15,9 +15,12 @@ import { IpAssignment } from "../IpAssignment.ts";
|
|
|
15
15
|
import { Service, type ServiceProps } from "../Service.ts";
|
|
16
16
|
import {
|
|
17
17
|
type FrameworkSite,
|
|
18
|
+
publishWebsiteDomainDns,
|
|
18
19
|
type Ref,
|
|
19
20
|
type WebsiteAssetsProps,
|
|
21
|
+
type WebsiteDomain,
|
|
20
22
|
staticConfigFromAssets,
|
|
23
|
+
websiteDomainName,
|
|
21
24
|
} from "./FrameworkSite.ts";
|
|
22
25
|
|
|
23
26
|
const DEFAULT_PORT = 3000;
|
|
@@ -69,9 +72,16 @@ export interface StaticSiteProps {
|
|
|
69
72
|
*/
|
|
70
73
|
app?: Ref<App>;
|
|
71
74
|
/**
|
|
72
|
-
* Optional custom hostname. Requests ACME (`Fly.Certificate`) on the App
|
|
75
|
+
* Optional custom hostname. Requests ACME (`Fly.Certificate`) on the App
|
|
76
|
+
* and `url` becomes `https://<domain>`.
|
|
77
|
+
*
|
|
78
|
+
* A string (or `{ name }`) leaves DNS to you. Set `dns` (e.g.
|
|
79
|
+
* `{ name: "www.example.com", dns: Cloudflare.DNS.Adapter() }`) to
|
|
80
|
+
* publish the ACME challenge `CNAME` and the `A` / `AAAA` records
|
|
81
|
+
* through that DNS host. See
|
|
82
|
+
* [DNS Adapters](/infrastructure-as-code/dns-adapters).
|
|
73
83
|
*/
|
|
74
|
-
domain?:
|
|
84
|
+
domain?: WebsiteDomain;
|
|
75
85
|
/**
|
|
76
86
|
* User-defined tags. Accepted for API parity; Fly Services do not
|
|
77
87
|
* surface resource tags.
|
|
@@ -262,14 +272,24 @@ export const StaticSite = (id: string, props: StaticSiteProps) =>
|
|
|
262
272
|
],
|
|
263
273
|
});
|
|
264
274
|
|
|
275
|
+
const domainName = websiteDomainName(props.domain);
|
|
265
276
|
const certificate =
|
|
266
|
-
|
|
267
|
-
? yield* Certificate("Certificate", {
|
|
268
|
-
|
|
269
|
-
|
|
277
|
+
domainName !== undefined
|
|
278
|
+
? yield* Certificate("Certificate", {
|
|
279
|
+
app,
|
|
280
|
+
hostname: domainName,
|
|
281
|
+
kind: "acme",
|
|
282
|
+
}).pipe(Namespace.push(id))
|
|
270
283
|
: undefined;
|
|
284
|
+
if (props.domain !== undefined && certificate !== undefined) {
|
|
285
|
+
yield* publishWebsiteDomainDns({
|
|
286
|
+
domain: props.domain,
|
|
287
|
+
certificate,
|
|
288
|
+
ip,
|
|
289
|
+
}).pipe(Namespace.push(id));
|
|
290
|
+
}
|
|
271
291
|
|
|
272
|
-
const url =
|
|
292
|
+
const url = domainName !== undefined ? `https://${domainName}` : app.url;
|
|
273
293
|
|
|
274
294
|
return { url, app, service, ip, certificate };
|
|
275
295
|
}).pipe(Effect.orDie);
|
package/src/Website/Vite.ts
CHANGED
|
@@ -77,6 +77,15 @@ export interface ViteProps extends FrameworkSiteProps {
|
|
|
77
77
|
* });
|
|
78
78
|
* ```
|
|
79
79
|
*
|
|
80
|
+
* **Example:** Publish DNS in Cloudflare
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // The ACME challenge CNAME and A/AAAA records are published in the
|
|
83
|
+
* // Cloudflare zone. Requires Cloudflare.providers() in the stack.
|
|
84
|
+
* const site = yield* Fly.Website.Vite("Web", {
|
|
85
|
+
* domain: { name: "app.example.com", dns: Cloudflare.DNS.Adapter() },
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
80
89
|
* ### Build Configuration
|
|
81
90
|
* **Example:** Custom Output Directory and Base Path
|
|
82
91
|
* ```typescript
|
|
@@ -43,7 +43,7 @@ export const certificatesWriteClient = (
|
|
|
43
43
|
return yield* authorize(
|
|
44
44
|
machines
|
|
45
45
|
.createAppAcmeCertificate({ app_name: yield* appName, hostname })
|
|
46
|
-
.pipe(Effect.catchTag("Conflict", () => Effect.
|
|
46
|
+
.pipe(Effect.catchTag("Conflict", () => Effect.undefined)),
|
|
47
47
|
);
|
|
48
48
|
}),
|
|
49
49
|
upload: Effect.fn("Fly.Certificates.upload")(function* (request) {
|
|
@@ -78,7 +78,7 @@ export const certificatesWriteClient = (
|
|
|
78
78
|
return yield* authorize(
|
|
79
79
|
machines
|
|
80
80
|
.getAppCertificate({ app_name: yield* appName, hostname })
|
|
81
|
-
.pipe(Effect.catchTag("NotFound", () => Effect.
|
|
81
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.undefined)),
|
|
82
82
|
);
|
|
83
83
|
}),
|
|
84
84
|
remove: Effect.fn("Fly.Certificates.remove")(function* (hostname) {
|