alchemy 2.0.0-beta.3 → 2.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/{alchemy-effect.js → alchemy.js} +170 -170
- package/bin/alchemy.js.map +1 -0
- package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
- package/lib/cli/index.d.ts.map +1 -1
- package/lib/cli/index.js +167 -167
- package/lib/cli/index.js.map +1 -1
- package/package.json +38 -38
- package/src/AWS/AGENTS.md +10 -10
- package/src/AWS/Assets.ts +1 -1
- package/src/AWS/AuthProvider.ts +392 -0
- package/src/AWS/Credentials.ts +0 -1
- package/src/AWS/DynamoDB/Table.ts +63 -10
- package/src/AWS/EC2/VpcEndpoint.ts +4 -4
- package/src/AWS/EC2/hosted.ts +1 -1
- package/src/AWS/ECS/Task.ts +1 -1
- package/src/AWS/Kinesis/Stream.ts +44 -8
- package/src/AWS/Lambda/Function.ts +218 -10
- package/src/AWS/S3/Bucket.ts +26 -19
- package/src/AWS/S3/BucketNotifications.ts +1 -1
- package/src/AWS/SNS/Topic.ts +47 -10
- package/src/AWS/SQS/Queue.ts +66 -0
- package/src/Auth/AuthProvider.ts +33 -0
- package/src/Auth/Credentials.ts +56 -0
- package/src/Auth/Env.ts +45 -0
- package/src/Auth/Profile.ts +72 -0
- package/src/Auth/index.ts +2 -0
- package/src/Cloudflare/Auth/AuthProvider.ts +670 -0
- package/src/Cloudflare/Auth/OAuthClient.ts +315 -0
- package/src/Cloudflare/Container/ContainerApplication.ts +3 -3
- package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
- package/src/Cloudflare/Container/StartContainer.ts +1 -1
- package/src/Cloudflare/Providers.ts +1 -6
- package/src/Cloudflare/R2/R2Bucket.ts +2 -2
- package/src/Cloudflare/Website/StaticSite.ts +44 -15
- package/src/Cloudflare/Website/Vite.ts +34 -12
- package/src/Cloudflare/Workers/Assets.ts +170 -207
- package/src/Cloudflare/Workers/DurableObjectNamespace.ts +230 -370
- package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
- package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
- package/src/Cloudflare/Workers/InferEnv.ts +11 -7
- package/src/Cloudflare/Workers/Rpc.ts +13 -2
- package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
- package/src/Cloudflare/Workers/WebSocket.ts +1 -1
- package/src/Cloudflare/Workers/Worker.ts +261 -61
- package/src/Cloudflare/Workers/index.ts +3 -0
- package/src/Construct.ts +2 -2
- package/src/GitHub/Secret.ts +52 -7
- package/src/GitHub/Variable.ts +45 -2
- package/src/Kubernetes/client.ts +2 -2
- package/src/Output.ts +1 -1
- package/src/Platform.ts +10 -5
- package/src/Resource.ts +4 -4
- package/src/Test/Vitest.ts +4 -3
- package/src/Util/Clank.ts +77 -0
- package/src/Util/PlatformServices.ts +21 -0
- package/src/Util/dedent.ts +4 -1
- package/bin/alchemy-effect.js.map +0 -1
- package/src/Daemon/Client.ts +0 -116
- package/src/Daemon/Config.ts +0 -28
- package/src/Daemon/Errors.ts +0 -48
- package/src/Daemon/Lock.ts +0 -162
- package/src/Daemon/ProcessRegistry.ts +0 -284
- package/src/Daemon/RpcSchema.ts +0 -43
- package/src/Daemon/RpcServer.ts +0 -231
- package/src/Daemon/index.ts +0 -27
- package/src/Spawn.ts +0 -23
- /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import http from "node:http";
|
|
5
|
+
import {
|
|
6
|
+
OAUTH_CLIENT_ID,
|
|
7
|
+
OAUTH_ENDPOINTS,
|
|
8
|
+
OAUTH_REDIRECT_URI,
|
|
9
|
+
} from "./AuthProvider.ts";
|
|
10
|
+
|
|
11
|
+
export class OAuthError extends Data.TaggedError("OAuthError")<{
|
|
12
|
+
error: string;
|
|
13
|
+
errorDescription: string;
|
|
14
|
+
}> {}
|
|
15
|
+
|
|
16
|
+
export interface OAuthCredentials {
|
|
17
|
+
type: "oauth";
|
|
18
|
+
access: string;
|
|
19
|
+
refresh: string;
|
|
20
|
+
expires: number;
|
|
21
|
+
scopes: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Authorization {
|
|
25
|
+
url: string;
|
|
26
|
+
state: string;
|
|
27
|
+
verifier: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function generateState(length = 32): string {
|
|
31
|
+
return crypto.randomBytes(length).toString("base64url");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function generatePKCE(length = 96): {
|
|
35
|
+
verifier: string;
|
|
36
|
+
challenge: string;
|
|
37
|
+
} {
|
|
38
|
+
const verifier = crypto.randomBytes(length).toString("base64url");
|
|
39
|
+
const challenge = crypto
|
|
40
|
+
.createHash("sha256")
|
|
41
|
+
.update(verifier)
|
|
42
|
+
.digest("base64url");
|
|
43
|
+
return { verifier, challenge };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractCredentials(json: {
|
|
47
|
+
access_token: string;
|
|
48
|
+
refresh_token: string;
|
|
49
|
+
expires_in: number;
|
|
50
|
+
scope: string;
|
|
51
|
+
}): OAuthCredentials {
|
|
52
|
+
return {
|
|
53
|
+
type: "oauth",
|
|
54
|
+
access: json.access_token,
|
|
55
|
+
refresh: json.refresh_token,
|
|
56
|
+
expires: Date.now() + json.expires_in * 1000,
|
|
57
|
+
scopes: json.scope.split(" "),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const tokenRequest = (
|
|
62
|
+
body: Record<string, string>,
|
|
63
|
+
): Effect.Effect<OAuthCredentials, OAuthError> =>
|
|
64
|
+
Effect.gen(function* () {
|
|
65
|
+
const res = yield* Effect.tryPromise({
|
|
66
|
+
try: () =>
|
|
67
|
+
fetch(OAUTH_ENDPOINTS.token, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
Accept: "application/json",
|
|
71
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
72
|
+
},
|
|
73
|
+
body: new URLSearchParams(body).toString(),
|
|
74
|
+
}),
|
|
75
|
+
catch: (err) =>
|
|
76
|
+
new OAuthError({
|
|
77
|
+
error: "network_error",
|
|
78
|
+
errorDescription: `Token request failed: ${err}`,
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
const json = yield* Effect.tryPromise({
|
|
84
|
+
try: () =>
|
|
85
|
+
res.json() as Promise<{ error: string; error_description: string }>,
|
|
86
|
+
catch: () =>
|
|
87
|
+
new OAuthError({
|
|
88
|
+
error: "parse_error",
|
|
89
|
+
errorDescription: `Token endpoint returned ${res.status}`,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
return yield* new OAuthError({
|
|
93
|
+
error: json.error,
|
|
94
|
+
errorDescription: json.error_description,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const json = yield* Effect.tryPromise({
|
|
99
|
+
try: () =>
|
|
100
|
+
res.json() as Promise<{
|
|
101
|
+
access_token: string;
|
|
102
|
+
refresh_token: string;
|
|
103
|
+
expires_in: number;
|
|
104
|
+
scope: string;
|
|
105
|
+
}>,
|
|
106
|
+
catch: () =>
|
|
107
|
+
new OAuthError({
|
|
108
|
+
error: "parse_error",
|
|
109
|
+
errorDescription: "Failed to parse token response",
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
return extractCredentials(json);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generate an authorization URL with PKCE for the given scopes.
|
|
117
|
+
*/
|
|
118
|
+
export function authorize(scopes: string[]): Authorization {
|
|
119
|
+
const state = generateState();
|
|
120
|
+
const pkce = generatePKCE();
|
|
121
|
+
const url = new URL(OAUTH_ENDPOINTS.authorize);
|
|
122
|
+
url.searchParams.set("client_id", OAUTH_CLIENT_ID);
|
|
123
|
+
url.searchParams.set("redirect_uri", OAUTH_REDIRECT_URI);
|
|
124
|
+
url.searchParams.set("response_type", "code");
|
|
125
|
+
url.searchParams.set("scope", scopes.join(" "));
|
|
126
|
+
url.searchParams.set("state", state);
|
|
127
|
+
url.searchParams.set("code_challenge", pkce.challenge);
|
|
128
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
129
|
+
return { url: url.toString(), state, verifier: pkce.verifier };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Exchange an authorization code for OAuth credentials.
|
|
134
|
+
*/
|
|
135
|
+
export const exchange = (
|
|
136
|
+
code: string,
|
|
137
|
+
verifier: string,
|
|
138
|
+
): Effect.Effect<OAuthCredentials, OAuthError> =>
|
|
139
|
+
tokenRequest({
|
|
140
|
+
grant_type: "authorization_code",
|
|
141
|
+
code,
|
|
142
|
+
code_verifier: verifier,
|
|
143
|
+
client_id: OAUTH_CLIENT_ID,
|
|
144
|
+
redirect_uri: OAUTH_REDIRECT_URI,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Refresh expired OAuth credentials.
|
|
149
|
+
*/
|
|
150
|
+
export const refresh = (
|
|
151
|
+
credentials: OAuthCredentials,
|
|
152
|
+
): Effect.Effect<OAuthCredentials, OAuthError> =>
|
|
153
|
+
tokenRequest({
|
|
154
|
+
grant_type: "refresh_token",
|
|
155
|
+
refresh_token: credentials.refresh,
|
|
156
|
+
client_id: OAUTH_CLIENT_ID,
|
|
157
|
+
redirect_uri: OAUTH_REDIRECT_URI,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Revoke OAuth credentials.
|
|
162
|
+
*/
|
|
163
|
+
export const revoke = (
|
|
164
|
+
credentials: OAuthCredentials,
|
|
165
|
+
): Effect.Effect<void, OAuthError> =>
|
|
166
|
+
Effect.gen(function* () {
|
|
167
|
+
yield* Effect.tryPromise({
|
|
168
|
+
try: () =>
|
|
169
|
+
fetch(OAUTH_ENDPOINTS.revoke, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: {
|
|
172
|
+
Accept: "application/json",
|
|
173
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
174
|
+
},
|
|
175
|
+
body: new URLSearchParams({
|
|
176
|
+
refresh_token: credentials.refresh,
|
|
177
|
+
client_id: OAUTH_CLIENT_ID,
|
|
178
|
+
redirect_uri: OAUTH_REDIRECT_URI,
|
|
179
|
+
}).toString(),
|
|
180
|
+
}),
|
|
181
|
+
catch: (err) =>
|
|
182
|
+
new OAuthError({
|
|
183
|
+
error: "network_error",
|
|
184
|
+
errorDescription: `Revoke request failed: ${err}`,
|
|
185
|
+
}),
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Start a local HTTP server to listen for the OAuth callback, exchange
|
|
191
|
+
* the authorization code, and return the credentials.
|
|
192
|
+
*
|
|
193
|
+
* Times out after 5 minutes.
|
|
194
|
+
*/
|
|
195
|
+
export const callback = (
|
|
196
|
+
authorization: Authorization,
|
|
197
|
+
): Effect.Effect<OAuthCredentials, OAuthError> =>
|
|
198
|
+
Effect.tryPromise({
|
|
199
|
+
try: () => callbackPromise(authorization),
|
|
200
|
+
catch: (err) => {
|
|
201
|
+
if (err instanceof OAuthError) return err;
|
|
202
|
+
return new OAuthError({
|
|
203
|
+
error: "callback_error",
|
|
204
|
+
errorDescription: `OAuth callback failed: ${err}`,
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
function callbackPromise(
|
|
210
|
+
authorization: Authorization,
|
|
211
|
+
): Promise<OAuthCredentials> {
|
|
212
|
+
const { pathname, port } = new URL(OAUTH_REDIRECT_URI);
|
|
213
|
+
|
|
214
|
+
return new Promise<OAuthCredentials>((resolve, reject) => {
|
|
215
|
+
const server = http.createServer(async (req, res) => {
|
|
216
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
217
|
+
|
|
218
|
+
if (url.pathname !== pathname) {
|
|
219
|
+
res.statusCode = 404;
|
|
220
|
+
res.end("Not Found");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const error = url.searchParams.get("error");
|
|
225
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
226
|
+
if (error) {
|
|
227
|
+
res.writeHead(302, { Location: "https://alchemy.run/auth/error" });
|
|
228
|
+
res.end();
|
|
229
|
+
cleanup();
|
|
230
|
+
reject(
|
|
231
|
+
new OAuthError({
|
|
232
|
+
error,
|
|
233
|
+
errorDescription: errorDescription ?? "An unknown error occurred.",
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const code = url.searchParams.get("code");
|
|
240
|
+
const state = url.searchParams.get("state");
|
|
241
|
+
if (!code || !state) {
|
|
242
|
+
res.writeHead(302, { Location: "https://alchemy.run/auth/error" });
|
|
243
|
+
res.end();
|
|
244
|
+
cleanup();
|
|
245
|
+
reject(
|
|
246
|
+
new OAuthError({
|
|
247
|
+
error: "invalid_request",
|
|
248
|
+
errorDescription: "Missing code or state",
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (state !== authorization.state) {
|
|
255
|
+
res.writeHead(302, { Location: "https://alchemy.run/auth/error" });
|
|
256
|
+
res.end();
|
|
257
|
+
cleanup();
|
|
258
|
+
reject(
|
|
259
|
+
new OAuthError({
|
|
260
|
+
error: "invalid_request",
|
|
261
|
+
errorDescription: "Invalid state",
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const credentials = await Effect.runPromise(
|
|
269
|
+
exchange(code, authorization.verifier),
|
|
270
|
+
);
|
|
271
|
+
res.writeHead(302, {
|
|
272
|
+
Location: "https://alchemy.run/auth/success",
|
|
273
|
+
});
|
|
274
|
+
res.end();
|
|
275
|
+
cleanup();
|
|
276
|
+
resolve(credentials);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
res.writeHead(302, { Location: "https://alchemy.run/auth/error" });
|
|
279
|
+
res.end();
|
|
280
|
+
cleanup();
|
|
281
|
+
reject(err);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const timeout = setTimeout(
|
|
286
|
+
() => {
|
|
287
|
+
cleanup();
|
|
288
|
+
reject(
|
|
289
|
+
new OAuthError({
|
|
290
|
+
error: "timeout",
|
|
291
|
+
errorDescription: "The authorization process timed out.",
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
},
|
|
295
|
+
5 * 60 * 1000,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
function cleanup() {
|
|
299
|
+
clearTimeout(timeout);
|
|
300
|
+
server.close();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
server.on("error", (err) => {
|
|
304
|
+
cleanup();
|
|
305
|
+
reject(
|
|
306
|
+
new OAuthError({
|
|
307
|
+
error: "server_error",
|
|
308
|
+
errorDescription: `Failed to start callback server: ${err.message}`,
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
server.listen(Number(port));
|
|
314
|
+
});
|
|
315
|
+
}
|
|
@@ -483,16 +483,16 @@ ${
|
|
|
483
483
|
runtime === "bun"
|
|
484
484
|
? `
|
|
485
485
|
import { BunServices } from "@effect/platform-bun";
|
|
486
|
-
import { BunHttpServer } from "alchemy
|
|
486
|
+
import { BunHttpServer } from "alchemy/Http";
|
|
487
487
|
const HttpServer = BunHttpServer;
|
|
488
488
|
`
|
|
489
489
|
: `
|
|
490
490
|
import { NodeServices } from "@effect/platform-node";
|
|
491
|
-
import { NodeHttpServer } from "alchemy
|
|
491
|
+
import { NodeHttpServer } from "alchemy/Http";
|
|
492
492
|
const HttpServer = NodeHttpServer;
|
|
493
493
|
`
|
|
494
494
|
}
|
|
495
|
-
import { Stack } from "alchemy
|
|
495
|
+
import { Stack } from "alchemy/Stack";
|
|
496
496
|
import * as Effect from "effect/Effect";
|
|
497
497
|
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
498
498
|
import * as Layer from "effect/Layer";
|
|
@@ -5,10 +5,8 @@ import {
|
|
|
5
5
|
toCloudflareFetcher,
|
|
6
6
|
type Fetcher,
|
|
7
7
|
} from "../Fetcher.ts";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
DurableObjectState,
|
|
11
|
-
} from "../Workers/DurableObjectNamespace.ts";
|
|
8
|
+
import { DurableObjectNamespace } from "../Workers/DurableObjectNamespace.ts";
|
|
9
|
+
import { DurableObjectState } from "../Workers/DurableObjectState.ts";
|
|
12
10
|
import { Worker } from "../Workers/Worker.ts";
|
|
13
11
|
import type { Container } from "./Container.ts";
|
|
14
12
|
import type { ContainerApplication } from "./ContainerApplication.ts";
|
|
@@ -6,7 +6,7 @@ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
|
|
|
6
6
|
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
7
7
|
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
8
8
|
import { type Fetcher } from "../Fetcher.ts";
|
|
9
|
-
import { DurableObjectState } from "../Workers/
|
|
9
|
+
import { DurableObjectState } from "../Workers/DurableObjectState.ts";
|
|
10
10
|
import { type Container, ContainerError } from "./Container.ts";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -10,7 +10,6 @@ import * as Containers from "./Container/index.ts";
|
|
|
10
10
|
import * as D1 from "./D1/index.ts";
|
|
11
11
|
import * as KV from "./KV/index.ts";
|
|
12
12
|
import * as R2 from "./R2/index.ts";
|
|
13
|
-
import * as Assets from "./Workers/Assets.ts";
|
|
14
13
|
import * as Workers from "./Workers/index.ts";
|
|
15
14
|
import * as Workflows from "./Workers/Workflow.ts";
|
|
16
15
|
|
|
@@ -60,11 +59,7 @@ export const providers = () =>
|
|
|
60
59
|
),
|
|
61
60
|
),
|
|
62
61
|
Layer.provideMerge(
|
|
63
|
-
Layer.mergeAll(
|
|
64
|
-
Assets.AssetsProvider(),
|
|
65
|
-
Build.CommandProvider(),
|
|
66
|
-
RandomProvider(),
|
|
67
|
-
),
|
|
62
|
+
Layer.mergeAll(Build.CommandProvider(), RandomProvider()),
|
|
68
63
|
),
|
|
69
64
|
Layer.provideMerge(
|
|
70
65
|
Layer.mergeAll(
|
|
@@ -6,7 +6,7 @@ import { createPhysicalName } from "../../PhysicalName.ts";
|
|
|
6
6
|
import * as Provider from "../../Provider.ts";
|
|
7
7
|
import { Resource } from "../../Resource.ts";
|
|
8
8
|
import { Account } from "../Account.ts";
|
|
9
|
-
import type
|
|
9
|
+
import type * as Cloudflare from "../Providers.ts";
|
|
10
10
|
import { R2BucketBinding } from "./R2BucketBinding.ts";
|
|
11
11
|
|
|
12
12
|
export type R2BucketName = string;
|
|
@@ -44,7 +44,7 @@ export type R2Bucket = Resource<
|
|
|
44
44
|
accountId: string;
|
|
45
45
|
},
|
|
46
46
|
never,
|
|
47
|
-
Providers
|
|
47
|
+
Cloudflare.Providers
|
|
48
48
|
>;
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -3,10 +3,17 @@ import { Command, type CommandProps } from "../../Build/Command.ts";
|
|
|
3
3
|
import type { InputProps } from "../../Input.ts";
|
|
4
4
|
import * as Namespace from "../../Namespace.ts";
|
|
5
5
|
import type { AssetsConfig } from "../Workers/Assets.ts";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Worker,
|
|
8
|
+
type WorkerAssetsConfig,
|
|
9
|
+
type WorkerBindingProps,
|
|
10
|
+
type WorkerProps,
|
|
11
|
+
} from "../Workers/Worker.ts";
|
|
7
12
|
|
|
8
|
-
export interface StaticSiteProps
|
|
9
|
-
extends
|
|
13
|
+
export interface StaticSiteProps<Bindings extends WorkerBindingProps = {}>
|
|
14
|
+
extends
|
|
15
|
+
Omit<WorkerProps<Bindings, WorkerAssetsConfig>, "assets">,
|
|
16
|
+
Omit<CommandProps, "env"> {
|
|
10
17
|
/**
|
|
11
18
|
* Optional configuration for static asset routing behavior.
|
|
12
19
|
* Supports `runWorkerFirst`, `htmlHandling`, `notFoundHandling`, etc.
|
|
@@ -76,19 +83,41 @@ export type StaticSite = ReturnType<typeof StaticSite>;
|
|
|
76
83
|
* });
|
|
77
84
|
* ```
|
|
78
85
|
*/
|
|
79
|
-
export const StaticSite =
|
|
86
|
+
export const StaticSite = <
|
|
87
|
+
const Bindings extends WorkerBindingProps = {},
|
|
88
|
+
Req = never,
|
|
89
|
+
>(
|
|
90
|
+
id: string,
|
|
91
|
+
propsEff:
|
|
92
|
+
| InputProps<StaticSiteProps<Bindings>>
|
|
93
|
+
| Effect.Effect<InputProps<StaticSiteProps<Bindings>>, never, Req>,
|
|
94
|
+
) =>
|
|
80
95
|
Effect.gen(function* () {
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
const props = Effect.isEffect(propsEff)
|
|
97
|
+
? propsEff
|
|
98
|
+
: Effect.succeed(propsEff);
|
|
83
99
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
// TODO(sam): local dev/hmr support?
|
|
101
|
+
const build = yield* Command(
|
|
102
|
+
"Build",
|
|
103
|
+
Effect.map(props, (props) => ({
|
|
104
|
+
command: props.command,
|
|
105
|
+
cwd: props.cwd,
|
|
106
|
+
memo: props.memo,
|
|
107
|
+
outdir: props.outdir,
|
|
108
|
+
env: props.env,
|
|
109
|
+
})),
|
|
110
|
+
);
|
|
92
111
|
|
|
93
|
-
return
|
|
112
|
+
return yield* Worker<Bindings, WorkerAssetsConfig, Req>(
|
|
113
|
+
"Worker",
|
|
114
|
+
Effect.map(props, (props) => ({
|
|
115
|
+
...props,
|
|
116
|
+
assets: {
|
|
117
|
+
path: build.outdir,
|
|
118
|
+
hash: build.hash,
|
|
119
|
+
config: props.assetsConfig,
|
|
120
|
+
},
|
|
121
|
+
})),
|
|
122
|
+
);
|
|
94
123
|
}).pipe(Namespace.push(id));
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
2
|
import type { MemoOptions } from "../../Build/Memo.ts";
|
|
3
|
-
import {
|
|
3
|
+
import type { InputProps } from "../../Input.ts";
|
|
4
|
+
import {
|
|
5
|
+
Worker,
|
|
6
|
+
type WorkerAssetsConfig,
|
|
7
|
+
type WorkerBindingProps,
|
|
8
|
+
type WorkerProps,
|
|
9
|
+
} from "../Workers/Worker.ts";
|
|
4
10
|
|
|
5
|
-
export interface ViteProps
|
|
11
|
+
export interface ViteProps<
|
|
12
|
+
Bindings extends WorkerBindingProps = {},
|
|
13
|
+
> extends Omit<WorkerProps<Bindings>, "vite" | "main"> {
|
|
6
14
|
/**
|
|
7
15
|
* Root directory passed to Vite's `root` option.
|
|
8
16
|
* Defaults to the current working directory (`process.cwd()`).
|
|
@@ -99,12 +107,26 @@ export interface ViteProps extends Omit<WorkerProps, "vite" | "main"> {
|
|
|
99
107
|
* });
|
|
100
108
|
* ```
|
|
101
109
|
*/
|
|
102
|
-
export const Vite =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
export const Vite = <
|
|
111
|
+
const Bindings extends WorkerBindingProps = {},
|
|
112
|
+
Req = never,
|
|
113
|
+
>(
|
|
114
|
+
id: string,
|
|
115
|
+
propsEff?:
|
|
116
|
+
| InputProps<ViteProps<Bindings>>
|
|
117
|
+
| Effect.Effect<InputProps<ViteProps<Bindings>>, never, Req>,
|
|
118
|
+
) =>
|
|
119
|
+
Worker<Bindings, WorkerAssetsConfig, Req>(
|
|
120
|
+
id,
|
|
121
|
+
Effect.map(
|
|
122
|
+
Effect.isEffect(propsEff) ? propsEff : Effect.succeed(propsEff),
|
|
123
|
+
(props) => ({
|
|
124
|
+
...props,
|
|
125
|
+
main: undefined!,
|
|
126
|
+
vite: {
|
|
127
|
+
rootDir: props?.rootDir,
|
|
128
|
+
memo: props?.memo,
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
),
|
|
132
|
+
);
|