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 +46 -1
- package/lib/os/exec.d.ts.map +1 -1
- package/lib/os/exec.js +47 -1
- package/lib/os/exec.js.map +1 -1
- package/package.json +1 -1
- package/src/os/exec.ts +87 -2
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
|
/**
|
package/lib/os/exec.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
package/lib/os/exec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA8F1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAC1B,UAAU,EACV;IACE,YAAY,EAAE,IAAI;CACnB,EACD,KAAK,WAEH,EAAU,EACV,KAAgB;IAEhB,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,iDAAiD;QACjD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAED,MAAM,IAAI,GACR,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;QAC/B,CAAC,CAAC,MAAM,UAAU,CAAC,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QACtE,CAAC,CAAC,SAAS,CAAC;IAEhB,IACE,IAAI,CAAC,KAAK,KAAK,QAAQ;QACvB,KAAK,CAAC,OAAO;QACb,IAAI,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,CAAC,OAAO;QACtC,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,SAAS;YACjC,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,EACvC,CAAC;QACD,mFAAmF;QACnF,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,iBAAiB;IACjB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,6DAA6D;IAC7D,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,KAAK,KAAK,CAAC;IAElD,IAAI,CAAC;QACH,0CAA0C;QAC1C,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAElD,qCAAqC;QACrC,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YACpC,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;YAC/B,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE;YACrC,KAAK,EAAE,IAAI,EAAE,uCAAuC;YACpD,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,+BAA+B;SAC1E,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,mDAAmD;YACnD,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACvC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACvC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;QACL,CAAC;QAED,mCAAmC;QACnC,QAAQ,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACvD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBAChC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;YACrB,CAAC,CAAC,CAAC;YAEH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC/B,MAAM,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;gBACzB,OAAO,CAAC,CAAC,CAAC,CAAC;YACb,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,iDAAiD;QACjD,QAAQ,GAAG,CAAC,CAAC;QACb,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,iCAAiC,QAAQ,KAAK,KAAK,CAAC,OAAO,KAAK,MAAM,EAAE,CACzE,CAAC;IACJ,CAAC;IAED,8BAA8B;IAC9B,OAAO,IAAI,CAAC;QACV,EAAE;QACF,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,YAAY;QACZ,QAAQ;QACR,MAAM;QACN,MAAM;QACN,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;QACtB,SAAS,EAAE,IAAI;QACf,IAAI;KACL,CAAC,CAAC;AACL,CAAC,CACF,CAAC;AAEF,MAAM,cAAc,GAAiB;IACnC,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE;QACH,GAAG,OAAO,CAAC,GAAG;KACf;IACD,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,OAAe,EACf,OAA+B;IAE/B,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAE5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,GAAG,cAAc;YACjB,GAAG,OAAO;YACV,GAAG,EAAE;gBACH,GAAG,cAAc,CAAC,GAAG;gBACrB,GAAG,OAAO,EAAE,GAAG;aAChB;SACF,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,OAAO,EAAE,CAAC;YACZ,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,GAAW,EAAE,QAAkB;IACvD,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAE5D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,MAAM,OAAO,CAAC,GAAG,CACf,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACjC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAC5D,OAAO,OAAO,CAAC,GAAG,CAChB,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;YAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC7B,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;YACrC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACvE,CAAC,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC9D,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACzB,CAAC;IAEF,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvC,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,YAAY,EAAE,CAAC;QACpC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC"}
|
package/package.json
CHANGED
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
|
+
}
|