alchemy 0.16.1 → 0.16.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/os/exec.d.ts CHANGED
@@ -12,9 +12,30 @@ export interface ExecProps {
12
12
  /**
13
13
  * Whether to memoize the command (only re-run if the command changes)
14
14
  *
15
+ * When set to `true`, the command will only be re-executed if the command string changes.
16
+ *
17
+ * When set to an object with `patterns`, the command will be re-executed if either:
18
+ * 1. The command string changes, or
19
+ * 2. The contents of any files matching the glob patterns change
20
+ *
21
+ * ⚠️ **Important Note**: When using memoization with build commands, the build outputs
22
+ * will not be produced if the command is memoized. This is because the command is not
23
+ * actually executed when memoized. Consider disabling memoization in CI environments:
24
+ *
25
+ * @example
26
+ * // Disable memoization in CI to ensure build outputs are always produced
27
+ * await Exec("build", {
28
+ * command: "vite build",
29
+ * memoize: process.env.CI ? false : {
30
+ * patterns: ["./src/**"]
31
+ * }
32
+ * });
33
+ *
15
34
  * @default false
16
35
  */
17
- memoize?: boolean;
36
+ memoize?: boolean | {
37
+ patterns: string[];
38
+ };
18
39
  /**
19
40
  * Working directory for the command
20
41
  */
@@ -57,6 +78,10 @@ export interface Exec extends Resource<"os::Exec">, ExecProps {
57
78
  * Whether the command has completed execution
58
79
  */
59
80
  completed: boolean;
81
+ /**
82
+ * Hash of the command inputs
83
+ */
84
+ hash?: string;
60
85
  }
61
86
  /**
62
87
  * Execute a shell command
@@ -96,6 +121,26 @@ export interface Exec extends Resource<"os::Exec">, ExecProps {
96
121
  * command: "git status",
97
122
  * memoize: true
98
123
  * });
124
+ *
125
+ * @example
126
+ * // Memoize a build command with file patterns, but disable in CI
127
+ * // This ensures build outputs are always produced in CI
128
+ * const build = await Exec("build", {
129
+ * command: "vite build",
130
+ * memoize: process.env.CI ? false : {
131
+ * patterns: ["./src/**"]
132
+ * }
133
+ * });
134
+ *
135
+ * @example
136
+ * // Memoize a database migration command based on schema files
137
+ * // This is safe to memoize since it's idempotent
138
+ * const migrate = await Exec("db-migrate", {
139
+ * command: "drizzle-kit push:pg",
140
+ * memoize: {
141
+ * patterns: ["./src/db/schema/**"]
142
+ * }
143
+ * });
99
144
  */
100
145
  export declare const Exec: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Exec>, id: string, props: ExecProps) => Promise<Exec>);
101
146
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,IAAK,SAAQ,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS;IAC3D;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,IAAI,yFAMP,OAAO,CAAC,IAAI,CAAC,MACf,MAAM,SACH,SAAS,KACf,OAAO,CAAC,IAAI,CAAC,CAkFjB,CAAC;AAUF;;GAEG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAyBf"}
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAE3C;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,IAAK,SAAQ,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS;IAC3D;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,eAAO,MAAM,IAAI,yFAMP,OAAO,CAAC,IAAI,CAAC,MACf,MAAM,SACH,SAAS,KACf,OAAO,CAAC,IAAI,CAAC,CA4FjB,CAAC;AAUF;;GAEG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAyBf"}
package/lib/os/exec.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { join } from "node:path";
2
4
  import { Resource } from "../resource.js";
3
5
  /**
4
6
  * Execute a shell command
@@ -38,6 +40,26 @@ import { Resource } from "../resource.js";
38
40
  * command: "git status",
39
41
  * memoize: true
40
42
  * });
43
+ *
44
+ * @example
45
+ * // Memoize a build command with file patterns, but disable in CI
46
+ * // This ensures build outputs are always produced in CI
47
+ * const build = await Exec("build", {
48
+ * command: "vite build",
49
+ * memoize: process.env.CI ? false : {
50
+ * patterns: ["./src/**"]
51
+ * }
52
+ * });
53
+ *
54
+ * @example
55
+ * // Memoize a database migration command based on schema files
56
+ * // This is safe to memoize since it's idempotent
57
+ * const migrate = await Exec("db-migrate", {
58
+ * command: "drizzle-kit push:pg",
59
+ * memoize: {
60
+ * patterns: ["./src/db/schema/**"]
61
+ * }
62
+ * });
41
63
  */
42
64
  export const Exec = Resource("os::Exec", {
43
65
  alwaysUpdate: true,
@@ -46,9 +68,14 @@ export const Exec = Resource("os::Exec", {
46
68
  // Nothing to actually delete for an exec command
47
69
  return this.destroy();
48
70
  }
71
+ const hash = typeof props.memoize === "object"
72
+ ? await hashInputs(props.cwd ?? process.cwd(), props.memoize.patterns)
73
+ : undefined;
49
74
  if (this.phase === "update" &&
50
75
  props.memoize &&
51
- this.output?.command === props.command) {
76
+ this.output?.command === props.command &&
77
+ (typeof props.memoize === "boolean" ||
78
+ (hash && this.output?.hash === hash))) {
52
79
  // If memoize is enabled and the command hasn't changed, return the existing output
53
80
  return this.output;
54
81
  }
@@ -109,6 +136,7 @@ export const Exec = Resource("os::Exec", {
109
136
  stderr,
110
137
  executedAt: Date.now(),
111
138
  completed: true,
139
+ hash,
112
140
  });
113
141
  });
114
142
  const defaultOptions = {
@@ -145,4 +173,22 @@ export async function exec(command, options) {
145
173
  });
146
174
  });
147
175
  }
176
+ async function hashInputs(cwd, patterns) {
177
+ const { glob, readFile } = await import("node:fs/promises");
178
+ const hashes = new Map();
179
+ await Promise.all(patterns.flatMap(async (pattern) => {
180
+ const files = await Array.fromAsync(glob(pattern, { cwd }));
181
+ return Promise.all(files.map(async (file) => {
182
+ const path = join(cwd, file);
183
+ const content = await readFile(path);
184
+ hashes.set(path, createHash("sha256").update(content).digest("hex"));
185
+ }));
186
+ }));
187
+ const sortedHashes = Array.from(hashes.entries()).sort((a, b) => a[0].localeCompare(b[0]));
188
+ const finalHash = createHash("sha256");
189
+ for (const [, hash] of sortedHashes) {
190
+ finalHash.update(hash);
191
+ }
192
+ return finalHash.digest("hex");
193
+ }
148
194
  //# sourceMappingURL=exec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
4
4
  "type": "module",
5
5
  "module": "./lib/index.js",
6
6
  "scripts": {
package/src/os/exec.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { spawn, type SpawnOptions } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { join } from "node:path";
2
4
  import type { Context } from "../context.js";
3
5
  import { Resource } from "../resource.js";
4
6
 
@@ -14,9 +16,28 @@ export interface ExecProps {
14
16
  /**
15
17
  * Whether to memoize the command (only re-run if the command changes)
16
18
  *
19
+ * When set to `true`, the command will only be re-executed if the command string changes.
20
+ *
21
+ * When set to an object with `patterns`, the command will be re-executed if either:
22
+ * 1. The command string changes, or
23
+ * 2. The contents of any files matching the glob patterns change
24
+ *
25
+ * ⚠️ **Important Note**: When using memoization with build commands, the build outputs
26
+ * will not be produced if the command is memoized. This is because the command is not
27
+ * actually executed when memoized. Consider disabling memoization in CI environments:
28
+ *
29
+ * @example
30
+ * // Disable memoization in CI to ensure build outputs are always produced
31
+ * await Exec("build", {
32
+ * command: "vite build",
33
+ * memoize: process.env.CI ? false : {
34
+ * patterns: ["./src/**"]
35
+ * }
36
+ * });
37
+ *
17
38
  * @default false
18
39
  */
19
- memoize?: boolean;
40
+ memoize?: boolean | { patterns: string[] };
20
41
 
21
42
  /**
22
43
  * Working directory for the command
@@ -68,6 +89,11 @@ export interface Exec extends Resource<"os::Exec">, ExecProps {
68
89
  * Whether the command has completed execution
69
90
  */
70
91
  completed: boolean;
92
+
93
+ /**
94
+ * Hash of the command inputs
95
+ */
96
+ hash?: string;
71
97
  }
72
98
 
73
99
  /**
@@ -108,6 +134,26 @@ export interface Exec extends Resource<"os::Exec">, ExecProps {
108
134
  * command: "git status",
109
135
  * memoize: true
110
136
  * });
137
+ *
138
+ * @example
139
+ * // Memoize a build command with file patterns, but disable in CI
140
+ * // This ensures build outputs are always produced in CI
141
+ * const build = await Exec("build", {
142
+ * command: "vite build",
143
+ * memoize: process.env.CI ? false : {
144
+ * patterns: ["./src/**"]
145
+ * }
146
+ * });
147
+ *
148
+ * @example
149
+ * // Memoize a database migration command based on schema files
150
+ * // This is safe to memoize since it's idempotent
151
+ * const migrate = await Exec("db-migrate", {
152
+ * command: "drizzle-kit push:pg",
153
+ * memoize: {
154
+ * patterns: ["./src/db/schema/**"]
155
+ * }
156
+ * });
111
157
  */
112
158
  export const Exec = Resource(
113
159
  "os::Exec",
@@ -123,14 +169,23 @@ export const Exec = Resource(
123
169
  // Nothing to actually delete for an exec command
124
170
  return this.destroy();
125
171
  }
172
+
173
+ const hash =
174
+ typeof props.memoize === "object"
175
+ ? await hashInputs(props.cwd ?? process.cwd(), props.memoize.patterns)
176
+ : undefined;
177
+
126
178
  if (
127
179
  this.phase === "update" &&
128
180
  props.memoize &&
129
- this.output?.command === props.command
181
+ this.output?.command === props.command &&
182
+ (typeof props.memoize === "boolean" ||
183
+ (hash && this.output?.hash === hash))
130
184
  ) {
131
185
  // If memoize is enabled and the command hasn't changed, return the existing output
132
186
  return this.output;
133
187
  }
188
+
134
189
  // Default values
135
190
  let stdout = "";
136
191
  let stderr = "";
@@ -198,6 +253,7 @@ export const Exec = Resource(
198
253
  stderr,
199
254
  executedAt: Date.now(),
200
255
  completed: true,
256
+ hash,
201
257
  });
202
258
  },
203
259
  );
@@ -242,3 +298,32 @@ export async function exec(
242
298
  });
243
299
  });
244
300
  }
301
+
302
+ async function hashInputs(cwd: string, patterns: string[]) {
303
+ const { glob, readFile } = await import("node:fs/promises");
304
+
305
+ const hashes = new Map<string, string>();
306
+
307
+ await Promise.all(
308
+ patterns.flatMap(async (pattern) => {
309
+ const files = await Array.fromAsync(glob(pattern, { cwd }));
310
+ return Promise.all(
311
+ files.map(async (file: string) => {
312
+ const path = join(cwd, file);
313
+ const content = await readFile(path);
314
+ hashes.set(path, createHash("sha256").update(content).digest("hex"));
315
+ }),
316
+ );
317
+ }),
318
+ );
319
+
320
+ const sortedHashes = Array.from(hashes.entries()).sort((a, b) =>
321
+ a[0].localeCompare(b[0]),
322
+ );
323
+
324
+ const finalHash = createHash("sha256");
325
+ for (const [, hash] of sortedHashes) {
326
+ finalHash.update(hash);
327
+ }
328
+ return finalHash.digest("hex");
329
+ }