alchemy 0.10.3 → 0.11.0

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.
@@ -1,5 +1,6 @@
1
1
  import { Architecture, Runtime } from "@aws-sdk/client-lambda";
2
2
  import type { Context } from "../context";
3
+ import type { Bundle } from "../esbuild/bundle";
3
4
  import { Resource } from "../resource";
4
5
  /**
5
6
  * Properties for creating or updating a Lambda function
@@ -10,9 +11,9 @@ export interface FunctionProps {
10
11
  */
11
12
  functionName: string;
12
13
  /**
13
- * Path to the zip file containing the function code
14
+ * Bundle for the function
14
15
  */
15
- zipPath: string;
16
+ bundle: Bundle;
16
17
  /**
17
18
  * ARN of the IAM role that Lambda assumes when executing the function
18
19
  */
@@ -21,7 +22,7 @@ export interface FunctionProps {
21
22
  * Function handler in the format 'file.function'
22
23
  * For Node.js this is typically 'index.handler' or similar
23
24
  */
24
- handler?: string;
25
+ handler: string;
25
26
  /**
26
27
  * Lambda runtime environment for the function
27
28
  * @default nodejs20.x if not specified
@@ -1,6 +1,4 @@
1
1
  import { AddPermissionCommand, Architecture, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteFunctionCommand, DeleteFunctionUrlConfigCommand, GetFunctionCommand, GetFunctionConfigurationCommand, GetFunctionUrlConfigCommand, LambdaClient, ResourceNotFoundException, Runtime, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, } from "@aws-sdk/client-lambda";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
2
  import { Resource } from "../resource";
5
3
  import { ignore } from "../util/ignore";
6
4
  /**
@@ -60,7 +58,7 @@ import { ignore } from "../util/ignore";
60
58
  export const Function = Resource("lambda::Function", async function (id, props) {
61
59
  const client = new LambdaClient({});
62
60
  const region = await resolveRegion(client);
63
- const code = await zipCode(props.zipPath);
61
+ const code = await zipCode(props);
64
62
  if (this.phase === "delete") {
65
63
  // Delete function URL if it exists
66
64
  if (this.output?.url) {
@@ -408,9 +406,10 @@ async function waitForFunctionStabilization(client, functionName) {
408
406
  }
409
407
  }
410
408
  // Helper to zip the code
411
- async function zipCode(filePath) {
412
- const fileContent = await fs.promises.readFile(filePath);
413
- const fileName = path.basename(filePath);
409
+ async function zipCode(props) {
410
+ const fileContent = props.bundle.content;
411
+ const fileName = props.handler.split(".").reverse().slice(0, -1).reverse().join(".") +
412
+ (props.bundle.format === "cjs" ? ".cjs" : ".mjs");
414
413
  // Create a zip buffer in memory
415
414
  const zip = new (await import("jszip")).default();
416
415
  zip.file(fileName, fileContent);
@@ -456,7 +456,15 @@ async function bundleWorkerScript(props) {
456
456
  external: [...(bundleOptions.external ?? []), "cloudflare:workers"],
457
457
  });
458
458
  try {
459
- return await fs.readFile(bundle.path, "utf-8");
459
+ if (bundle.content) {
460
+ return bundle.content;
461
+ }
462
+ else if (bundle.path) {
463
+ return await fs.readFile(bundle.path, "utf-8");
464
+ }
465
+ else {
466
+ throw new Error("Failed to create bundle");
467
+ }
460
468
  }
461
469
  catch (error) {
462
470
  console.error("Error reading bundle:", error);
@@ -1,4 +1,4 @@
1
- import * as esbuild from "esbuild";
1
+ import esbuild from "esbuild";
2
2
  import type { Context } from "../context";
3
3
  import { Resource } from "../resource";
4
4
  /**
@@ -64,17 +64,25 @@ export interface BundleProps {
64
64
  /**
65
65
  * Output returned after bundle creation/update
66
66
  */
67
- export interface Bundle extends Resource<"esbuild::Bundle"> {
67
+ export interface Bundle<P extends BundleProps = BundleProps> extends Resource<"esbuild::Bundle">, BundleProps {
68
68
  /**
69
69
  * Path to the bundled file
70
70
  * Absolute or relative path to the generated bundle
71
71
  */
72
- path: string;
72
+ path: P extends {
73
+ outdir: string;
74
+ } | {
75
+ outfile: string;
76
+ } ? string : undefined;
73
77
  /**
74
78
  * SHA-256 hash of the bundle contents
75
79
  * Used for cache busting and content verification
76
80
  */
77
81
  hash: string;
82
+ /**
83
+ * The content of the bundle (the .js or .mjs file)
84
+ */
85
+ content: string;
78
86
  }
79
87
  /**
80
88
  * esbuild Bundle Resource
@@ -92,8 +100,9 @@ export interface Bundle extends Resource<"esbuild::Bundle"> {
92
100
  * target: "node18"
93
101
  * });
94
102
  */
95
- export declare const Bundle: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Bundle>, id: string, props: BundleProps) => Promise<Bundle>);
103
+ export declare const Bundle: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | (<Props extends BundleProps>(this: Context<Bundle<any>>, id: string, props: Props) => Promise<Bundle<Props>>);
96
104
  export declare function bundle(props: BundleProps): Promise<esbuild.BuildResult<{
105
+ write: boolean;
97
106
  entryPoints: string[];
98
107
  outdir: string | undefined;
99
108
  outfile: string | undefined;
@@ -105,7 +114,6 @@ export declare function bundle(props: BundleProps): Promise<esbuild.BuildResult<
105
114
  external: string[];
106
115
  platform: "browser" | "node" | "neutral" | undefined;
107
116
  metafile: true;
108
- write: true;
109
117
  splitting?: boolean;
110
118
  preserveSymlinks?: boolean;
111
119
  outbase?: string;
@@ -1,6 +1,6 @@
1
- import * as esbuild from "esbuild";
1
+ import esbuild from "esbuild";
2
2
  import crypto from "node:crypto";
3
- import fs from "node:fs";
3
+ import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { Resource } from "../resource";
6
6
  /**
@@ -22,20 +22,25 @@ import { Resource } from "../resource";
22
22
  export const Bundle = Resource("esbuild::Bundle", {
23
23
  alwaysUpdate: true,
24
24
  }, async function (id, props) {
25
- // Determine output path
26
- const outDirPath = props.outfile != null ? path.dirname(props.outfile) : props.outdir;
27
- if (outDirPath == null) {
28
- throw new Error(`You need to specify either outfile or outdir in your bundle configuration ${JSON.stringify(props)}`);
29
- }
30
- // Ensure output directory exists
31
- await fs.promises.mkdir(path.dirname(outDirPath), { recursive: true });
32
25
  if (this.phase === "delete") {
33
- await fs.promises.rm(outDirPath, { recursive: true });
26
+ console.log("delete", this.output.path);
27
+ if (this.output.path) {
28
+ try {
29
+ await fs.rm(this.output.path, { force: true });
30
+ }
31
+ catch (error) {
32
+ if (error instanceof Error && error.message.includes("ENOENT")) {
33
+ // File doesn't exist, so we can ignore the error
34
+ }
35
+ else {
36
+ throw error;
37
+ }
38
+ }
39
+ }
40
+ return this.destroy();
34
41
  }
35
42
  const result = await bundle(props);
36
- // Check that bundle created output an file for the given entrypoint
37
- // Use bundle metada data to retrieve its content
38
- const bundleOutputPath = Object.entries(result.metafile.outputs).find(([file, output]) => {
43
+ const bundlePath = Object.entries(result.metafile.outputs).find(([_, output]) => {
39
44
  if (output.entryPoint === undefined) {
40
45
  return false;
41
46
  }
@@ -44,29 +49,37 @@ export const Bundle = Resource("esbuild::Bundle", {
44
49
  return (relativeOutput ===
45
50
  path.relative(process.cwd(), path.resolve(process.cwd(), props.entryPoint)));
46
51
  })?.[0];
47
- if (!bundleOutputPath) {
48
- throw new Error(`Unable to find a compiled file`);
52
+ const outputFile = result.outputFiles?.[0];
53
+ if (outputFile === undefined && bundlePath === undefined) {
54
+ throw new Error("Failed to create bundle");
55
+ }
56
+ else if (outputFile) {
57
+ return this({
58
+ ...props,
59
+ path: bundlePath,
60
+ hash: outputFile.hash,
61
+ content: outputFile.text,
62
+ });
63
+ }
64
+ else {
65
+ const content = await fs.readFile(bundlePath, "utf-8");
66
+ return this({
67
+ ...props,
68
+ path: bundlePath,
69
+ hash: crypto.createHash("sha256").update(content).digest("hex"),
70
+ content,
71
+ });
49
72
  }
50
- // Calculate hash of the output
51
- const bundleOutputContent = await fs.promises.readFile(bundleOutputPath);
52
- const hash = crypto
53
- .createHash("sha256")
54
- .update(bundleOutputContent)
55
- .digest("hex");
56
- // Store metadata in context
57
- await this.set("metafile", result.metafile);
58
- await this.set("hash", hash);
59
- // Return output info
60
- return this({
61
- path: bundleOutputPath,
62
- hash,
63
- });
64
73
  });
65
74
  export async function bundle(props) {
66
75
  return await esbuild.build({
67
76
  ...props.options,
77
+ write: !(props.outdir === undefined && props.outfile === undefined),
78
+ // write:
79
+ // props.outdir === undefined && props.outfile === undefined ? false : true,
80
+ // write: false,
68
81
  entryPoints: [props.entryPoint],
69
- outdir: props.outdir,
82
+ outdir: props.outdir ? props.outdir : props.outfile ? undefined : ".out",
70
83
  outfile: props.outfile,
71
84
  bundle: true,
72
85
  format: props.format,
@@ -80,6 +93,5 @@ export async function bundle(props) {
80
93
  ],
81
94
  platform: props.platform,
82
95
  metafile: true,
83
- write: true,
84
96
  });
85
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.10.3",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "module": "./lib/index.js",
6
6
  "scripts": {
@@ -15,9 +15,8 @@ import {
15
15
  UpdateFunctionConfigurationCommand,
16
16
  UpdateFunctionUrlConfigCommand,
17
17
  } from "@aws-sdk/client-lambda";
18
- import fs from "node:fs";
19
- import path from "node:path";
20
18
  import type { Context } from "../context";
19
+ import type { Bundle } from "../esbuild/bundle";
21
20
  import { Resource } from "../resource";
22
21
  import { ignore } from "../util/ignore";
23
22
 
@@ -31,9 +30,9 @@ export interface FunctionProps {
31
30
  functionName: string;
32
31
 
33
32
  /**
34
- * Path to the zip file containing the function code
33
+ * Bundle for the function
35
34
  */
36
- zipPath: string;
35
+ bundle: Bundle;
37
36
 
38
37
  /**
39
38
  * ARN of the IAM role that Lambda assumes when executing the function
@@ -44,7 +43,7 @@ export interface FunctionProps {
44
43
  * Function handler in the format 'file.function'
45
44
  * For Node.js this is typically 'index.handler' or similar
46
45
  */
47
- handler?: string;
46
+ handler: string;
48
47
 
49
48
  /**
50
49
  * Lambda runtime environment for the function
@@ -301,7 +300,7 @@ export const Function = Resource(
301
300
  const client = new LambdaClient({});
302
301
  const region = await resolveRegion(client);
303
302
 
304
- const code = await zipCode(props.zipPath);
303
+ const code = await zipCode(props);
305
304
 
306
305
  if (this.phase === "delete") {
307
306
  // Delete function URL if it exists
@@ -714,9 +713,12 @@ async function waitForFunctionStabilization(
714
713
  }
715
714
 
716
715
  // Helper to zip the code
717
- async function zipCode(filePath: string): Promise<Buffer> {
718
- const fileContent = await fs.promises.readFile(filePath);
719
- const fileName = path.basename(filePath);
716
+ async function zipCode(props: FunctionProps): Promise<Buffer> {
717
+ const fileContent = props.bundle.content;
718
+
719
+ const fileName =
720
+ props.handler.split(".").reverse().slice(0, -1).reverse().join(".") +
721
+ (props.bundle.format === "cjs" ? ".cjs" : ".mjs");
720
722
 
721
723
  // Create a zip buffer in memory
722
724
  const zip = new (await import("jszip")).default();
@@ -747,7 +747,13 @@ async function bundleWorkerScript<B extends Bindings>(props: WorkerProps) {
747
747
  });
748
748
 
749
749
  try {
750
- return await fs.readFile(bundle.path, "utf-8");
750
+ if (bundle.content) {
751
+ return bundle.content;
752
+ } else if (bundle.path) {
753
+ return await fs.readFile(bundle.path, "utf-8");
754
+ } else {
755
+ throw new Error("Failed to create bundle");
756
+ }
751
757
  } catch (error) {
752
758
  console.error("Error reading bundle:", error);
753
759
  throw new Error("Error reading bundle");
@@ -1,6 +1,6 @@
1
- import * as esbuild from "esbuild";
1
+ import esbuild from "esbuild";
2
2
  import crypto from "node:crypto";
3
- import fs from "node:fs";
3
+ import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import type { Context } from "../context";
6
6
  import { Resource } from "../resource";
@@ -78,18 +78,25 @@ export interface BundleProps {
78
78
  /**
79
79
  * Output returned after bundle creation/update
80
80
  */
81
- export interface Bundle extends Resource<"esbuild::Bundle"> {
81
+ export interface Bundle<P extends BundleProps = BundleProps>
82
+ extends Resource<"esbuild::Bundle">,
83
+ BundleProps {
82
84
  /**
83
85
  * Path to the bundled file
84
86
  * Absolute or relative path to the generated bundle
85
87
  */
86
- path: string;
88
+ path: P extends { outdir: string } | { outfile: string } ? string : undefined;
87
89
 
88
90
  /**
89
91
  * SHA-256 hash of the bundle contents
90
92
  * Used for cache busting and content verification
91
93
  */
92
94
  hash: string;
95
+
96
+ /**
97
+ * The content of the bundle (the .js or .mjs file)
98
+ */
99
+ content: string;
93
100
  }
94
101
 
95
102
  /**
@@ -113,32 +120,31 @@ export const Bundle = Resource(
113
120
  {
114
121
  alwaysUpdate: true,
115
122
  },
116
- async function (
117
- this: Context<Bundle>,
123
+ async function <Props extends BundleProps>(
124
+ this: Context<Bundle<any>>,
118
125
  id: string,
119
- props: BundleProps
120
- ): Promise<Bundle> {
121
- // Determine output path
122
- const outDirPath =
123
- props.outfile != null ? path.dirname(props.outfile) : props.outdir;
124
-
125
- if (outDirPath == null) {
126
- throw new Error(
127
- `You need to specify either outfile or outdir in your bundle configuration ${JSON.stringify(props)}`
128
- );
129
- }
130
-
131
- // Ensure output directory exists
132
- await fs.promises.mkdir(path.dirname(outDirPath), { recursive: true });
126
+ props: Props
127
+ ): Promise<Bundle<Props>> {
133
128
  if (this.phase === "delete") {
134
- await fs.promises.rm(outDirPath, { recursive: true });
129
+ console.log("delete", this.output.path);
130
+ if (this.output.path) {
131
+ try {
132
+ await fs.rm(this.output.path, { force: true });
133
+ } catch (error) {
134
+ if (error instanceof Error && error.message.includes("ENOENT")) {
135
+ // File doesn't exist, so we can ignore the error
136
+ } else {
137
+ throw error;
138
+ }
139
+ }
140
+ }
141
+ return this.destroy();
135
142
  }
136
143
 
137
144
  const result = await bundle(props);
138
- // Check that bundle created output an file for the given entrypoint
139
- // Use bundle metada data to retrieve its content
140
- const bundleOutputPath = Object.entries(result.metafile.outputs).find(
141
- ([file, output]) => {
145
+
146
+ const bundlePath = Object.entries(result.metafile.outputs).find(
147
+ ([_, output]) => {
142
148
  if (output.entryPoint === undefined) {
143
149
  return false;
144
150
  }
@@ -157,34 +163,37 @@ export const Bundle = Resource(
157
163
  }
158
164
  )?.[0];
159
165
 
160
- if (!bundleOutputPath) {
161
- throw new Error(`Unable to find a compiled file`);
166
+ const outputFile = result.outputFiles?.[0];
167
+ if (outputFile === undefined && bundlePath === undefined) {
168
+ throw new Error("Failed to create bundle");
169
+ } else if (outputFile) {
170
+ return this({
171
+ ...props,
172
+ path: bundlePath,
173
+ hash: outputFile.hash,
174
+ content: outputFile.text,
175
+ });
176
+ } else {
177
+ const content = await fs.readFile(bundlePath!, "utf-8");
178
+ return this({
179
+ ...props,
180
+ path: bundlePath,
181
+ hash: crypto.createHash("sha256").update(content).digest("hex"),
182
+ content,
183
+ });
162
184
  }
163
-
164
- // Calculate hash of the output
165
- const bundleOutputContent = await fs.promises.readFile(bundleOutputPath);
166
- const hash = crypto
167
- .createHash("sha256")
168
- .update(bundleOutputContent)
169
- .digest("hex");
170
-
171
- // Store metadata in context
172
- await this.set("metafile", result.metafile);
173
- await this.set("hash", hash);
174
-
175
- // Return output info
176
- return this({
177
- path: bundleOutputPath,
178
- hash,
179
- });
180
185
  }
181
186
  );
182
187
 
183
188
  export async function bundle(props: BundleProps) {
184
189
  return await esbuild.build({
185
190
  ...props.options,
191
+ write: !(props.outdir === undefined && props.outfile === undefined),
192
+ // write:
193
+ // props.outdir === undefined && props.outfile === undefined ? false : true,
194
+ // write: false,
186
195
  entryPoints: [props.entryPoint],
187
- outdir: props.outdir,
196
+ outdir: props.outdir ? props.outdir : props.outfile ? undefined : ".out",
188
197
  outfile: props.outfile,
189
198
  bundle: true,
190
199
  format: props.format,
@@ -198,6 +207,5 @@ export async function bundle(props: BundleProps) {
198
207
  ],
199
208
  platform: props.platform,
200
209
  metafile: true,
201
- write: true,
202
210
  });
203
211
  }