alchemy 0.12.9 → 0.12.10

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.
Files changed (54) hide show
  1. package/lib/cloudflare/api.d.ts.map +1 -1
  2. package/lib/cloudflare/api.js +17 -12
  3. package/lib/cloudflare/api.js.map +1 -1
  4. package/lib/cloudflare/bucket.d.ts +4 -0
  5. package/lib/cloudflare/bucket.d.ts.map +1 -1
  6. package/lib/cloudflare/bucket.js +13 -5
  7. package/lib/cloudflare/bucket.js.map +1 -1
  8. package/lib/cloudflare/external.d.ts +2 -0
  9. package/lib/cloudflare/external.d.ts.map +1 -0
  10. package/lib/cloudflare/external.js +30 -0
  11. package/lib/cloudflare/external.js.map +1 -0
  12. package/lib/cloudflare/index.d.ts +1 -0
  13. package/lib/cloudflare/index.d.ts.map +1 -1
  14. package/lib/cloudflare/index.js +1 -0
  15. package/lib/cloudflare/index.js.map +1 -1
  16. package/lib/cloudflare/pipeline.d.ts +7 -1
  17. package/lib/cloudflare/pipeline.d.ts.map +1 -1
  18. package/lib/cloudflare/pipeline.js +1 -1
  19. package/lib/cloudflare/pipeline.js.map +1 -1
  20. package/lib/cloudflare/redwood.d.ts +1 -33
  21. package/lib/cloudflare/redwood.d.ts.map +1 -1
  22. package/lib/cloudflare/redwood.js +7 -46
  23. package/lib/cloudflare/redwood.js.map +1 -1
  24. package/lib/cloudflare/website.d.ts +4 -1
  25. package/lib/cloudflare/website.d.ts.map +1 -1
  26. package/lib/cloudflare/website.js +10 -2
  27. package/lib/cloudflare/website.js.map +1 -1
  28. package/lib/cloudflare/worker.d.ts.map +1 -1
  29. package/lib/cloudflare/worker.js +17 -33
  30. package/lib/cloudflare/worker.js.map +1 -1
  31. package/lib/cloudflare/wrangler.json.d.ts +6 -0
  32. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  33. package/lib/cloudflare/wrangler.json.js +1 -1
  34. package/lib/cloudflare/wrangler.json.js.map +1 -1
  35. package/lib/esbuild/bundle.d.ts +4 -4
  36. package/lib/esbuild/bundle.d.ts.map +1 -1
  37. package/lib/esbuild/bundle.js +9 -7
  38. package/lib/esbuild/bundle.js.map +1 -1
  39. package/lib/os/exec.d.ts +15 -15
  40. package/lib/os/exec.d.ts.map +1 -1
  41. package/lib/os/exec.js +47 -26
  42. package/lib/os/exec.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/cloudflare/api.ts +15 -13
  45. package/src/cloudflare/bucket.ts +16 -5
  46. package/src/cloudflare/external.ts +30 -0
  47. package/src/cloudflare/index.ts +1 -0
  48. package/src/cloudflare/pipeline.ts +9 -2
  49. package/src/cloudflare/redwood.ts +8 -46
  50. package/src/cloudflare/website.ts +19 -3
  51. package/src/cloudflare/worker.ts +22 -42
  52. package/src/cloudflare/wrangler.json.ts +8 -1
  53. package/src/esbuild/bundle.ts +11 -9
  54. package/src/os/exec.ts +61 -34
@@ -79,6 +79,11 @@ export interface BucketProps {
79
79
  * Secret Access Key to use for the bucket
80
80
  */
81
81
  secretAccessKey?: Secret;
82
+
83
+ /**
84
+ * Whether to adopt an existing bucket
85
+ */
86
+ adopt?: boolean;
82
87
  }
83
88
 
84
89
  /**
@@ -163,7 +168,6 @@ export const R2Bucket = Resource(
163
168
  const bucketName = props.name || this.id;
164
169
 
165
170
  if (this.phase === "delete") {
166
- console.log("Deleting R2 bucket:", bucketName);
167
171
  if (props.delete !== false) {
168
172
  if (props.empty) {
169
173
  console.log("Emptying R2 bucket:", bucketName);
@@ -175,8 +179,6 @@ export const R2Bucket = Resource(
175
179
  await emptyBucket(r2Client, bucketName, props.jurisdiction);
176
180
  }
177
181
 
178
- // Delete R2 bucket
179
- console.log("Deleting R2 bucket:", bucketName);
180
182
  await deleteBucket(api, bucketName, props);
181
183
  }
182
184
 
@@ -184,8 +186,17 @@ export const R2Bucket = Resource(
184
186
  return this.destroy();
185
187
  } else {
186
188
  if (this.phase === "create") {
187
- console.log("Creating R2 bucket:", bucketName);
188
- await createBucket(api, bucketName, props);
189
+ try {
190
+ await createBucket(api, bucketName, props);
191
+ } catch (err) {
192
+ if (err instanceof CloudflareApiError && err.status === 409) {
193
+ if (!props.adopt) {
194
+ throw err;
195
+ }
196
+ } else {
197
+ throw err;
198
+ }
199
+ }
189
200
  }
190
201
 
191
202
  await updatePublicAccess(
@@ -0,0 +1,30 @@
1
+ // https://developers.cloudflare.com/workers/runtime-apis/nodejs/#supported-nodejs-apis
2
+ const nodejs_compat = [
3
+ "node:async_hooks",
4
+ "node:assert",
5
+ "node:buffer",
6
+ "node:console",
7
+ "node:crypto",
8
+ "node:diagnostics_channel",
9
+ "node:dns",
10
+ "node:events",
11
+ "node:inspector",
12
+ "node:net",
13
+ "node:path",
14
+ "node:perf_hooks",
15
+ "node:process",
16
+ "node:querystring",
17
+ "node:stream",
18
+ "node:string_decoder",
19
+ "node:timers",
20
+ "node:tls", // partially supported
21
+ "node:url",
22
+ "node:util",
23
+ "node:zlib",
24
+ ];
25
+
26
+ export const external = [
27
+ ...nodejs_compat,
28
+ ...nodejs_compat.map((p) => p.split(":")[1]),
29
+ "cloudflare:workers",
30
+ ];
@@ -9,6 +9,7 @@ export * from "./custom-domain.js";
9
9
  export * from "./d1-database.js";
10
10
  export * from "./dns-records.js";
11
11
  export * from "./durable-object-namespace.js";
12
+ export * from "./external.js";
12
13
  export * from "./hyperdrive.js";
13
14
  export * from "./kv-namespace.js";
14
15
  export * from "./local-dev-cloudflare-shim.js";
@@ -181,8 +181,10 @@ export type PipelineDestination = R2DestinationConfig;
181
181
  export interface PipelineProps extends CloudflareApiOptions {
182
182
  /**
183
183
  * Name of the pipeline
184
+ *
185
+ * @default id
184
186
  */
185
- name: string;
187
+ name?: string;
186
188
 
187
189
  /**
188
190
  * Source configuration for the pipeline
@@ -232,6 +234,11 @@ export interface Pipeline<T extends PipelineRecord = PipelineRecord>
232
234
  */
233
235
  id: string;
234
236
 
237
+ /**
238
+ * The name of the pipeline
239
+ */
240
+ name: string;
241
+
235
242
  /**
236
243
  * HTTP endpoint URL for the pipeline
237
244
  */
@@ -315,7 +322,7 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function <
315
322
  Pipeline<T>
316
323
  > {
317
324
  const api = await createCloudflareApi(props);
318
- const pipelineName = props.name || id;
325
+ const pipelineName = props.name ?? id;
319
326
 
320
327
  if (this.phase === "delete") {
321
328
  if (props.delete !== false) {
@@ -23,7 +23,7 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
23
23
  *
24
24
  * @example
25
25
  * // Deploy with a database binding
26
- * import { D1Database } from "../cloudflare/d1-database";
26
+ * import { D1Database } from alchemy/cloudflare";
27
27
  *
28
28
  * const database = await D1Database("redwood-db");
29
29
  *
@@ -33,38 +33,6 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
33
33
  * }
34
34
  * });
35
35
  *
36
- * @example
37
- * // Deploy with custom build command and environment variables
38
- * const redwoodApp = await Redwood("custom-redwood", {
39
- * command: "bun run test && RWSDK_DEPLOY=1 bun run build:production",
40
- * bindings: {
41
- * API_KEY: alchemy.secret("api-key-secret")
42
- * },
43
- * vars: {
44
- * NODE_ENV: "production",
45
- * APP_ENV: "staging"
46
- * }
47
- * });
48
- *
49
- * @example
50
- * // Deploy with custom paths and additional compatibility flags
51
- * const redwoodApp = await Redwood("redwood-custom-paths", {
52
- * main: "custom/worker.js",
53
- * assets: "custom/static",
54
- * compatibilityFlags: ["nodejs_compat", "fetch_refused_to_set_cookies"]
55
- * });
56
- *
57
- * @example
58
- * // Deploy with custom bundle configuration
59
- * const redwoodApp = await Redwood("redwood-custom-bundle", {
60
- * bundle: {
61
- * options: {
62
- * external: ["node:events", "node:stream", "node:crypto"],
63
- * format: "esm"
64
- * }
65
- * }
66
- * });
67
- *
68
36
  * @param id - Unique identifier for the RedwoodJS application
69
37
  * @param props - Configuration properties for the RedwoodJS deployment
70
38
  * @returns A Cloudflare Worker resource representing the deployed RedwoodJS application
@@ -76,21 +44,15 @@ export async function Redwood<B extends Bindings>(
76
44
  return Website(id, {
77
45
  ...props,
78
46
  command: props?.command ?? "bun run clean && RWSDK_DEPLOY=1 bun run build",
79
- wrangler: props?.wrangler ?? true,
80
- main: props?.main ?? path.join("src", "worker.tsx"),
47
+ wrangler:
48
+ props?.wrangler === false
49
+ ? false
50
+ : {
51
+ main: props?.main ?? path.join("src", "worker.tsx"),
52
+ },
53
+ main: props?.main ?? path.join("dist", "worker", "worker.js"),
81
54
  assets: props?.assets ?? path.join("dist", "client"),
82
55
  compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])],
83
56
  compatibilityDate: props?.compatibilityDate ?? "2025-04-02",
84
- bundle: {
85
- ...props?.bundle,
86
- options: {
87
- ...props?.bundle?.options,
88
- external: [
89
- "node:events",
90
- "node:stream",
91
- ...(props?.bundle?.options?.external ?? []),
92
- ],
93
- },
94
- },
95
57
  });
96
58
  }
@@ -45,7 +45,14 @@ export interface WebsiteProps<B extends Bindings>
45
45
  *
46
46
  * @default - no wrangler.jsonc file is written
47
47
  */
48
- wrangler?: boolean | string;
48
+ wrangler?:
49
+ | boolean
50
+ | string
51
+ | {
52
+ path?: string;
53
+ // override main
54
+ main?: string;
55
+ };
49
56
  }
50
57
 
51
58
  export type Website<B extends Bindings> = B extends { ASSETS: any }
@@ -66,9 +73,17 @@ export async function Website<B extends Bindings>(
66
73
 
67
74
  const cwd = path.resolve(props.cwd || process.cwd());
68
75
  const fileName =
69
- typeof props.wrangler === "boolean" ? "wrangler.jsonc" : props.wrangler;
76
+ typeof props.wrangler === "boolean"
77
+ ? "wrangler.jsonc"
78
+ : typeof props.wrangler === "string"
79
+ ? props.wrangler
80
+ : (props.wrangler?.path ?? "wrangler.jsonc");
70
81
  const wranglerPath =
71
82
  fileName && path.relative(cwd, path.join(cwd, fileName));
83
+ const wranglerMain =
84
+ typeof props.wrangler === "object"
85
+ ? (props.wrangler.main ?? props.main)
86
+ : props.main;
72
87
 
73
88
  if (props.wrangler) {
74
89
  try {
@@ -79,7 +94,7 @@ export async function Website<B extends Bindings>(
79
94
  JSON.stringify(
80
95
  {
81
96
  name: id,
82
- main: props.main,
97
+ main: wranglerMain,
83
98
  compatibility_date: new Date().toISOString().split("T")[0],
84
99
  },
85
100
  null,
@@ -128,6 +143,7 @@ export default {
128
143
  await WranglerJson("wrangler.jsonc", {
129
144
  path: wranglerPath,
130
145
  worker,
146
+ main: wranglerMain,
131
147
  });
132
148
  }
133
149
 
@@ -17,6 +17,7 @@ import type { Bindings, WorkerBindingSpec } from "./bindings.js";
17
17
  import type { Bound } from "./bound.js";
18
18
  import type { DurableObjectNamespace } from "./durable-object-namespace.js";
19
19
  import { type EventSource, isQueueEventSource } from "./event-source.js";
20
+ import { external } from "./external.js";
20
21
  import {
21
22
  QueueConsumer,
22
23
  deleteQueueConsumer,
@@ -576,8 +577,6 @@ async function putWorker(
576
577
  })
577
578
  );
578
579
 
579
- await new Promise((resolve) => setTimeout(resolve, 1000));
580
-
581
580
  // Upload worker script with bindings
582
581
  const uploadResponse = await api.put(
583
582
  `/accounts/${api.accountId}/workers/scripts/${workerName}`,
@@ -591,36 +590,22 @@ async function putWorker(
591
590
 
592
591
  // Check if the upload was successful
593
592
  if (!uploadResponse.ok) {
594
- const errorData: any = await uploadResponse
595
- .json()
596
- .catch(() => ({ errors: [{ message: uploadResponse.statusText }] }));
597
-
598
- const errorMessage = `Error (HTTP ${uploadResponse.status}) uploading worker script '${workerName}': ${errorData.errors?.[0]?.message || uploadResponse.statusText}`;
599
-
600
- if (
601
- uploadResponse.status === 400 &&
602
- errorMessage.includes("not found")
603
- ) {
604
- throw new NotFoundError(errorMessage);
605
- }
606
- throw new Error(errorMessage);
593
+ await handleApiError(
594
+ uploadResponse,
595
+ "uploading worker script",
596
+ "worker",
597
+ workerName
598
+ );
607
599
  }
608
600
 
609
601
  return formData;
610
602
  },
611
- (err) => err instanceof NotFoundError,
603
+ (err) => err.status === 404 || err.status === 500 || err.status === 503,
612
604
  10,
613
605
  100
614
606
  );
615
607
  }
616
608
 
617
- class NotFoundError extends Error {
618
- constructor(message: string) {
619
- super(message);
620
- this.name = "NotFoundError";
621
- }
622
- }
623
-
624
609
  interface WorkerMetadata {
625
610
  compatibility_date: string;
626
611
  compatibility_flags?: string[];
@@ -889,6 +874,9 @@ async function prepareWorkerMetadata<B extends Bindings>(
889
874
  // For service worker format (CJS)
890
875
  meta.body_part = scriptName;
891
876
  }
877
+ if (process.env.DEBUG) {
878
+ console.log(meta);
879
+ }
892
880
  return meta;
893
881
  }
894
882
 
@@ -931,34 +919,26 @@ async function assertWorkerDoesNotExist<B extends Bindings>(
931
919
  }
932
920
 
933
921
  async function bundleWorkerScript<B extends Bindings>(props: WorkerProps) {
934
- // Get the script content - either from props.script, or by bundling
935
-
936
- // Create and use a Bundle resource with worker-optimized configuration
937
- const defaultBundleOptions: Omit<BundleProps, "entryPoint"> = {
922
+ const bundle = await Bundle("bundle", {
923
+ entryPoint: props.entrypoint!,
938
924
  format: props.format === "cjs" ? "cjs" : "esm", // Use the specified format or default to ESM
939
- target: "es2020",
940
- platform: "browser",
925
+ target: "esnext",
926
+ platform: "neutral",
941
927
  minify: true,
928
+ ...(props.bundle || {}),
942
929
  options: {
930
+ ...(props.bundle?.options || {}),
943
931
  keepNames: true, // Important for Durable Object classes
944
932
  loader: {
945
933
  ".sql": "text",
946
934
  ".json": "json",
947
935
  },
948
936
  },
949
- };
950
-
951
- // Merge with user-provided options
952
- const bundleOptions = {
953
- ...defaultBundleOptions,
954
- ...(props.bundle || {}),
955
- };
956
-
957
- // Create the bundle
958
- const bundle = await Bundle("bundle", {
959
- entryPoint: props.entrypoint!,
960
- ...bundleOptions,
961
- external: [...(bundleOptions.external ?? []), "cloudflare:workers"],
937
+ external: [
938
+ ...external,
939
+ ...(props.bundle?.external ?? []),
940
+ ...(props.bundle?.options?.external ?? []),
941
+ ],
962
942
  });
963
943
 
964
944
  try {
@@ -23,6 +23,13 @@ export interface WranglerJsonProps {
23
23
  * @default cwd/wrangler.json
24
24
  */
25
25
  path?: string;
26
+
27
+ /**
28
+ * The main entry point for the worker
29
+ *
30
+ * @default worker.entrypoint
31
+ */
32
+ main?: string;
26
33
  }
27
34
 
28
35
  /**
@@ -75,7 +82,7 @@ export const WranglerJson = Resource(
75
82
  const spec: WranglerJsonSpec = {
76
83
  name: worker.name,
77
84
  // Use entrypoint as main if it exists
78
- main: worker.entrypoint,
85
+ main: props.main ?? worker.entrypoint,
79
86
  // see: https://developers.cloudflare.com/workers/configuration/compatibility-dates/
80
87
  compatibility_date: worker.compatibilityDate,
81
88
  compatibility_flags: props.worker.compatibilityFlags,
@@ -8,7 +8,7 @@ import { Resource } from "../resource.js";
8
8
  /**
9
9
  * Properties for creating or updating an esbuild bundle
10
10
  */
11
- export interface BundleProps {
11
+ export interface BundleProps extends Partial<esbuild.BuildOptions> {
12
12
  /**
13
13
  * Entry point for the bundle
14
14
  * Path to the source file to bundle (e.g., "src/handler.ts")
@@ -143,7 +143,7 @@ export const Bundle = Resource(
143
143
 
144
144
  const result = await bundle(props);
145
145
 
146
- const bundlePath = Object.entries(result.metafile.outputs).find(
146
+ const bundlePath = Object.entries(result.metafile!.outputs).find(
147
147
  ([_, output]) => {
148
148
  if (output.entryPoint === undefined) {
149
149
  return false;
@@ -186,7 +186,9 @@ export const Bundle = Resource(
186
186
  );
187
187
 
188
188
  export async function bundle(props: BundleProps) {
189
- return await esbuild.build({
189
+ const { entryPoint, options: _, ...rest } = props;
190
+ const options = {
191
+ ...rest,
190
192
  ...props.options,
191
193
  write: !(props.outdir === undefined && props.outfile === undefined),
192
194
  // write:
@@ -200,12 +202,12 @@ export async function bundle(props: BundleProps) {
200
202
  target: props.target,
201
203
  minify: props.minify,
202
204
  sourcemap: props.sourcemap,
203
- external: [
204
- "node:*",
205
- ...(props.external ?? []),
206
- ...(props.options?.external ?? []),
207
- ],
205
+ external: [...(props.external ?? []), ...(props.options?.external ?? [])],
208
206
  platform: props.platform,
209
207
  metafile: true,
210
- });
208
+ };
209
+ if (process.env.DEBUG) {
210
+ console.log(options);
211
+ }
212
+ return await esbuild.build(options);
211
213
  }
package/src/os/exec.ts CHANGED
@@ -1,10 +1,7 @@
1
- import { exec } from "node:child_process";
2
- import { promisify } from "node:util";
1
+ import { spawn } from "node:child_process";
3
2
  import type { Context } from "../context.js";
4
3
  import { Resource } from "../resource.js";
5
4
 
6
- const execAsync = promisify(exec);
7
-
8
5
  /**
9
6
  * Properties for executing a shell command
10
7
  */
@@ -32,15 +29,15 @@ export interface ExecProps {
32
29
  env?: Record<string, string>;
33
30
 
34
31
  /**
35
- * Maximum buffer size for stdout and stderr (in bytes)
36
- * @default 1024 * 1024 (1MB)
32
+ * Whether to throw an error if the command exits with a non-zero status
37
33
  */
38
- maxBuffer?: number;
34
+ throwOnError?: boolean;
39
35
 
40
36
  /**
41
- * Whether to throw an error if the command exits with a non-zero status
37
+ * Whether to inherit stdio from parent process
38
+ * @default true
42
39
  */
43
- throwOnError?: boolean;
40
+ inheritStdio?: boolean;
44
41
  }
45
42
 
46
43
  /**
@@ -58,12 +55,12 @@ export interface Exec extends Resource<"os::Exec">, ExecProps {
58
55
  exitCode: number;
59
56
 
60
57
  /**
61
- * Standard output from the command
58
+ * Standard output from the command (only available when inheritStdio is false)
62
59
  */
63
60
  stdout: string;
64
61
 
65
62
  /**
66
- * Standard error from the command
63
+ * Standard error from the command (only available when inheritStdio is false)
67
64
  */
68
65
  stderr: string;
69
66
 
@@ -82,11 +79,18 @@ export interface Exec extends Resource<"os::Exec">, ExecProps {
82
79
  * Execute a shell command
83
80
  *
84
81
  * @example
85
- * // Run a simple command
82
+ * // Run a simple command with inherited stdio (default)
86
83
  * const result = await Exec("list-files", {
87
84
  * command: "ls -la"
88
85
  * });
89
86
  *
87
+ * @example
88
+ * // Run a command and capture output instead of inheriting stdio
89
+ * const result = await Exec("list-files", {
90
+ * command: "ls -la",
91
+ * inheritStdio: false
92
+ * });
93
+ *
90
94
  * console.log(result.stdout);
91
95
  *
92
96
  * @example
@@ -98,13 +102,6 @@ export interface Exec extends Resource<"os::Exec">, ExecProps {
98
102
  * });
99
103
  *
100
104
  * @example
101
- * // Run a command with a larger buffer for output
102
- * const logs = await Exec("get-logs", {
103
- * command: "cat large-log-file.log",
104
- * maxBuffer: 10 * 1024 * 1024 // 10MB
105
- * });
106
- *
107
- * @example
108
105
  * // Run a memoized command that only re-executes when the command changes
109
106
  * const memoizedCmd = await Exec("status-check", {
110
107
  * command: "git status",
@@ -143,31 +140,61 @@ export const Exec = Resource(
143
140
  let stderr = "";
144
141
  let exitCode = 0;
145
142
 
143
+ // Default to inheriting stdio unless explicitly set to false
144
+ const inheritStdio = props.inheritStdio !== false;
145
+
146
146
  try {
147
- console.log(props.command);
148
- // Execute the command
149
- const result = await execAsync(props.command, {
147
+ // Parse the command into command and args
148
+ const [cmd, ...args] = props.command.split(/\s+/);
149
+
150
+ // Use spawn for better stdio control
151
+ const childProcess = spawn(cmd, args, {
150
152
  cwd: props.cwd || process.cwd(),
151
153
  env: { ...process.env, ...props.env },
152
- maxBuffer: props.maxBuffer || 1024 * 1024, // Default 1MB
154
+ shell: true, // Use shell to handle complex commands
155
+ stdio: inheritStdio ? "inherit" : "pipe", // Inherit stdio when requested
156
+ });
157
+
158
+ if (!inheritStdio) {
159
+ // If not inheriting stdio, collect output manually
160
+ childProcess.stdout?.on("data", (data) => {
161
+ stdout += data.toString();
162
+ });
163
+
164
+ childProcess.stderr?.on("data", (data) => {
165
+ stderr += data.toString();
166
+ });
167
+ }
168
+
169
+ // Wait for the process to complete
170
+ exitCode = await new Promise<number>((resolve, reject) => {
171
+ childProcess.on("close", (code) => {
172
+ resolve(code || 0);
173
+ });
174
+
175
+ childProcess.on("error", (err) => {
176
+ if (props.throwOnError) {
177
+ reject(err);
178
+ } else {
179
+ stderr += err.toString();
180
+ resolve(1);
181
+ }
182
+ });
153
183
  });
154
184
 
155
- stdout = result.stdout;
156
- console.log(stdout);
157
- stderr = result.stderr;
158
- exitCode = 0; // Success
185
+ if (exitCode !== 0 && props.throwOnError) {
186
+ throw new Error(
187
+ `Command failed with exit code ${exitCode}: ${props.command}`
188
+ );
189
+ }
159
190
  } catch (error: any) {
160
- console.log("error", error);
161
191
  if (props.throwOnError) {
162
192
  throw error;
163
193
  }
164
194
 
165
195
  // If not throwing, capture the error information
166
- exitCode = error.code || 1;
167
- stdout = error.stdout || "";
168
- stderr = error.stderr || String(error);
169
- console.log(stdout);
170
- console.error(stderr);
196
+ exitCode = 1;
197
+ stderr += String(error);
171
198
  }
172
199
 
173
200
  // Return the execution result
@@ -176,9 +203,9 @@ export const Exec = Resource(
176
203
  command: props.command,
177
204
  cwd: props.cwd,
178
205
  env: props.env,
179
- maxBuffer: props.maxBuffer,
180
206
  throwOnError: props.throwOnError,
181
207
  memoize: props.memoize,
208
+ inheritStdio,
182
209
  exitCode,
183
210
  stdout,
184
211
  stderr,