alchemy 0.9.1 → 0.9.2
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/aws/function.d.ts +4 -0
- package/lib/aws/function.js +203 -1
- package/lib/cloudflare/api-error.js +8 -1
- package/package.json +1 -1
- package/src/aws/function.ts +247 -14
- package/src/cloudflare/api-error.ts +7 -2
package/lib/aws/function.d.ts
CHANGED
|
@@ -177,6 +177,10 @@ export interface Function extends Resource<"lambda::Function">, FunctionProps {
|
|
|
177
177
|
* ARN of the signing job
|
|
178
178
|
*/
|
|
179
179
|
signingJobArn?: string;
|
|
180
|
+
/**
|
|
181
|
+
* Function URL if configured
|
|
182
|
+
*/
|
|
183
|
+
functionUrl?: string;
|
|
180
184
|
}
|
|
181
185
|
/**
|
|
182
186
|
* AWS Lambda Function Resource
|
package/lib/aws/function.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Architecture, CreateFunctionCommand, DeleteFunctionCommand, GetFunctionCommand, GetFunctionConfigurationCommand, LambdaClient, ResourceNotFoundException, Runtime, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, } from "@aws-sdk/client-lambda";
|
|
1
|
+
import { AddPermissionCommand, Architecture, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteFunctionCommand, DeleteFunctionUrlConfigCommand, GetFunctionCommand, GetFunctionConfigurationCommand, GetFunctionUrlConfigCommand, LambdaClient, ResourceNotFoundException, Runtime, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, } from "@aws-sdk/client-lambda";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Resource } from "../resource";
|
|
@@ -62,12 +62,26 @@ export const Function = Resource("lambda::Function", async function (id, props)
|
|
|
62
62
|
const region = await resolveRegion(client);
|
|
63
63
|
const code = await zipCode(props.zipPath);
|
|
64
64
|
if (this.phase === "delete") {
|
|
65
|
+
// Delete function URL if it exists
|
|
66
|
+
if (this.output?.url) {
|
|
67
|
+
try {
|
|
68
|
+
await client.send(new DeleteFunctionUrlConfigCommand({
|
|
69
|
+
FunctionName: props.functionName,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (error.name !== "ResourceNotFoundException") {
|
|
74
|
+
console.warn("Failed to delete function URL:", error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
65
78
|
await ignore(ResourceNotFoundException.name, () => client.send(new DeleteFunctionCommand({
|
|
66
79
|
FunctionName: props.functionName,
|
|
67
80
|
})));
|
|
68
81
|
return this.destroy();
|
|
69
82
|
}
|
|
70
83
|
else {
|
|
84
|
+
let functionUrl;
|
|
71
85
|
try {
|
|
72
86
|
// Check if function exists
|
|
73
87
|
await client.send(new GetFunctionCommand({
|
|
@@ -98,6 +112,139 @@ export const Function = Resource("lambda::Function", async function (id, props)
|
|
|
98
112
|
}));
|
|
99
113
|
// Wait for configuration update to stabilize
|
|
100
114
|
await waitForFunctionStabilization(client, props.functionName);
|
|
115
|
+
// Handle URL configuration
|
|
116
|
+
if (props.url) {
|
|
117
|
+
try {
|
|
118
|
+
// Check if URL config exists already
|
|
119
|
+
const urlConfig = await client.send(new GetFunctionUrlConfigCommand({
|
|
120
|
+
FunctionName: props.functionName,
|
|
121
|
+
}));
|
|
122
|
+
// Update URL configuration if it exists
|
|
123
|
+
if (urlConfig) {
|
|
124
|
+
const updateResult = await client.send(new UpdateFunctionUrlConfigCommand({
|
|
125
|
+
FunctionName: props.functionName,
|
|
126
|
+
AuthType: props.url.authType || "NONE",
|
|
127
|
+
Cors: props.url.cors
|
|
128
|
+
? {
|
|
129
|
+
AllowCredentials: props.url.cors.allowCredentials,
|
|
130
|
+
AllowHeaders: props.url.cors.allowHeaders,
|
|
131
|
+
AllowMethods: props.url.cors.allowMethods,
|
|
132
|
+
AllowOrigins: props.url.cors.allowOrigins,
|
|
133
|
+
ExposeHeaders: props.url.cors.exposeHeaders,
|
|
134
|
+
MaxAge: props.url.cors.maxAge,
|
|
135
|
+
}
|
|
136
|
+
: undefined,
|
|
137
|
+
}));
|
|
138
|
+
functionUrl = updateResult.FunctionUrl;
|
|
139
|
+
// Add public access permission for function URL
|
|
140
|
+
if (props.url.authType === "NONE") {
|
|
141
|
+
try {
|
|
142
|
+
await client.send(new AddPermissionCommand({
|
|
143
|
+
FunctionName: props.functionName,
|
|
144
|
+
StatementId: "FunctionURLAllowPublicAccess",
|
|
145
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
146
|
+
Principal: "*",
|
|
147
|
+
FunctionUrlAuthType: "NONE",
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
catch (permError) {
|
|
151
|
+
if (!permError.message?.includes("already exists")) {
|
|
152
|
+
console.warn("Error adding URL permission:", permError);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Create URL configuration if it doesn't exist
|
|
159
|
+
const createResult = await client.send(new CreateFunctionUrlConfigCommand({
|
|
160
|
+
FunctionName: props.functionName,
|
|
161
|
+
AuthType: props.url.authType || "NONE",
|
|
162
|
+
Cors: props.url.cors
|
|
163
|
+
? {
|
|
164
|
+
AllowCredentials: props.url.cors.allowCredentials,
|
|
165
|
+
AllowHeaders: props.url.cors.allowHeaders,
|
|
166
|
+
AllowMethods: props.url.cors.allowMethods,
|
|
167
|
+
AllowOrigins: props.url.cors.allowOrigins,
|
|
168
|
+
ExposeHeaders: props.url.cors.exposeHeaders,
|
|
169
|
+
MaxAge: props.url.cors.maxAge,
|
|
170
|
+
}
|
|
171
|
+
: undefined,
|
|
172
|
+
}));
|
|
173
|
+
functionUrl = createResult.FunctionUrl;
|
|
174
|
+
// Add public access permission for function URL
|
|
175
|
+
if (props.url.authType === "NONE") {
|
|
176
|
+
try {
|
|
177
|
+
await client.send(new AddPermissionCommand({
|
|
178
|
+
FunctionName: props.functionName,
|
|
179
|
+
StatementId: "FunctionURLAllowPublicAccess",
|
|
180
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
181
|
+
Principal: "*",
|
|
182
|
+
FunctionUrlAuthType: "NONE",
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
catch (permError) {
|
|
186
|
+
if (!permError.message?.includes("already exists")) {
|
|
187
|
+
console.warn("Error adding URL permission:", permError);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (error.name === "ResourceNotFoundException") {
|
|
195
|
+
// Create URL configuration if it doesn't exist
|
|
196
|
+
const createResult = await client.send(new CreateFunctionUrlConfigCommand({
|
|
197
|
+
FunctionName: props.functionName,
|
|
198
|
+
AuthType: props.url.authType || "NONE",
|
|
199
|
+
Cors: props.url.cors
|
|
200
|
+
? {
|
|
201
|
+
AllowCredentials: props.url.cors.allowCredentials,
|
|
202
|
+
AllowHeaders: props.url.cors.allowHeaders,
|
|
203
|
+
AllowMethods: props.url.cors.allowMethods,
|
|
204
|
+
AllowOrigins: props.url.cors.allowOrigins,
|
|
205
|
+
ExposeHeaders: props.url.cors.exposeHeaders,
|
|
206
|
+
MaxAge: props.url.cors.maxAge,
|
|
207
|
+
}
|
|
208
|
+
: undefined,
|
|
209
|
+
}));
|
|
210
|
+
functionUrl = createResult.FunctionUrl;
|
|
211
|
+
// Add public access permission for function URL
|
|
212
|
+
if (props.url.authType === "NONE") {
|
|
213
|
+
try {
|
|
214
|
+
await client.send(new AddPermissionCommand({
|
|
215
|
+
FunctionName: props.functionName,
|
|
216
|
+
StatementId: "FunctionURLAllowPublicAccess",
|
|
217
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
218
|
+
Principal: "*",
|
|
219
|
+
FunctionUrlAuthType: "NONE",
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
catch (permError) {
|
|
223
|
+
if (!permError.message?.includes("already exists")) {
|
|
224
|
+
console.warn("Error adding URL permission:", permError);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (this.output?.url) {
|
|
235
|
+
// Remove URL config if it was previously set but not in current props
|
|
236
|
+
try {
|
|
237
|
+
await client.send(new DeleteFunctionUrlConfigCommand({
|
|
238
|
+
FunctionName: props.functionName,
|
|
239
|
+
}));
|
|
240
|
+
functionUrl = undefined;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (error.name !== "ResourceNotFoundException") {
|
|
244
|
+
console.warn("Failed to delete function URL:", error);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
101
248
|
}
|
|
102
249
|
}
|
|
103
250
|
catch (error) {
|
|
@@ -149,6 +296,46 @@ export const Function = Resource("lambda::Function", async function (id, props)
|
|
|
149
296
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
150
297
|
}
|
|
151
298
|
}
|
|
299
|
+
// Create URL configuration if needed
|
|
300
|
+
if (props.url) {
|
|
301
|
+
try {
|
|
302
|
+
const createResult = await client.send(new CreateFunctionUrlConfigCommand({
|
|
303
|
+
FunctionName: props.functionName,
|
|
304
|
+
AuthType: props.url.authType || "NONE",
|
|
305
|
+
Cors: props.url.cors
|
|
306
|
+
? {
|
|
307
|
+
AllowCredentials: props.url.cors.allowCredentials,
|
|
308
|
+
AllowHeaders: props.url.cors.allowHeaders,
|
|
309
|
+
AllowMethods: props.url.cors.allowMethods,
|
|
310
|
+
AllowOrigins: props.url.cors.allowOrigins,
|
|
311
|
+
ExposeHeaders: props.url.cors.exposeHeaders,
|
|
312
|
+
MaxAge: props.url.cors.maxAge,
|
|
313
|
+
}
|
|
314
|
+
: undefined,
|
|
315
|
+
}));
|
|
316
|
+
functionUrl = createResult.FunctionUrl;
|
|
317
|
+
// Add public access permission for function URL
|
|
318
|
+
if (props.url.authType === "NONE") {
|
|
319
|
+
try {
|
|
320
|
+
await client.send(new AddPermissionCommand({
|
|
321
|
+
FunctionName: props.functionName,
|
|
322
|
+
StatementId: "FunctionURLAllowPublicAccess",
|
|
323
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
324
|
+
Principal: "*",
|
|
325
|
+
FunctionUrlAuthType: "NONE",
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
catch (permError) {
|
|
329
|
+
if (!permError.message?.includes("already exists")) {
|
|
330
|
+
console.warn("Error adding URL permission:", permError);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
console.warn("Failed to create function URL:", error);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
152
339
|
}
|
|
153
340
|
else {
|
|
154
341
|
throw error;
|
|
@@ -163,6 +350,20 @@ export const Function = Resource("lambda::Function", async function (id, props)
|
|
|
163
350
|
FunctionName: props.functionName,
|
|
164
351
|
})),
|
|
165
352
|
]);
|
|
353
|
+
// Try to get function URL if it wasn't already retrieved and URL is configured
|
|
354
|
+
if (!functionUrl && (props.url || this.output?.url)) {
|
|
355
|
+
try {
|
|
356
|
+
const urlConfig = await client.send(new GetFunctionUrlConfigCommand({
|
|
357
|
+
FunctionName: props.functionName,
|
|
358
|
+
}));
|
|
359
|
+
functionUrl = urlConfig.FunctionUrl;
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
if (error.name !== "ResourceNotFoundException") {
|
|
363
|
+
console.warn("Failed to get function URL:", error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
166
367
|
return this({
|
|
167
368
|
...props,
|
|
168
369
|
arn: config.FunctionArn,
|
|
@@ -185,6 +386,7 @@ export const Function = Resource("lambda::Function", async function (id, props)
|
|
|
185
386
|
packageType: config.PackageType,
|
|
186
387
|
signingProfileVersionArn: config.SigningProfileVersionArn,
|
|
187
388
|
signingJobArn: config.SigningJobArn,
|
|
389
|
+
functionUrl: functionUrl,
|
|
188
390
|
});
|
|
189
391
|
}
|
|
190
392
|
});
|
|
@@ -38,7 +38,14 @@ export class CloudflareApiError extends Error {
|
|
|
38
38
|
* @returns Never returns - always throws an error
|
|
39
39
|
*/
|
|
40
40
|
export async function handleApiError(response, action, resourceType, resourceName) {
|
|
41
|
-
const
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
let json;
|
|
43
|
+
try {
|
|
44
|
+
json = JSON.parse(text);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
json = { errors: [{ message: text }] };
|
|
48
|
+
}
|
|
42
49
|
const errors = json?.errors || [
|
|
43
50
|
{ message: response.statusText },
|
|
44
51
|
];
|
package/package.json
CHANGED
package/src/aws/function.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AddPermissionCommand,
|
|
2
3
|
Architecture,
|
|
3
4
|
CreateFunctionCommand,
|
|
5
|
+
CreateFunctionUrlConfigCommand,
|
|
4
6
|
DeleteFunctionCommand,
|
|
7
|
+
DeleteFunctionUrlConfigCommand,
|
|
5
8
|
GetFunctionCommand,
|
|
6
9
|
GetFunctionConfigurationCommand,
|
|
10
|
+
GetFunctionUrlConfigCommand,
|
|
7
11
|
LambdaClient,
|
|
8
12
|
ResourceNotFoundException,
|
|
9
13
|
Runtime,
|
|
10
14
|
UpdateFunctionCodeCommand,
|
|
11
15
|
UpdateFunctionConfigurationCommand,
|
|
16
|
+
UpdateFunctionUrlConfigCommand,
|
|
12
17
|
} from "@aws-sdk/client-lambda";
|
|
13
18
|
import fs from "node:fs";
|
|
14
19
|
import path from "node:path";
|
|
@@ -229,6 +234,11 @@ export interface Function extends Resource<"lambda::Function">, FunctionProps {
|
|
|
229
234
|
* ARN of the signing job
|
|
230
235
|
*/
|
|
231
236
|
signingJobArn?: string;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Function URL if configured
|
|
240
|
+
*/
|
|
241
|
+
functionUrl?: string;
|
|
232
242
|
}
|
|
233
243
|
|
|
234
244
|
/**
|
|
@@ -294,22 +304,38 @@ export const Function = Resource(
|
|
|
294
304
|
const code = await zipCode(props.zipPath);
|
|
295
305
|
|
|
296
306
|
if (this.phase === "delete") {
|
|
307
|
+
// Delete function URL if it exists
|
|
308
|
+
if (this.output?.url) {
|
|
309
|
+
try {
|
|
310
|
+
await client.send(
|
|
311
|
+
new DeleteFunctionUrlConfigCommand({
|
|
312
|
+
FunctionName: props.functionName,
|
|
313
|
+
})
|
|
314
|
+
);
|
|
315
|
+
} catch (error: any) {
|
|
316
|
+
if (error.name !== "ResourceNotFoundException") {
|
|
317
|
+
console.warn("Failed to delete function URL:", error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
297
322
|
await ignore(ResourceNotFoundException.name, () =>
|
|
298
323
|
client.send(
|
|
299
324
|
new DeleteFunctionCommand({
|
|
300
325
|
FunctionName: props.functionName,
|
|
301
|
-
})
|
|
302
|
-
)
|
|
326
|
+
})
|
|
327
|
+
)
|
|
303
328
|
);
|
|
304
329
|
|
|
305
330
|
return this.destroy();
|
|
306
331
|
} else {
|
|
332
|
+
let functionUrl: string | undefined;
|
|
307
333
|
try {
|
|
308
334
|
// Check if function exists
|
|
309
335
|
await client.send(
|
|
310
336
|
new GetFunctionCommand({
|
|
311
337
|
FunctionName: props.functionName,
|
|
312
|
-
})
|
|
338
|
+
})
|
|
313
339
|
);
|
|
314
340
|
|
|
315
341
|
if (this.phase === "update") {
|
|
@@ -321,7 +347,7 @@ export const Function = Resource(
|
|
|
321
347
|
new UpdateFunctionCodeCommand({
|
|
322
348
|
FunctionName: props.functionName,
|
|
323
349
|
ZipFile: code,
|
|
324
|
-
})
|
|
350
|
+
})
|
|
325
351
|
);
|
|
326
352
|
|
|
327
353
|
// Wait for code update to stabilize
|
|
@@ -340,11 +366,157 @@ export const Function = Resource(
|
|
|
340
366
|
Environment: props.environment
|
|
341
367
|
? { Variables: props.environment }
|
|
342
368
|
: undefined,
|
|
343
|
-
})
|
|
369
|
+
})
|
|
344
370
|
);
|
|
345
371
|
|
|
346
372
|
// Wait for configuration update to stabilize
|
|
347
373
|
await waitForFunctionStabilization(client, props.functionName);
|
|
374
|
+
|
|
375
|
+
// Handle URL configuration
|
|
376
|
+
if (props.url) {
|
|
377
|
+
try {
|
|
378
|
+
// Check if URL config exists already
|
|
379
|
+
const urlConfig = await client.send(
|
|
380
|
+
new GetFunctionUrlConfigCommand({
|
|
381
|
+
FunctionName: props.functionName,
|
|
382
|
+
})
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// Update URL configuration if it exists
|
|
386
|
+
if (urlConfig) {
|
|
387
|
+
const updateResult = await client.send(
|
|
388
|
+
new UpdateFunctionUrlConfigCommand({
|
|
389
|
+
FunctionName: props.functionName,
|
|
390
|
+
AuthType: props.url.authType || "NONE",
|
|
391
|
+
Cors: props.url.cors
|
|
392
|
+
? {
|
|
393
|
+
AllowCredentials: props.url.cors.allowCredentials,
|
|
394
|
+
AllowHeaders: props.url.cors.allowHeaders,
|
|
395
|
+
AllowMethods: props.url.cors.allowMethods,
|
|
396
|
+
AllowOrigins: props.url.cors.allowOrigins,
|
|
397
|
+
ExposeHeaders: props.url.cors.exposeHeaders,
|
|
398
|
+
MaxAge: props.url.cors.maxAge,
|
|
399
|
+
}
|
|
400
|
+
: undefined,
|
|
401
|
+
})
|
|
402
|
+
);
|
|
403
|
+
functionUrl = updateResult.FunctionUrl;
|
|
404
|
+
|
|
405
|
+
// Add public access permission for function URL
|
|
406
|
+
if (props.url.authType === "NONE") {
|
|
407
|
+
try {
|
|
408
|
+
await client.send(
|
|
409
|
+
new AddPermissionCommand({
|
|
410
|
+
FunctionName: props.functionName,
|
|
411
|
+
StatementId: "FunctionURLAllowPublicAccess",
|
|
412
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
413
|
+
Principal: "*",
|
|
414
|
+
FunctionUrlAuthType: "NONE",
|
|
415
|
+
})
|
|
416
|
+
);
|
|
417
|
+
} catch (permError: any) {
|
|
418
|
+
if (!permError.message?.includes("already exists")) {
|
|
419
|
+
console.warn("Error adding URL permission:", permError);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
// Create URL configuration if it doesn't exist
|
|
425
|
+
const createResult = await client.send(
|
|
426
|
+
new CreateFunctionUrlConfigCommand({
|
|
427
|
+
FunctionName: props.functionName,
|
|
428
|
+
AuthType: props.url.authType || "NONE",
|
|
429
|
+
Cors: props.url.cors
|
|
430
|
+
? {
|
|
431
|
+
AllowCredentials: props.url.cors.allowCredentials,
|
|
432
|
+
AllowHeaders: props.url.cors.allowHeaders,
|
|
433
|
+
AllowMethods: props.url.cors.allowMethods,
|
|
434
|
+
AllowOrigins: props.url.cors.allowOrigins,
|
|
435
|
+
ExposeHeaders: props.url.cors.exposeHeaders,
|
|
436
|
+
MaxAge: props.url.cors.maxAge,
|
|
437
|
+
}
|
|
438
|
+
: undefined,
|
|
439
|
+
})
|
|
440
|
+
);
|
|
441
|
+
functionUrl = createResult.FunctionUrl;
|
|
442
|
+
|
|
443
|
+
// Add public access permission for function URL
|
|
444
|
+
if (props.url.authType === "NONE") {
|
|
445
|
+
try {
|
|
446
|
+
await client.send(
|
|
447
|
+
new AddPermissionCommand({
|
|
448
|
+
FunctionName: props.functionName,
|
|
449
|
+
StatementId: "FunctionURLAllowPublicAccess",
|
|
450
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
451
|
+
Principal: "*",
|
|
452
|
+
FunctionUrlAuthType: "NONE",
|
|
453
|
+
})
|
|
454
|
+
);
|
|
455
|
+
} catch (permError: any) {
|
|
456
|
+
if (!permError.message?.includes("already exists")) {
|
|
457
|
+
console.warn("Error adding URL permission:", permError);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch (error: any) {
|
|
463
|
+
if (error.name === "ResourceNotFoundException") {
|
|
464
|
+
// Create URL configuration if it doesn't exist
|
|
465
|
+
const createResult = await client.send(
|
|
466
|
+
new CreateFunctionUrlConfigCommand({
|
|
467
|
+
FunctionName: props.functionName,
|
|
468
|
+
AuthType: props.url.authType || "NONE",
|
|
469
|
+
Cors: props.url.cors
|
|
470
|
+
? {
|
|
471
|
+
AllowCredentials: props.url.cors.allowCredentials,
|
|
472
|
+
AllowHeaders: props.url.cors.allowHeaders,
|
|
473
|
+
AllowMethods: props.url.cors.allowMethods,
|
|
474
|
+
AllowOrigins: props.url.cors.allowOrigins,
|
|
475
|
+
ExposeHeaders: props.url.cors.exposeHeaders,
|
|
476
|
+
MaxAge: props.url.cors.maxAge,
|
|
477
|
+
}
|
|
478
|
+
: undefined,
|
|
479
|
+
})
|
|
480
|
+
);
|
|
481
|
+
functionUrl = createResult.FunctionUrl;
|
|
482
|
+
|
|
483
|
+
// Add public access permission for function URL
|
|
484
|
+
if (props.url.authType === "NONE") {
|
|
485
|
+
try {
|
|
486
|
+
await client.send(
|
|
487
|
+
new AddPermissionCommand({
|
|
488
|
+
FunctionName: props.functionName,
|
|
489
|
+
StatementId: "FunctionURLAllowPublicAccess",
|
|
490
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
491
|
+
Principal: "*",
|
|
492
|
+
FunctionUrlAuthType: "NONE",
|
|
493
|
+
})
|
|
494
|
+
);
|
|
495
|
+
} catch (permError: any) {
|
|
496
|
+
if (!permError.message?.includes("already exists")) {
|
|
497
|
+
console.warn("Error adding URL permission:", permError);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else if (this.output?.url) {
|
|
506
|
+
// Remove URL config if it was previously set but not in current props
|
|
507
|
+
try {
|
|
508
|
+
await client.send(
|
|
509
|
+
new DeleteFunctionUrlConfigCommand({
|
|
510
|
+
FunctionName: props.functionName,
|
|
511
|
+
})
|
|
512
|
+
);
|
|
513
|
+
functionUrl = undefined;
|
|
514
|
+
} catch (error: any) {
|
|
515
|
+
if (error.name !== "ResourceNotFoundException") {
|
|
516
|
+
console.warn("Failed to delete function URL:", error);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
348
520
|
}
|
|
349
521
|
} catch (error: any) {
|
|
350
522
|
if (error.name === "ResourceNotFoundException") {
|
|
@@ -371,7 +543,7 @@ export const Function = Resource(
|
|
|
371
543
|
? [props.architecture]
|
|
372
544
|
: [Architecture.x86_64],
|
|
373
545
|
Tags: props.tags,
|
|
374
|
-
})
|
|
546
|
+
})
|
|
375
547
|
);
|
|
376
548
|
break; // Success - exit retry loop
|
|
377
549
|
} catch (createError: any) {
|
|
@@ -384,7 +556,7 @@ export const Function = Resource(
|
|
|
384
556
|
|
|
385
557
|
if (Date.now() - startTime > 10000) {
|
|
386
558
|
throw new Error(
|
|
387
|
-
"Timeout waiting for IAM role to be assumable by Lambda after 10s"
|
|
559
|
+
"Timeout waiting for IAM role to be assumable by Lambda after 10s"
|
|
388
560
|
);
|
|
389
561
|
}
|
|
390
562
|
|
|
@@ -399,13 +571,57 @@ export const Function = Resource(
|
|
|
399
571
|
const config = await client.send(
|
|
400
572
|
new GetFunctionConfigurationCommand({
|
|
401
573
|
FunctionName: props.functionName,
|
|
402
|
-
})
|
|
574
|
+
})
|
|
403
575
|
);
|
|
404
576
|
isCreating = config.State === "Pending";
|
|
405
577
|
if (isCreating) {
|
|
406
578
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
407
579
|
}
|
|
408
580
|
}
|
|
581
|
+
|
|
582
|
+
// Create URL configuration if needed
|
|
583
|
+
if (props.url) {
|
|
584
|
+
try {
|
|
585
|
+
const createResult = await client.send(
|
|
586
|
+
new CreateFunctionUrlConfigCommand({
|
|
587
|
+
FunctionName: props.functionName,
|
|
588
|
+
AuthType: props.url.authType || "NONE",
|
|
589
|
+
Cors: props.url.cors
|
|
590
|
+
? {
|
|
591
|
+
AllowCredentials: props.url.cors.allowCredentials,
|
|
592
|
+
AllowHeaders: props.url.cors.allowHeaders,
|
|
593
|
+
AllowMethods: props.url.cors.allowMethods,
|
|
594
|
+
AllowOrigins: props.url.cors.allowOrigins,
|
|
595
|
+
ExposeHeaders: props.url.cors.exposeHeaders,
|
|
596
|
+
MaxAge: props.url.cors.maxAge,
|
|
597
|
+
}
|
|
598
|
+
: undefined,
|
|
599
|
+
})
|
|
600
|
+
);
|
|
601
|
+
functionUrl = createResult.FunctionUrl;
|
|
602
|
+
|
|
603
|
+
// Add public access permission for function URL
|
|
604
|
+
if (props.url.authType === "NONE") {
|
|
605
|
+
try {
|
|
606
|
+
await client.send(
|
|
607
|
+
new AddPermissionCommand({
|
|
608
|
+
FunctionName: props.functionName,
|
|
609
|
+
StatementId: "FunctionURLAllowPublicAccess",
|
|
610
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
611
|
+
Principal: "*",
|
|
612
|
+
FunctionUrlAuthType: "NONE",
|
|
613
|
+
})
|
|
614
|
+
);
|
|
615
|
+
} catch (permError: any) {
|
|
616
|
+
if (!permError.message?.includes("already exists")) {
|
|
617
|
+
console.warn("Error adding URL permission:", permError);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} catch (error) {
|
|
622
|
+
console.warn("Failed to create function URL:", error);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
409
625
|
} else {
|
|
410
626
|
throw error;
|
|
411
627
|
}
|
|
@@ -416,15 +632,31 @@ export const Function = Resource(
|
|
|
416
632
|
client.send(
|
|
417
633
|
new GetFunctionCommand({
|
|
418
634
|
FunctionName: props.functionName,
|
|
419
|
-
})
|
|
635
|
+
})
|
|
420
636
|
),
|
|
421
637
|
client.send(
|
|
422
638
|
new GetFunctionConfigurationCommand({
|
|
423
639
|
FunctionName: props.functionName,
|
|
424
|
-
})
|
|
640
|
+
})
|
|
425
641
|
),
|
|
426
642
|
]);
|
|
427
643
|
|
|
644
|
+
// Try to get function URL if it wasn't already retrieved and URL is configured
|
|
645
|
+
if (!functionUrl && (props.url || this.output?.url)) {
|
|
646
|
+
try {
|
|
647
|
+
const urlConfig = await client.send(
|
|
648
|
+
new GetFunctionUrlConfigCommand({
|
|
649
|
+
FunctionName: props.functionName,
|
|
650
|
+
})
|
|
651
|
+
);
|
|
652
|
+
functionUrl = urlConfig.FunctionUrl;
|
|
653
|
+
} catch (error: any) {
|
|
654
|
+
if (error.name !== "ResourceNotFoundException") {
|
|
655
|
+
console.warn("Failed to get function URL:", error);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
428
660
|
return this({
|
|
429
661
|
...props,
|
|
430
662
|
arn: config.FunctionArn!,
|
|
@@ -447,21 +679,22 @@ export const Function = Resource(
|
|
|
447
679
|
packageType: config.PackageType!,
|
|
448
680
|
signingProfileVersionArn: config.SigningProfileVersionArn,
|
|
449
681
|
signingJobArn: config.SigningJobArn,
|
|
682
|
+
functionUrl: functionUrl,
|
|
450
683
|
});
|
|
451
684
|
}
|
|
452
|
-
}
|
|
685
|
+
}
|
|
453
686
|
);
|
|
454
687
|
|
|
455
688
|
// Helper to wait for function to stabilize
|
|
456
689
|
async function waitForFunctionStabilization(
|
|
457
690
|
client: LambdaClient,
|
|
458
|
-
functionName: string
|
|
691
|
+
functionName: string
|
|
459
692
|
) {
|
|
460
693
|
while (true) {
|
|
461
694
|
const config = await client.send(
|
|
462
695
|
new GetFunctionConfigurationCommand({
|
|
463
696
|
FunctionName: functionName,
|
|
464
|
-
})
|
|
697
|
+
})
|
|
465
698
|
);
|
|
466
699
|
|
|
467
700
|
// Check if function is in a stable state
|
|
@@ -472,7 +705,7 @@ async function waitForFunctionStabilization(
|
|
|
472
705
|
// If there's a failure, throw an error
|
|
473
706
|
if (config.State === "Failed" || config.LastUpdateStatus === "Failed") {
|
|
474
707
|
throw new Error(
|
|
475
|
-
`Function failed to stabilize: ${config.StateReason || config.LastUpdateStatusReason}
|
|
708
|
+
`Function failed to stabilize: ${config.StateReason || config.LastUpdateStatusReason}`
|
|
476
709
|
);
|
|
477
710
|
}
|
|
478
711
|
|
|
@@ -48,11 +48,16 @@ export async function handleApiError(
|
|
|
48
48
|
resourceType: string,
|
|
49
49
|
resourceName: string
|
|
50
50
|
): Promise<never> {
|
|
51
|
-
const
|
|
51
|
+
const text = await response.text();
|
|
52
|
+
let json: any;
|
|
53
|
+
try {
|
|
54
|
+
json = JSON.parse(text);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
json = { errors: [{ message: text }] };
|
|
57
|
+
}
|
|
52
58
|
const errors: { message: string }[] = json?.errors || [
|
|
53
59
|
{ message: response.statusText },
|
|
54
60
|
];
|
|
55
61
|
const errorMessage = `Error ${action} ${resourceType} '${resourceName}': ${errors[0]?.message || response.statusText}`;
|
|
56
|
-
|
|
57
62
|
throw new CloudflareApiError(errorMessage, response, errors);
|
|
58
63
|
}
|