alchemy 0.12.21 → 0.14.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.
Files changed (319) hide show
  1. package/lib/ai/client.d.ts.map +1 -1
  2. package/lib/ai/client.js +3 -5
  3. package/lib/ai/client.js.map +1 -1
  4. package/lib/alchemy.d.ts +2 -1
  5. package/lib/alchemy.d.ts.map +1 -1
  6. package/lib/alchemy.js +87 -92
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/apply.d.ts.map +1 -1
  9. package/lib/apply.js +6 -1
  10. package/lib/apply.js.map +1 -1
  11. package/lib/aws/bucket.d.ts.map +1 -1
  12. package/lib/aws/bucket.js +55 -57
  13. package/lib/aws/bucket.js.map +1 -1
  14. package/lib/aws/function.d.ts.map +1 -1
  15. package/lib/aws/function.js +250 -252
  16. package/lib/aws/function.js.map +1 -1
  17. package/lib/aws/policy-attachment.d.ts.map +1 -1
  18. package/lib/aws/policy-attachment.js +4 -6
  19. package/lib/aws/policy-attachment.js.map +1 -1
  20. package/lib/aws/policy.d.ts.map +1 -1
  21. package/lib/aws/policy.js +63 -65
  22. package/lib/aws/policy.js.map +1 -1
  23. package/lib/aws/queue.js +88 -90
  24. package/lib/aws/queue.js.map +1 -1
  25. package/lib/aws/table.d.ts.map +1 -1
  26. package/lib/aws/table.js +95 -99
  27. package/lib/aws/table.js.map +1 -1
  28. package/lib/cloudflare/account-api-token.d.ts +1 -1
  29. package/lib/cloudflare/account-api-token.d.ts.map +1 -1
  30. package/lib/cloudflare/account-api-token.js +0 -1
  31. package/lib/cloudflare/account-api-token.js.map +1 -1
  32. package/lib/cloudflare/ai-gateway.js.map +1 -1
  33. package/lib/cloudflare/ai.d.ts +1 -1
  34. package/lib/cloudflare/ai.d.ts.map +1 -1
  35. package/lib/cloudflare/ai.js.map +1 -1
  36. package/lib/cloudflare/api.d.ts +5 -5
  37. package/lib/cloudflare/api.d.ts.map +1 -1
  38. package/lib/cloudflare/api.js +52 -15
  39. package/lib/cloudflare/api.js.map +1 -1
  40. package/lib/cloudflare/auth.d.ts +24 -55
  41. package/lib/cloudflare/auth.d.ts.map +1 -1
  42. package/lib/cloudflare/auth.js +15 -72
  43. package/lib/cloudflare/auth.js.map +1 -1
  44. package/lib/cloudflare/bucket.d.ts +2 -2
  45. package/lib/cloudflare/bucket.d.ts.map +1 -1
  46. package/lib/cloudflare/bucket.js +22 -24
  47. package/lib/cloudflare/bucket.js.map +1 -1
  48. package/lib/cloudflare/bundle/alias-plugin.d.ts +6 -0
  49. package/lib/cloudflare/bundle/alias-plugin.d.ts.map +1 -0
  50. package/lib/cloudflare/bundle/alias-plugin.js +36 -0
  51. package/lib/cloudflare/bundle/alias-plugin.js.map +1 -0
  52. package/lib/cloudflare/bundle/build-failures.d.ts +12 -0
  53. package/lib/cloudflare/bundle/build-failures.d.ts.map +1 -0
  54. package/lib/cloudflare/bundle/build-failures.js +45 -0
  55. package/lib/cloudflare/bundle/build-failures.js.map +1 -0
  56. package/lib/cloudflare/bundle/bundle-worker.d.ts +7 -0
  57. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -0
  58. package/lib/cloudflare/bundle/bundle-worker.js +69 -0
  59. package/lib/cloudflare/bundle/bundle-worker.js.map +1 -0
  60. package/lib/cloudflare/bundle/external.d.ts +1 -0
  61. package/lib/cloudflare/bundle/external.d.ts.map +1 -1
  62. package/lib/cloudflare/bundle/external.js +8 -1
  63. package/lib/cloudflare/bundle/external.js.map +1 -1
  64. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.d.ts +1 -1
  65. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.d.ts.map +1 -1
  66. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.js +6 -6
  67. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.js.map +1 -1
  68. package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts +17 -0
  69. package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +1 -0
  70. package/lib/cloudflare/bundle/nodejs-compat-mode.js +27 -0
  71. package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +1 -0
  72. package/lib/cloudflare/bundle/{hybrid-nodejs-compat.d.ts → nodejs-compat.d.ts} +2 -2
  73. package/lib/cloudflare/bundle/nodejs-compat.d.ts.map +1 -0
  74. package/lib/cloudflare/bundle/{hybrid-nodejs-compat.js → nodejs-compat.js} +2 -2
  75. package/lib/cloudflare/bundle/nodejs-compat.js.map +1 -0
  76. package/lib/cloudflare/custom-domain.d.ts.map +1 -1
  77. package/lib/cloudflare/custom-domain.js +3 -5
  78. package/lib/cloudflare/custom-domain.js.map +1 -1
  79. package/lib/cloudflare/d1-database.d.ts +1 -1
  80. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  81. package/lib/cloudflare/d1-database.js +70 -72
  82. package/lib/cloudflare/d1-database.js.map +1 -1
  83. package/lib/cloudflare/d1-migrations.d.ts +1 -1
  84. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  85. package/lib/cloudflare/d1-migrations.js +2 -3
  86. package/lib/cloudflare/d1-migrations.js.map +1 -1
  87. package/lib/cloudflare/dns-records.d.ts.map +1 -1
  88. package/lib/cloudflare/dns-records.js +2 -4
  89. package/lib/cloudflare/dns-records.js.map +1 -1
  90. package/lib/cloudflare/kv-namespace.d.ts +1 -1
  91. package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
  92. package/lib/cloudflare/kv-namespace.js +24 -27
  93. package/lib/cloudflare/kv-namespace.js.map +1 -1
  94. package/lib/cloudflare/pipeline.d.ts +1 -1
  95. package/lib/cloudflare/pipeline.d.ts.map +1 -1
  96. package/lib/cloudflare/pipeline.js +32 -34
  97. package/lib/cloudflare/pipeline.js.map +1 -1
  98. package/lib/cloudflare/queue-consumer.d.ts +1 -1
  99. package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
  100. package/lib/cloudflare/queue-consumer.js +27 -33
  101. package/lib/cloudflare/queue-consumer.js.map +1 -1
  102. package/lib/cloudflare/queue.d.ts +1 -1
  103. package/lib/cloudflare/queue.d.ts.map +1 -1
  104. package/lib/cloudflare/queue.js +36 -38
  105. package/lib/cloudflare/queue.js.map +1 -1
  106. package/lib/cloudflare/r2-rest-state-store.js +4 -8
  107. package/lib/cloudflare/r2-rest-state-store.js.map +1 -1
  108. package/lib/cloudflare/types.d.ts.map +1 -1
  109. package/lib/cloudflare/types.js +1 -3
  110. package/lib/cloudflare/types.js.map +1 -1
  111. package/lib/cloudflare/user.d.ts +51 -0
  112. package/lib/cloudflare/user.d.ts.map +1 -0
  113. package/lib/cloudflare/user.js +62 -0
  114. package/lib/cloudflare/user.js.map +1 -0
  115. package/lib/cloudflare/vectorize-index.d.ts.map +1 -1
  116. package/lib/cloudflare/vectorize-index.js +37 -39
  117. package/lib/cloudflare/vectorize-index.js.map +1 -1
  118. package/lib/cloudflare/vectorize-metadata-index.d.ts +1 -1
  119. package/lib/cloudflare/vectorize-metadata-index.d.ts.map +1 -1
  120. package/lib/cloudflare/vectorize-metadata-index.js +10 -13
  121. package/lib/cloudflare/vectorize-metadata-index.js.map +1 -1
  122. package/lib/cloudflare/worker.d.ts +18 -3
  123. package/lib/cloudflare/worker.d.ts.map +1 -1
  124. package/lib/cloudflare/worker.js +11 -54
  125. package/lib/cloudflare/worker.js.map +1 -1
  126. package/lib/cloudflare/workflow.d.ts +1 -1
  127. package/lib/cloudflare/workflow.d.ts.map +1 -1
  128. package/lib/cloudflare/workflow.js.map +1 -1
  129. package/lib/cloudflare/zone.d.ts.map +1 -1
  130. package/lib/cloudflare/zone.js +48 -50
  131. package/lib/cloudflare/zone.js.map +1 -1
  132. package/lib/dns/godaddy.js +1 -1
  133. package/lib/dns/import-dns.js +1 -1
  134. package/lib/dns/import-dns.js.map +1 -1
  135. package/lib/env.js +7 -9
  136. package/lib/env.js.map +1 -1
  137. package/lib/esbuild/bundle.d.ts.map +1 -1
  138. package/lib/esbuild/bundle.js +8 -11
  139. package/lib/esbuild/bundle.js.map +1 -1
  140. package/lib/fs/copy-file.d.ts.map +1 -1
  141. package/lib/fs/copy-file.js +27 -29
  142. package/lib/fs/copy-file.js.map +1 -1
  143. package/lib/fs/file.d.ts +1 -1
  144. package/lib/fs/file.d.ts.map +1 -1
  145. package/lib/fs/file.js +1 -1
  146. package/lib/fs/file.js.map +1 -1
  147. package/lib/fs/folder.d.ts.map +1 -1
  148. package/lib/fs/folder.js +1 -3
  149. package/lib/fs/folder.js.map +1 -1
  150. package/lib/github/client.d.ts.map +1 -1
  151. package/lib/github/client.js +3 -5
  152. package/lib/github/client.js.map +1 -1
  153. package/lib/github/repository-environment.d.ts.map +1 -1
  154. package/lib/github/repository-environment.js +173 -176
  155. package/lib/github/repository-environment.js.map +1 -1
  156. package/lib/github/secret.d.ts.map +1 -1
  157. package/lib/github/secret.js +97 -99
  158. package/lib/github/secret.js.map +1 -1
  159. package/lib/internal/docs/providers.d.ts.map +1 -1
  160. package/lib/internal/docs/providers.js +5 -7
  161. package/lib/internal/docs/providers.js.map +1 -1
  162. package/lib/neon/api.js +1 -2
  163. package/lib/neon/api.js.map +1 -1
  164. package/lib/neon/project.js +2 -2
  165. package/lib/neon/project.js.map +1 -1
  166. package/lib/os/exec.d.ts +5 -0
  167. package/lib/os/exec.d.ts.map +1 -1
  168. package/lib/os/exec.js +88 -56
  169. package/lib/os/exec.js.map +1 -1
  170. package/lib/scope.d.ts +3 -0
  171. package/lib/scope.d.ts.map +1 -1
  172. package/lib/scope.js +10 -3
  173. package/lib/scope.js.map +1 -1
  174. package/lib/secret.d.ts.map +1 -1
  175. package/lib/secret.js +1 -3
  176. package/lib/secret.js.map +1 -1
  177. package/lib/serde.d.ts.map +1 -1
  178. package/lib/serde.js +13 -15
  179. package/lib/serde.js.map +1 -1
  180. package/lib/stripe/price.d.ts.map +1 -1
  181. package/lib/stripe/price.js +69 -73
  182. package/lib/stripe/price.js.map +1 -1
  183. package/lib/stripe/product.d.ts.map +1 -1
  184. package/lib/stripe/product.js +50 -52
  185. package/lib/stripe/product.js.map +1 -1
  186. package/lib/stripe/webhook.d.ts.map +1 -1
  187. package/lib/stripe/webhook.js +51 -53
  188. package/lib/stripe/webhook.js.map +1 -1
  189. package/lib/test/bun.d.ts +1 -1
  190. package/lib/test/bun.d.ts.map +1 -1
  191. package/lib/test/bun.js +3 -8
  192. package/lib/test/bun.js.map +1 -1
  193. package/lib/test/prune.d.ts.map +1 -1
  194. package/lib/test/prune.js.map +1 -1
  195. package/lib/util/dedent.js +4 -4
  196. package/lib/util/dedent.js.map +1 -1
  197. package/lib/web/astro.js +1 -1
  198. package/lib/web/shadcn.js +4 -4
  199. package/lib/web/shadcn.js.map +1 -1
  200. package/lib/web/tailwind.d.ts.map +1 -1
  201. package/lib/web/tailwind.js +0 -1
  202. package/lib/web/tailwind.js.map +1 -1
  203. package/lib/web/vite.js +3 -3
  204. package/lib/web/vitepress/home-page.d.ts.map +1 -1
  205. package/lib/web/vitepress/home-page.js +6 -8
  206. package/lib/web/vitepress/home-page.js.map +1 -1
  207. package/lib/web/vitepress/process-front-matter-files.js +1 -1
  208. package/lib/web/vitepress/process-front-matter-files.js.map +1 -1
  209. package/lib/web/vitepress/vitepress.js +2 -2
  210. package/lib/web/vitepress/vitepress.js.map +1 -1
  211. package/package.json +4 -1
  212. package/src/ai/astro-file.ts +4 -4
  213. package/src/ai/client.ts +4 -5
  214. package/src/ai/css-file.ts +4 -4
  215. package/src/ai/document.ts +3 -3
  216. package/src/ai/html-file.ts +4 -4
  217. package/src/ai/json-file.ts +1 -1
  218. package/src/ai/typescript-file.ts +4 -4
  219. package/src/ai/vue-file.ts +4 -4
  220. package/src/ai/yaml-file.ts +1 -1
  221. package/src/alchemy.ts +111 -104
  222. package/src/apply.ts +13 -6
  223. package/src/aws/bucket.ts +72 -75
  224. package/src/aws/function.ts +296 -297
  225. package/src/aws/oidc/github-oidc-provider.ts +1 -1
  226. package/src/aws/oidc/oidc-provider.ts +15 -15
  227. package/src/aws/policy-attachment.ts +10 -11
  228. package/src/aws/policy.ts +91 -94
  229. package/src/aws/queue.ts +111 -111
  230. package/src/aws/role.ts +24 -24
  231. package/src/aws/ses.ts +17 -17
  232. package/src/aws/table.ts +114 -115
  233. package/src/cloudflare/account-api-token.ts +11 -11
  234. package/src/cloudflare/ai-gateway.ts +4 -4
  235. package/src/cloudflare/ai.ts +1 -1
  236. package/src/cloudflare/api-error.ts +1 -1
  237. package/src/cloudflare/api.ts +70 -25
  238. package/src/cloudflare/assets.ts +4 -4
  239. package/src/cloudflare/auth.ts +49 -164
  240. package/src/cloudflare/bucket.ts +62 -63
  241. package/src/cloudflare/bundle/alias-plugin.ts +48 -0
  242. package/src/cloudflare/bundle/build-failures.ts +55 -0
  243. package/src/cloudflare/bundle/bundle-worker.ts +89 -0
  244. package/src/cloudflare/bundle/external.ts +9 -1
  245. package/src/cloudflare/bundle/local-dev-cloudflare-shim.ts +7 -6
  246. package/src/cloudflare/bundle/nodejs-compat-mode.ts +49 -0
  247. package/src/cloudflare/bundle/{hybrid-nodejs-compat.ts → nodejs-compat.ts} +10 -10
  248. package/src/cloudflare/custom-domain.ts +27 -28
  249. package/src/cloudflare/d1-database.ts +101 -102
  250. package/src/cloudflare/d1-migrations.ts +10 -10
  251. package/src/cloudflare/dns-records.ts +23 -24
  252. package/src/cloudflare/durable-object-namespace.ts +1 -1
  253. package/src/cloudflare/event-source.ts +1 -1
  254. package/src/cloudflare/hyperdrive.ts +3 -3
  255. package/src/cloudflare/kv-namespace.ts +36 -37
  256. package/src/cloudflare/nuxt.ts +1 -1
  257. package/src/cloudflare/permission-groups.ts +7 -7
  258. package/src/cloudflare/pipeline.ts +55 -56
  259. package/src/cloudflare/queue-consumer.ts +46 -53
  260. package/src/cloudflare/queue.ts +56 -57
  261. package/src/cloudflare/r2-rest-state-store.ts +11 -11
  262. package/src/cloudflare/redwood.ts +1 -1
  263. package/src/cloudflare/tanstack-start.ts +1 -1
  264. package/src/cloudflare/types.ts +1 -2
  265. package/src/cloudflare/user.ts +142 -0
  266. package/src/cloudflare/vectorize-index.ts +60 -61
  267. package/src/cloudflare/vectorize-metadata-index.ts +25 -25
  268. package/src/cloudflare/vite.ts +1 -1
  269. package/src/cloudflare/website.ts +3 -3
  270. package/src/cloudflare/worker.ts +83 -108
  271. package/src/cloudflare/workflow.ts +4 -4
  272. package/src/cloudflare/wrangler.json.ts +4 -4
  273. package/src/cloudflare/zone.ts +69 -70
  274. package/src/destroy.ts +4 -4
  275. package/src/dns/godaddy.ts +1 -1
  276. package/src/dns/import-dns.ts +5 -5
  277. package/src/encrypt.ts +6 -6
  278. package/src/env.ts +10 -10
  279. package/src/esbuild/bundle.ts +14 -15
  280. package/src/fs/copy-file.ts +35 -36
  281. package/src/fs/file-system-state-store.ts +4 -4
  282. package/src/fs/file.ts +9 -8
  283. package/src/fs/folder.ts +6 -7
  284. package/src/github/client.ts +15 -14
  285. package/src/github/repository-environment.ts +206 -210
  286. package/src/github/secret.ts +111 -116
  287. package/src/internal/docs/providers.ts +16 -17
  288. package/src/neon/api-error.ts +1 -1
  289. package/src/neon/api.ts +6 -6
  290. package/src/neon/project.ts +18 -17
  291. package/src/os/exec.ts +107 -66
  292. package/src/resource.ts +3 -3
  293. package/src/scope.ts +14 -4
  294. package/src/secret.ts +2 -3
  295. package/src/serde.ts +30 -22
  296. package/src/stripe/price.ts +74 -77
  297. package/src/stripe/product.ts +55 -56
  298. package/src/stripe/webhook.ts +56 -57
  299. package/src/test/bun.ts +9 -13
  300. package/src/test/prune.ts +4 -4
  301. package/src/util/dedent.ts +4 -4
  302. package/src/web/astro.ts +10 -10
  303. package/src/web/shadcn-component.ts +4 -4
  304. package/src/web/shadcn.ts +10 -10
  305. package/src/web/tailwind.ts +2 -3
  306. package/src/web/vite.ts +11 -11
  307. package/src/web/vitepress/config.ts +2 -2
  308. package/src/web/vitepress/custom-theme.ts +7 -7
  309. package/src/web/vitepress/dependencies.ts +4 -4
  310. package/src/web/vitepress/home-page.ts +8 -9
  311. package/src/web/vitepress/process-front-matter-files.ts +5 -5
  312. package/src/web/vitepress/vitepress.ts +7 -7
  313. package/lib/cloudflare/account-id.d.ts +0 -6
  314. package/lib/cloudflare/account-id.d.ts.map +0 -1
  315. package/lib/cloudflare/account-id.js +0 -6
  316. package/lib/cloudflare/account-id.js.map +0 -1
  317. package/lib/cloudflare/bundle/hybrid-nodejs-compat.d.ts.map +0 -1
  318. package/lib/cloudflare/bundle/hybrid-nodejs-compat.js.map +0 -1
  319. package/src/cloudflare/account-id.ts +0 -12
package/src/os/exec.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawn, type SpawnOptions } from "node:child_process";
2
2
  import type { Context } from "../context.js";
3
3
  import { Resource } from "../resource.js";
4
4
 
@@ -117,87 +117,128 @@ export const Exec = Resource(
117
117
  async function (
118
118
  this: Context<Exec>,
119
119
  id: string,
120
- props: ExecProps
120
+ props: ExecProps,
121
121
  ): Promise<Exec> {
122
122
  if (this.phase === "delete") {
123
123
  // Nothing to actually delete for an exec command
124
124
  return this.destroy();
125
- } else if (
125
+ }
126
+ if (
126
127
  this.phase === "update" &&
127
128
  props.memoize &&
128
129
  this.output?.command === props.command
129
130
  ) {
130
131
  // If memoize is enabled and the command hasn't changed, return the existing output
131
132
  return this.output;
132
- } else {
133
- // Default values
134
- let stdout = "";
135
- let stderr = "";
136
- let exitCode = 0;
137
-
138
- // Default to inheriting stdio unless explicitly set to false
139
- const inheritStdio = props.inheritStdio !== false;
140
-
141
- try {
142
- // Parse the command into command and args
143
- const [cmd, ...args] = props.command.split(/\s+/);
144
-
145
- // Use spawn for better stdio control
146
- const childProcess = spawn(cmd, args, {
147
- cwd: props.cwd || process.cwd(),
148
- env: { ...process.env, ...props.env },
149
- shell: true, // Use shell to handle complex commands
150
- stdio: inheritStdio ? "inherit" : "pipe", // Inherit stdio when requested
133
+ }
134
+ // Default values
135
+ let stdout = "";
136
+ let stderr = "";
137
+ let exitCode = 0;
138
+
139
+ // Default to inheriting stdio unless explicitly set to false
140
+ const inheritStdio = props.inheritStdio !== false;
141
+
142
+ try {
143
+ // Parse the command into command and args
144
+ const [cmd, ...args] = props.command.split(/\s+/);
145
+
146
+ // Use spawn for better stdio control
147
+ const childProcess = spawn(cmd, args, {
148
+ cwd: props.cwd || process.cwd(),
149
+ env: { ...process.env, ...props.env },
150
+ shell: true, // Use shell to handle complex commands
151
+ stdio: inheritStdio ? "inherit" : "pipe", // Inherit stdio when requested
152
+ });
153
+
154
+ if (!inheritStdio) {
155
+ // If not inheriting stdio, collect output manually
156
+ childProcess.stdout?.on("data", (data) => {
157
+ stdout += data.toString();
151
158
  });
152
159
 
153
- if (!inheritStdio) {
154
- // If not inheriting stdio, collect output manually
155
- childProcess.stdout?.on("data", (data) => {
156
- stdout += data.toString();
157
- });
158
-
159
- childProcess.stderr?.on("data", (data) => {
160
- stderr += data.toString();
161
- });
162
- }
163
-
164
- // Wait for the process to complete
165
- exitCode = await new Promise<number>((resolve, reject) => {
166
- childProcess.on("close", (code) => {
167
- resolve(code || 0);
168
- });
169
-
170
- childProcess.on("error", (err) => {
171
- stderr += err.toString();
172
- resolve(1);
173
- });
160
+ childProcess.stderr?.on("data", (data) => {
161
+ stderr += data.toString();
174
162
  });
175
- } catch (error: any) {
176
- // If not throwing, capture the error information
177
- exitCode = 1;
178
- stderr += String(error);
179
163
  }
180
164
 
181
- if (exitCode !== 0) {
182
- throw new Error(
183
- `Command failed with exit code ${exitCode}: ${props.command}\n${stderr}`
184
- );
185
- }
165
+ // Wait for the process to complete
166
+ exitCode = await new Promise<number>((resolve, reject) => {
167
+ childProcess.on("close", (code) => {
168
+ resolve(code || 0);
169
+ });
186
170
 
187
- // Return the execution result
188
- return this({
189
- id,
190
- command: props.command,
191
- cwd: props.cwd,
192
- env: props.env,
193
- memoize: props.memoize,
194
- inheritStdio,
195
- exitCode,
196
- stdout,
197
- stderr,
198
- executedAt: Date.now(),
199
- completed: true,
171
+ childProcess.on("error", (err) => {
172
+ stderr += err.toString();
173
+ resolve(1);
174
+ });
200
175
  });
176
+ } catch (error: any) {
177
+ // If not throwing, capture the error information
178
+ exitCode = 1;
179
+ stderr += String(error);
201
180
  }
202
- }
181
+
182
+ if (exitCode !== 0) {
183
+ throw new Error(
184
+ `Command failed with exit code ${exitCode}: ${props.command}\n${stderr}`,
185
+ );
186
+ }
187
+
188
+ // Return the execution result
189
+ return this({
190
+ id,
191
+ command: props.command,
192
+ cwd: props.cwd,
193
+ env: props.env,
194
+ memoize: props.memoize,
195
+ inheritStdio,
196
+ exitCode,
197
+ stdout,
198
+ stderr,
199
+ executedAt: Date.now(),
200
+ completed: true,
201
+ });
202
+ },
203
203
  );
204
+
205
+ const defaultOptions: SpawnOptions = {
206
+ stdio: "inherit",
207
+ env: {
208
+ ...process.env,
209
+ },
210
+ shell: true,
211
+ };
212
+
213
+ /**
214
+ * Execute a shell command.
215
+ */
216
+ export async function exec(
217
+ command: string,
218
+ options?: Partial<SpawnOptions>,
219
+ ): Promise<void> {
220
+ const [cmd, ...args] = command.split(/\s+/);
221
+
222
+ return new Promise((resolve, reject) => {
223
+ const child = spawn(cmd, args, {
224
+ ...defaultOptions,
225
+ ...options,
226
+ env: {
227
+ ...defaultOptions.env,
228
+ ...options?.env,
229
+ },
230
+ });
231
+
232
+ child.on("close", (code) => {
233
+ if (code === 0) {
234
+ resolve();
235
+ } else {
236
+ reject(new Error(`Command failed with exit code ${code}`));
237
+ }
238
+ });
239
+
240
+ child.on("error", (err) => {
241
+ reject(err);
242
+ });
243
+ });
244
+ }
package/src/resource.ts CHANGED
@@ -69,7 +69,7 @@ type IsClass = {
69
69
  type ResourceLifecycleHandler = (
70
70
  this: Context<any, any>,
71
71
  id: string,
72
- props: any
72
+ props: any,
73
73
  ) => Promise<Resource<string>>;
74
74
 
75
75
  // see: https://x.com/samgoodwin89/status/1904640134097887653
@@ -100,7 +100,7 @@ export function Resource<
100
100
 
101
101
  const provider = ((
102
102
  resourceID: string,
103
- props: ResourceProps
103
+ props: ResourceProps,
104
104
  ): Promise<Resource<string>> => {
105
105
  const scope = _Scope.current;
106
106
 
@@ -116,7 +116,7 @@ export function Resource<
116
116
  if (otherResource?.Kind !== type) {
117
117
  scope.fail();
118
118
  throw new Error(
119
- `Resource ${resourceID} already exists in the stack and is of a different type: '${otherResource?.Kind}' !== '${type}'`
119
+ `Resource ${resourceID} already exists in the stack and is of a different type: '${otherResource?.Kind}' !== '${type}'`,
120
120
  );
121
121
  }
122
122
  // console.warn(
package/src/scope.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { Phase } from "./alchemy.js";
2
3
  import { destroy } from "./destroy.js";
3
4
  import { FileSystemStateStore } from "./fs/file-system-state-store.js";
4
5
  import type { PendingResource, ResourceID } from "./resource.js";
@@ -14,6 +15,7 @@ export type ScopeOptions = {
14
15
  password?: string;
15
16
  stateStore?: StateStoreType;
16
17
  quiet?: boolean;
18
+ phase?: Phase;
17
19
  };
18
20
 
19
21
  // TODO: support browser
@@ -40,6 +42,7 @@ export class Scope {
40
42
  public readonly password: string | undefined;
41
43
  public readonly state: StateStore;
42
44
  public readonly quiet: boolean;
45
+ public readonly phase: Phase;
43
46
 
44
47
  private isErrored = false;
45
48
 
@@ -56,6 +59,11 @@ export class Scope {
56
59
  this.state = options.stateStore
57
60
  ? options.stateStore(this)
58
61
  : new FileSystemStateStore(this);
62
+ const phase = options.phase ?? this.parent?.phase;
63
+ if (phase === undefined) {
64
+ throw new Error("Phase is required");
65
+ }
66
+ this.phase = phase;
59
67
  }
60
68
 
61
69
  public async delete(resourceID: ResourceID) {
@@ -74,9 +82,8 @@ export class Scope {
74
82
  const app = this.appName ? [this.appName] : [];
75
83
  if (this.parent) {
76
84
  return [...this.parent.chain, ...thisScope];
77
- } else {
78
- return [...app, this.stage, ...thisScope];
79
85
  }
86
+ return [...app, this.stage, ...thisScope];
80
87
  }
81
88
 
82
89
  public fail() {
@@ -110,15 +117,18 @@ export class Scope {
110
117
  }
111
118
 
112
119
  public async finalize() {
120
+ if (this.phase === "read") {
121
+ return;
122
+ }
113
123
  if (!this.isErrored) {
114
124
  // TODO: need to detect if it is in error
115
125
  const resourceIds = await this.state.list();
116
126
  const aliveIds = new Set(this.resources.keys());
117
127
  const orphanIds = Array.from(
118
- resourceIds.filter((id) => !aliveIds.has(id))
128
+ resourceIds.filter((id) => !aliveIds.has(id)),
119
129
  );
120
130
  const orphans = await Promise.all(
121
- orphanIds.map(async (id) => (await this.state.get(id))!.output)
131
+ orphanIds.map(async (id) => (await this.state.get(id))!.output),
122
132
  );
123
133
  await destroy.all(orphans, {
124
134
  quiet: this.quiet,
package/src/secret.ts CHANGED
@@ -100,13 +100,12 @@ export namespace secret {
100
100
  async function _env(
101
101
  name: string,
102
102
  value?: string,
103
- error?: string
103
+ error?: string,
104
104
  ): Promise<Secret> {
105
105
  const result = await alchemy.env(name, value, error);
106
106
  if (typeof result === "string") {
107
107
  return secret(result);
108
- } else {
109
- throw new Error(`Secret environment variable ${name} is not a string`);
110
108
  }
109
+ throw new Error(`Secret environment variable ${name} is not a string`);
111
110
  }
112
111
  }
package/src/serde.ts CHANGED
@@ -18,11 +18,12 @@ export async function serialize(
18
18
  value: any,
19
19
  options?: {
20
20
  encrypt?: boolean;
21
- }
21
+ },
22
22
  ): Promise<any> {
23
23
  if (Array.isArray(value)) {
24
24
  return Promise.all(value.map((value) => serialize(scope, value, options)));
25
- } else if (value instanceof Secret) {
25
+ }
26
+ if (value instanceof Secret) {
26
27
  if (!scope.password) {
27
28
  throw new Error("Cannot serialize secret without password");
28
29
  }
@@ -32,26 +33,31 @@ export async function serialize(
32
33
  ? await encrypt(value.unencrypted, scope.password)
33
34
  : value.unencrypted,
34
35
  };
35
- } else if (isType(value)) {
36
+ }
37
+ if (isType(value)) {
36
38
  return {
37
39
  "@schema": value.toJSON(),
38
40
  };
39
- } else if (value instanceof Date) {
41
+ }
42
+ if (value instanceof Date) {
40
43
  return {
41
44
  "@date": value.toISOString(),
42
45
  };
43
- } else if (value instanceof Scope) {
46
+ }
47
+ if (value instanceof Scope) {
44
48
  return undefined;
45
- } else if (value && typeof value === "object") {
49
+ }
50
+ if (value && typeof value === "object") {
46
51
  return Object.fromEntries(
47
52
  await Promise.all(
48
53
  Object.entries(value).map(async ([key, value]) => [
49
54
  key,
50
55
  await serialize(scope, value, options),
51
- ])
52
- )
56
+ ]),
57
+ ),
53
58
  );
54
- } else if (typeof value === "function") {
59
+ }
60
+ if (typeof value === "function") {
55
61
  // can't serialize functions
56
62
  return undefined;
57
63
  }
@@ -61,28 +67,30 @@ export async function serialize(
61
67
  export async function deserialize(scope: Scope, value: any): Promise<any> {
62
68
  if (Array.isArray(value)) {
63
69
  return await Promise.all(
64
- value.map(async (item) => await deserialize(scope, item))
70
+ value.map(async (item) => await deserialize(scope, item)),
65
71
  );
66
- } else if (value && typeof value === "object") {
72
+ }
73
+ if (value && typeof value === "object") {
67
74
  if (typeof value["@secret"] === "string") {
68
75
  if (!scope.password) {
69
76
  throw new Error("Cannot deserialize secret without password");
70
77
  }
71
78
  return new Secret(await decryptWithKey(value["@secret"], scope.password));
72
- } else if ("@schema" in value) {
79
+ }
80
+ if ("@schema" in value) {
73
81
  return value["@schema"];
74
- } else if ("@date" in value) {
82
+ }
83
+ if ("@date" in value) {
75
84
  return new Date(value["@date"]);
76
- } else {
77
- return Object.fromEntries(
78
- await Promise.all(
79
- Object.entries(value).map(async ([key, value]) => [
80
- key,
81
- await deserialize(scope, value),
82
- ])
83
- )
84
- );
85
85
  }
86
+ return Object.fromEntries(
87
+ await Promise.all(
88
+ Object.entries(value).map(async ([key, value]) => [
89
+ key,
90
+ await deserialize(scope, value),
91
+ ]),
92
+ ),
93
+ );
86
94
  }
87
95
  return value;
88
96
  }
@@ -180,7 +180,7 @@ export const Price = Resource(
180
180
  async function (
181
181
  this: Context<Price>,
182
182
  id: string,
183
- props: PriceProps
183
+ props: PriceProps,
184
184
  ): Promise<Price> {
185
185
  // Get Stripe API key from context or environment
186
186
  const apiKey = process.env.STRIPE_API_KEY;
@@ -203,87 +203,84 @@ export const Price = Resource(
203
203
  }
204
204
 
205
205
  return this.destroy();
206
- } else {
207
- try {
208
- let price: Stripe.Price;
209
-
210
- if (this.phase === "update" && this.output?.id) {
211
- // Update existing price (limited properties can be updated)
212
- price = await stripe.prices.update(this.output.id, {
213
- active: props.active,
214
- metadata: props.metadata,
215
- nickname: props.nickname,
216
- });
217
- } else {
218
- // Create new price
219
- const createParams: Stripe.PriceCreateParams = {
220
- currency: props.currency,
221
- product: props.product,
222
- active: props.active,
223
- billing_scheme: props.billingScheme,
224
- nickname: props.nickname,
225
- metadata: props.metadata,
226
- tax_behavior: props.taxBehavior,
227
- };
228
-
229
- // Add unit amount fields
230
- if (props.unitAmount !== undefined) {
231
- createParams.unit_amount = props.unitAmount;
232
- } else if (props.unitAmountDecimal !== undefined) {
233
- createParams.unit_amount_decimal = props.unitAmountDecimal;
234
- }
206
+ }
207
+ try {
208
+ let price: Stripe.Price;
235
209
 
236
- // Add recurring parameters if present
237
- if (props.recurring) {
238
- createParams.recurring = {
239
- interval: props.recurring.interval,
240
- interval_count: props.recurring.intervalCount,
241
- usage_type: props.recurring.usageType,
242
- aggregate_usage: props.recurring.aggregateUsage,
243
- };
244
- }
210
+ if (this.phase === "update" && this.output?.id) {
211
+ // Update existing price (limited properties can be updated)
212
+ price = await stripe.prices.update(this.output.id, {
213
+ active: props.active,
214
+ metadata: props.metadata,
215
+ nickname: props.nickname,
216
+ });
217
+ } else {
218
+ // Create new price
219
+ const createParams: Stripe.PriceCreateParams = {
220
+ currency: props.currency,
221
+ product: props.product,
222
+ active: props.active,
223
+ billing_scheme: props.billingScheme,
224
+ nickname: props.nickname,
225
+ metadata: props.metadata,
226
+ tax_behavior: props.taxBehavior,
227
+ };
245
228
 
246
- price = await stripe.prices.create(createParams);
229
+ // Add unit amount fields
230
+ if (props.unitAmount !== undefined) {
231
+ createParams.unit_amount = props.unitAmount;
232
+ } else if (props.unitAmountDecimal !== undefined) {
233
+ createParams.unit_amount_decimal = props.unitAmountDecimal;
247
234
  }
248
235
 
249
- // Transform Stripe recurring object to our format
250
- const recurring = price.recurring
251
- ? {
252
- interval: price.recurring
253
- .interval as Stripe.PriceCreateParams.Recurring.Interval,
254
- intervalCount: price.recurring.interval_count,
255
- usageType: price.recurring
256
- .usage_type as Stripe.PriceCreateParams.Recurring.UsageType,
257
- aggregateUsage: price.recurring
258
- .aggregate_usage as Stripe.PriceCreateParams.Recurring.AggregateUsage,
259
- }
260
- : undefined;
236
+ // Add recurring parameters if present
237
+ if (props.recurring) {
238
+ createParams.recurring = {
239
+ interval: props.recurring.interval,
240
+ interval_count: props.recurring.intervalCount,
241
+ usage_type: props.recurring.usageType,
242
+ aggregate_usage: props.recurring.aggregateUsage,
243
+ };
244
+ }
261
245
 
262
- // Map Stripe API response to our output format
263
- return this({
264
- id: price.id,
265
- product:
266
- typeof price.product === "string"
267
- ? price.product
268
- : price.product.id,
269
- currency: price.currency,
270
- unitAmount: price.unit_amount || undefined,
271
- unitAmountDecimal: price.unit_amount_decimal || undefined,
272
- active: price.active,
273
- billingScheme: price.billing_scheme as BillingScheme,
274
- nickname: price.nickname || undefined,
275
- recurring: recurring,
276
- metadata: price.metadata || undefined,
277
- taxBehavior: price.tax_behavior as TaxBehavior,
278
- createdAt: price.created,
279
- livemode: price.livemode,
280
- type: price.type as Stripe.Price.Type,
281
- lookupKey: price.lookup_key || undefined,
282
- });
283
- } catch (error) {
284
- console.error("Error creating/updating price:", error);
285
- throw error;
246
+ price = await stripe.prices.create(createParams);
286
247
  }
248
+
249
+ // Transform Stripe recurring object to our format
250
+ const recurring = price.recurring
251
+ ? {
252
+ interval: price.recurring
253
+ .interval as Stripe.PriceCreateParams.Recurring.Interval,
254
+ intervalCount: price.recurring.interval_count,
255
+ usageType: price.recurring
256
+ .usage_type as Stripe.PriceCreateParams.Recurring.UsageType,
257
+ aggregateUsage: price.recurring
258
+ .aggregate_usage as Stripe.PriceCreateParams.Recurring.AggregateUsage,
259
+ }
260
+ : undefined;
261
+
262
+ // Map Stripe API response to our output format
263
+ return this({
264
+ id: price.id,
265
+ product:
266
+ typeof price.product === "string" ? price.product : price.product.id,
267
+ currency: price.currency,
268
+ unitAmount: price.unit_amount || undefined,
269
+ unitAmountDecimal: price.unit_amount_decimal || undefined,
270
+ active: price.active,
271
+ billingScheme: price.billing_scheme as BillingScheme,
272
+ nickname: price.nickname || undefined,
273
+ recurring: recurring,
274
+ metadata: price.metadata || undefined,
275
+ taxBehavior: price.tax_behavior as TaxBehavior,
276
+ createdAt: price.created,
277
+ livemode: price.livemode,
278
+ type: price.type as Stripe.Price.Type,
279
+ lookupKey: price.lookup_key || undefined,
280
+ });
281
+ } catch (error) {
282
+ console.error("Error creating/updating price:", error);
283
+ throw error;
287
284
  }
288
- }
285
+ },
289
286
  );