alchemy 0.12.21 → 0.13.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 (269) 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 -0
  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.js +1 -5
  37. package/lib/cloudflare/api.js.map +1 -1
  38. package/lib/cloudflare/bucket.d.ts +2 -2
  39. package/lib/cloudflare/bucket.d.ts.map +1 -1
  40. package/lib/cloudflare/bucket.js +22 -24
  41. package/lib/cloudflare/bucket.js.map +1 -1
  42. package/lib/cloudflare/bundle/hybrid-nodejs-compat.d.ts +0 -3
  43. package/lib/cloudflare/bundle/hybrid-nodejs-compat.d.ts.map +1 -1
  44. package/lib/cloudflare/bundle/hybrid-nodejs-compat.js +3 -4
  45. package/lib/cloudflare/bundle/hybrid-nodejs-compat.js.map +1 -1
  46. package/lib/cloudflare/custom-domain.d.ts.map +1 -1
  47. package/lib/cloudflare/custom-domain.js +3 -5
  48. package/lib/cloudflare/custom-domain.js.map +1 -1
  49. package/lib/cloudflare/d1-database.d.ts +1 -1
  50. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  51. package/lib/cloudflare/d1-database.js +70 -72
  52. package/lib/cloudflare/d1-database.js.map +1 -1
  53. package/lib/cloudflare/d1-migrations.d.ts +1 -1
  54. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  55. package/lib/cloudflare/d1-migrations.js +2 -3
  56. package/lib/cloudflare/d1-migrations.js.map +1 -1
  57. package/lib/cloudflare/dns-records.d.ts.map +1 -1
  58. package/lib/cloudflare/dns-records.js +2 -4
  59. package/lib/cloudflare/dns-records.js.map +1 -1
  60. package/lib/cloudflare/kv-namespace.d.ts +1 -1
  61. package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
  62. package/lib/cloudflare/kv-namespace.js +24 -27
  63. package/lib/cloudflare/kv-namespace.js.map +1 -1
  64. package/lib/cloudflare/pipeline.d.ts +1 -1
  65. package/lib/cloudflare/pipeline.d.ts.map +1 -1
  66. package/lib/cloudflare/pipeline.js +32 -34
  67. package/lib/cloudflare/pipeline.js.map +1 -1
  68. package/lib/cloudflare/queue-consumer.d.ts +1 -1
  69. package/lib/cloudflare/queue-consumer.d.ts.map +1 -1
  70. package/lib/cloudflare/queue-consumer.js +31 -33
  71. package/lib/cloudflare/queue-consumer.js.map +1 -1
  72. package/lib/cloudflare/queue.d.ts +1 -1
  73. package/lib/cloudflare/queue.d.ts.map +1 -1
  74. package/lib/cloudflare/queue.js +36 -38
  75. package/lib/cloudflare/queue.js.map +1 -1
  76. package/lib/cloudflare/r2-rest-state-store.js +4 -8
  77. package/lib/cloudflare/r2-rest-state-store.js.map +1 -1
  78. package/lib/cloudflare/types.d.ts.map +1 -1
  79. package/lib/cloudflare/types.js +1 -3
  80. package/lib/cloudflare/types.js.map +1 -1
  81. package/lib/cloudflare/vectorize-index.d.ts.map +1 -1
  82. package/lib/cloudflare/vectorize-index.js +37 -39
  83. package/lib/cloudflare/vectorize-index.js.map +1 -1
  84. package/lib/cloudflare/vectorize-metadata-index.d.ts +1 -1
  85. package/lib/cloudflare/vectorize-metadata-index.d.ts.map +1 -1
  86. package/lib/cloudflare/vectorize-metadata-index.js +10 -13
  87. package/lib/cloudflare/vectorize-metadata-index.js.map +1 -1
  88. package/lib/cloudflare/worker.js +6 -12
  89. package/lib/cloudflare/worker.js.map +1 -1
  90. package/lib/cloudflare/workflow.d.ts +1 -1
  91. package/lib/cloudflare/workflow.d.ts.map +1 -1
  92. package/lib/cloudflare/workflow.js.map +1 -1
  93. package/lib/cloudflare/zone.d.ts.map +1 -1
  94. package/lib/cloudflare/zone.js +48 -50
  95. package/lib/cloudflare/zone.js.map +1 -1
  96. package/lib/dns/godaddy.js +1 -1
  97. package/lib/dns/import-dns.js +1 -1
  98. package/lib/dns/import-dns.js.map +1 -1
  99. package/lib/env.js +7 -9
  100. package/lib/env.js.map +1 -1
  101. package/lib/esbuild/bundle.js +8 -10
  102. package/lib/esbuild/bundle.js.map +1 -1
  103. package/lib/fs/copy-file.d.ts.map +1 -1
  104. package/lib/fs/copy-file.js +27 -29
  105. package/lib/fs/copy-file.js.map +1 -1
  106. package/lib/fs/file.d.ts +1 -1
  107. package/lib/fs/file.d.ts.map +1 -1
  108. package/lib/fs/file.js +1 -1
  109. package/lib/fs/file.js.map +1 -1
  110. package/lib/fs/folder.d.ts.map +1 -1
  111. package/lib/fs/folder.js +1 -3
  112. package/lib/fs/folder.js.map +1 -1
  113. package/lib/github/client.d.ts.map +1 -1
  114. package/lib/github/client.js +3 -5
  115. package/lib/github/client.js.map +1 -1
  116. package/lib/github/repository-environment.d.ts.map +1 -1
  117. package/lib/github/repository-environment.js +173 -176
  118. package/lib/github/repository-environment.js.map +1 -1
  119. package/lib/github/secret.d.ts.map +1 -1
  120. package/lib/github/secret.js +97 -99
  121. package/lib/github/secret.js.map +1 -1
  122. package/lib/internal/docs/providers.d.ts.map +1 -1
  123. package/lib/internal/docs/providers.js +5 -7
  124. package/lib/internal/docs/providers.js.map +1 -1
  125. package/lib/neon/api.js +1 -2
  126. package/lib/neon/api.js.map +1 -1
  127. package/lib/neon/project.js +2 -2
  128. package/lib/neon/project.js.map +1 -1
  129. package/lib/os/exec.d.ts +5 -0
  130. package/lib/os/exec.d.ts.map +1 -1
  131. package/lib/os/exec.js +88 -56
  132. package/lib/os/exec.js.map +1 -1
  133. package/lib/scope.d.ts +3 -0
  134. package/lib/scope.d.ts.map +1 -1
  135. package/lib/scope.js +10 -3
  136. package/lib/scope.js.map +1 -1
  137. package/lib/secret.d.ts.map +1 -1
  138. package/lib/secret.js +1 -3
  139. package/lib/secret.js.map +1 -1
  140. package/lib/serde.d.ts.map +1 -1
  141. package/lib/serde.js +13 -15
  142. package/lib/serde.js.map +1 -1
  143. package/lib/stripe/price.d.ts.map +1 -1
  144. package/lib/stripe/price.js +69 -73
  145. package/lib/stripe/price.js.map +1 -1
  146. package/lib/stripe/product.d.ts.map +1 -1
  147. package/lib/stripe/product.js +50 -52
  148. package/lib/stripe/product.js.map +1 -1
  149. package/lib/stripe/webhook.d.ts.map +1 -1
  150. package/lib/stripe/webhook.js +51 -53
  151. package/lib/stripe/webhook.js.map +1 -1
  152. package/lib/test/bun.d.ts +1 -1
  153. package/lib/test/bun.d.ts.map +1 -1
  154. package/lib/test/bun.js +1 -0
  155. package/lib/test/bun.js.map +1 -1
  156. package/lib/test/prune.d.ts.map +1 -1
  157. package/lib/test/prune.js.map +1 -1
  158. package/lib/util/dedent.js +4 -4
  159. package/lib/util/dedent.js.map +1 -1
  160. package/lib/web/astro.js +1 -1
  161. package/lib/web/shadcn.js +4 -4
  162. package/lib/web/shadcn.js.map +1 -1
  163. package/lib/web/tailwind.d.ts.map +1 -1
  164. package/lib/web/tailwind.js +0 -1
  165. package/lib/web/tailwind.js.map +1 -1
  166. package/lib/web/vite.js +3 -3
  167. package/lib/web/vitepress/home-page.d.ts.map +1 -1
  168. package/lib/web/vitepress/home-page.js +6 -8
  169. package/lib/web/vitepress/home-page.js.map +1 -1
  170. package/lib/web/vitepress/process-front-matter-files.js +1 -1
  171. package/lib/web/vitepress/process-front-matter-files.js.map +1 -1
  172. package/lib/web/vitepress/vitepress.js +2 -2
  173. package/lib/web/vitepress/vitepress.js.map +1 -1
  174. package/package.json +1 -1
  175. package/src/ai/astro-file.ts +4 -4
  176. package/src/ai/client.ts +4 -5
  177. package/src/ai/css-file.ts +4 -4
  178. package/src/ai/document.ts +3 -3
  179. package/src/ai/html-file.ts +4 -4
  180. package/src/ai/json-file.ts +1 -1
  181. package/src/ai/typescript-file.ts +4 -4
  182. package/src/ai/vue-file.ts +4 -4
  183. package/src/ai/yaml-file.ts +1 -1
  184. package/src/alchemy.ts +111 -104
  185. package/src/apply.ts +13 -5
  186. package/src/aws/bucket.ts +72 -75
  187. package/src/aws/function.ts +296 -297
  188. package/src/aws/oidc/github-oidc-provider.ts +1 -1
  189. package/src/aws/oidc/oidc-provider.ts +15 -15
  190. package/src/aws/policy-attachment.ts +10 -11
  191. package/src/aws/policy.ts +91 -94
  192. package/src/aws/queue.ts +111 -111
  193. package/src/aws/role.ts +24 -24
  194. package/src/aws/ses.ts +17 -17
  195. package/src/aws/table.ts +114 -115
  196. package/src/cloudflare/account-api-token.ts +11 -11
  197. package/src/cloudflare/account-id.ts +1 -1
  198. package/src/cloudflare/ai-gateway.ts +4 -4
  199. package/src/cloudflare/ai.ts +1 -1
  200. package/src/cloudflare/api-error.ts +1 -1
  201. package/src/cloudflare/api.ts +7 -11
  202. package/src/cloudflare/assets.ts +4 -4
  203. package/src/cloudflare/auth.ts +13 -13
  204. package/src/cloudflare/bucket.ts +62 -63
  205. package/src/cloudflare/bundle/hybrid-nodejs-compat.ts +13 -14
  206. package/src/cloudflare/custom-domain.ts +27 -28
  207. package/src/cloudflare/d1-database.ts +101 -102
  208. package/src/cloudflare/d1-migrations.ts +10 -10
  209. package/src/cloudflare/dns-records.ts +23 -24
  210. package/src/cloudflare/durable-object-namespace.ts +1 -1
  211. package/src/cloudflare/event-source.ts +1 -1
  212. package/src/cloudflare/hyperdrive.ts +3 -3
  213. package/src/cloudflare/kv-namespace.ts +36 -37
  214. package/src/cloudflare/nuxt.ts +1 -1
  215. package/src/cloudflare/permission-groups.ts +7 -7
  216. package/src/cloudflare/pipeline.ts +55 -56
  217. package/src/cloudflare/queue-consumer.ts +53 -54
  218. package/src/cloudflare/queue.ts +56 -57
  219. package/src/cloudflare/r2-rest-state-store.ts +11 -11
  220. package/src/cloudflare/redwood.ts +1 -1
  221. package/src/cloudflare/tanstack-start.ts +1 -1
  222. package/src/cloudflare/types.ts +1 -2
  223. package/src/cloudflare/vectorize-index.ts +60 -61
  224. package/src/cloudflare/vectorize-metadata-index.ts +25 -25
  225. package/src/cloudflare/vite.ts +1 -1
  226. package/src/cloudflare/website.ts +3 -3
  227. package/src/cloudflare/worker.ts +61 -61
  228. package/src/cloudflare/workflow.ts +4 -4
  229. package/src/cloudflare/wrangler.json.ts +4 -4
  230. package/src/cloudflare/zone.ts +69 -70
  231. package/src/destroy.ts +4 -4
  232. package/src/dns/godaddy.ts +1 -1
  233. package/src/dns/import-dns.ts +5 -5
  234. package/src/encrypt.ts +6 -6
  235. package/src/env.ts +10 -10
  236. package/src/esbuild/bundle.ts +14 -14
  237. package/src/fs/copy-file.ts +35 -36
  238. package/src/fs/file-system-state-store.ts +4 -4
  239. package/src/fs/file.ts +9 -8
  240. package/src/fs/folder.ts +6 -7
  241. package/src/github/client.ts +15 -14
  242. package/src/github/repository-environment.ts +206 -210
  243. package/src/github/secret.ts +111 -116
  244. package/src/internal/docs/providers.ts +16 -17
  245. package/src/neon/api-error.ts +1 -1
  246. package/src/neon/api.ts +6 -6
  247. package/src/neon/project.ts +18 -17
  248. package/src/os/exec.ts +107 -66
  249. package/src/resource.ts +3 -3
  250. package/src/scope.ts +14 -4
  251. package/src/secret.ts +2 -3
  252. package/src/serde.ts +30 -22
  253. package/src/stripe/price.ts +74 -77
  254. package/src/stripe/product.ts +55 -56
  255. package/src/stripe/webhook.ts +56 -57
  256. package/src/test/bun.ts +7 -6
  257. package/src/test/prune.ts +4 -4
  258. package/src/util/dedent.ts +4 -4
  259. package/src/web/astro.ts +10 -10
  260. package/src/web/shadcn-component.ts +4 -4
  261. package/src/web/shadcn.ts +10 -10
  262. package/src/web/tailwind.ts +2 -3
  263. package/src/web/vite.ts +11 -11
  264. package/src/web/vitepress/config.ts +2 -2
  265. package/src/web/vitepress/custom-theme.ts +7 -7
  266. package/src/web/vitepress/dependencies.ts +4 -4
  267. package/src/web/vitepress/home-page.ts +8 -9
  268. package/src/web/vitepress/process-front-matter-files.ts +5 -5
  269. package/src/web/vitepress/vitepress.ts +7 -7
package/src/alchemy.ts CHANGED
@@ -98,10 +98,12 @@ async function _alchemy(
98
98
  ): Promise<Scope | string | never> {
99
99
  if (typeof args[0] === "string") {
100
100
  const [appName, options] = args as [string, AlchemyOptions?];
101
+ const phase = options?.phase ?? "up";
101
102
  const root = new Scope({
102
103
  ...options,
103
104
  appName,
104
105
  stage: options?.stage,
106
+ phase,
105
107
  });
106
108
  root.enter();
107
109
  if (options?.phase === "destroy") {
@@ -109,118 +111,123 @@ async function _alchemy(
109
111
  return process.exit(0);
110
112
  }
111
113
  return root;
112
- } else {
113
- const [template, ...values] = args;
114
- const [, secondLine] = template[0].split("\n");
115
- const leadingSpaces = secondLine
116
- ? secondLine.match(/^(\s*)/)?.[1]?.length || 0
117
- : 0;
118
- const indent = " ".repeat(leadingSpaces);
114
+ }
115
+ const [template, ...values] = args;
116
+ const [, secondLine] = template[0].split("\n");
117
+ const leadingSpaces = secondLine
118
+ ? secondLine.match(/^(\s*)/)?.[1]?.length || 0
119
+ : 0;
120
+ const indent = " ".repeat(leadingSpaces);
119
121
 
120
- const [{ isFileRef }, { isFileCollection }] = await Promise.all([
121
- import("./fs/file-ref"),
122
- import("./fs/file-collection"),
123
- ]);
122
+ const [{ isFileRef }, { isFileCollection }] = await Promise.all([
123
+ import("./fs/file-ref.js"),
124
+ import("./fs/file-collection.js"),
125
+ ]);
124
126
 
125
- const appendices: Record<string, string> = {};
127
+ const appendices: Record<string, string> = {};
126
128
 
127
- const stringValues = await Promise.all(
128
- values.map(async function resolve(value): Promise<string> {
129
- if (typeof value === "string") {
130
- return indent + value;
131
- } else if (value === null) {
132
- return "null";
133
- } else if (value === undefined) {
134
- return "undefined";
135
- } else if (
136
- typeof value === "number" ||
137
- typeof value === "boolean" ||
138
- typeof value === "bigint"
139
- ) {
140
- return value.toString();
141
- } else if (value instanceof Promise) {
142
- return resolve(await value);
143
- } else if (isFileRef(value)) {
144
- if (!(value.path in appendices)) {
145
- appendices[value.path] = await fs.readFile(value.path, "utf-8");
146
- }
129
+ const stringValues = await Promise.all(
130
+ values.map(async function resolve(value): Promise<string> {
131
+ if (typeof value === "string") {
132
+ return indent + value;
133
+ }
134
+ if (value === null) {
135
+ return "null";
136
+ }
137
+ if (value === undefined) {
138
+ return "undefined";
139
+ }
140
+ if (
141
+ typeof value === "number" ||
142
+ typeof value === "boolean" ||
143
+ typeof value === "bigint"
144
+ ) {
145
+ return value.toString();
146
+ }
147
+ if (value instanceof Promise) {
148
+ return resolve(await value);
149
+ }
150
+ if (isFileRef(value)) {
151
+ if (!(value.path in appendices)) {
152
+ appendices[value.path] = await fs.readFile(value.path, "utf-8");
153
+ }
154
+ return `[${path.basename(value.path)}](${value.path})`;
155
+ }
156
+ if (isFileCollection(value)) {
157
+ return Object.entries(value.files)
158
+ .map(([filePath, content]) => {
159
+ appendices[filePath] = content;
160
+ return `[${path.basename(filePath)}](${filePath})`;
161
+ })
162
+ .join("\n\n");
163
+ }
164
+ if (Array.isArray(value)) {
165
+ return (
166
+ await Promise.all(
167
+ value.map(async (value, i) => `${i}. ${await resolve(value)}`),
168
+ )
169
+ ).join("\n");
170
+ }
171
+ if (typeof value === "object" && typeof value.path === "string") {
172
+ if (typeof value.content === "string") {
173
+ appendices[value.path] = value.content;
147
174
  return `[${path.basename(value.path)}](${value.path})`;
148
- } else if (isFileCollection(value)) {
149
- return Object.entries(value.files)
150
- .map(([filePath, content]) => {
151
- appendices[filePath] = content;
152
- return `[${path.basename(filePath)}](${filePath})`;
153
- })
154
- .join("\n\n");
155
- } else if (Array.isArray(value)) {
156
- return (
157
- await Promise.all(
158
- value.map(async (value, i) => `${i}. ${await resolve(value)}`)
159
- )
160
- ).join("\n");
161
- } else if (
162
- typeof value === "object" &&
163
- typeof value.path === "string"
164
- ) {
165
- if (typeof value.content === "string") {
166
- appendices[value.path] = value.content;
167
- return `[${path.basename(value.path)}](${value.path})`;
168
- } else {
169
- appendices[value.path] = await fs.readFile(value.path, "utf-8");
170
- return `[${path.basename(value.path)}](${value.path})`;
171
- }
172
- } else if (typeof value === "object") {
173
- return (
174
- await Promise.all(
175
- Object.entries(value).map(async ([key, value]) => {
176
- return `* ${key}: ${await resolve(value)}`;
177
- })
178
- )
179
- ).join("\n");
180
- } else {
181
- // TODO: support other types
182
- console.log(value);
183
- throw new Error(`Unsupported value type: ${value}`);
184
175
  }
185
- })
186
- );
187
-
188
- // Construct the string template by joining template parts with interpolated values
189
- const lines = template
190
- .map((part) =>
191
- part
192
- .split("\n")
193
- .map((line) =>
194
- line.startsWith(indent) ? line.slice(indent.length) : line
176
+ appendices[value.path] = await fs.readFile(value.path, "utf-8");
177
+ return `[${path.basename(value.path)}](${value.path})`;
178
+ }
179
+ if (typeof value === "object") {
180
+ return (
181
+ await Promise.all(
182
+ Object.entries(value).map(async ([key, value]) => {
183
+ return `* ${key}: ${await resolve(value)}`;
184
+ }),
195
185
  )
196
- .join("\n")
197
- )
198
- .flatMap((part, i) =>
199
- i < stringValues.length ? [part, stringValues[i] ?? ""] : [part]
200
- )
201
- .join("")
202
- .split("\n");
186
+ ).join("\n");
187
+ }
188
+ // TODO: support other types
189
+ console.log(value);
190
+ throw new Error(`Unsupported value type: ${value}`);
191
+ }),
192
+ );
203
193
 
204
- // Collect and sort appendices by file path
205
- return [
206
- // format the user prompt and trim the first line if it's empty
207
- lines.length > 1 && lines[0].replaceAll(" ", "").length === 0
208
- ? lines.slice(1).join("\n")
209
- : lines.join("\n"),
194
+ // Construct the string template by joining template parts with interpolated values
195
+ const lines = template
196
+ .map((part) =>
197
+ part
198
+ .split("\n")
199
+ .map((line) =>
200
+ line.startsWith(indent) ? line.slice(indent.length) : line,
201
+ )
202
+ .join("\n"),
203
+ )
204
+ .flatMap((part, i) =>
205
+ i < stringValues.length ? [part, stringValues[i] ?? ""] : [part],
206
+ )
207
+ .join("")
208
+ .split("\n");
210
209
 
211
- // sort appendices by path and include at the end of the prompt
212
- Object.entries(appendices)
213
- .sort(([a], [b]) => a.localeCompare(b))
214
- .map(([filePath, content]) => {
215
- const extension = path.extname(filePath).slice(1);
216
- const codeTag = extension ? extension : "";
217
- return `// ${filePath}\n\`\`\`${codeTag}\n${content}\n\`\`\``;
218
- })
219
- .join("\n\n"),
220
- ].join("\n");
221
- }
210
+ // Collect and sort appendices by file path
211
+ return [
212
+ // format the user prompt and trim the first line if it's empty
213
+ lines.length > 1 && lines[0].replaceAll(" ", "").length === 0
214
+ ? lines.slice(1).join("\n")
215
+ : lines.join("\n"),
216
+
217
+ // sort appendices by path and include at the end of the prompt
218
+ Object.entries(appendices)
219
+ .sort(([a], [b]) => a.localeCompare(b))
220
+ .map(([filePath, content]) => {
221
+ const extension = path.extname(filePath).slice(1);
222
+ const codeTag = extension ? extension : "";
223
+ return `// ${filePath}\n\`\`\`${codeTag}\n${content}\n\`\`\``;
224
+ })
225
+ .join("\n\n"),
226
+ ].join("\n");
222
227
  }
223
228
 
229
+ export type Phase = "up" | "destroy" | "read";
230
+
224
231
  export interface AlchemyOptions {
225
232
  /**
226
233
  * The name of the application.
@@ -231,7 +238,7 @@ export interface AlchemyOptions {
231
238
  *
232
239
  * @default "up"
233
240
  */
234
- phase?: "up" | "destroy";
241
+ phase?: Phase;
235
242
  /**
236
243
  * Name to scope the resource state under (e.g. `.alchemy/{stage}/..`).
237
244
  *
@@ -337,7 +344,7 @@ async function run<T>(
337
344
  await _scope.parent!.state.set(id, resource);
338
345
  _scope.parent!.resources.set(
339
346
  id,
340
- Object.assign(Promise.resolve(resource), output) as PendingResource
347
+ Object.assign(Promise.resolve(resource), output) as PendingResource,
341
348
  );
342
349
  }
343
350
  return await _scope.run(async () => fn.bind(_scope)(_scope));
package/src/apply.ts CHANGED
@@ -18,7 +18,7 @@ export interface ApplyOptions {
18
18
  export async function apply<Out extends Resource>(
19
19
  resource: PendingResource<Out>,
20
20
  props: ResourceProps | undefined,
21
- options?: ApplyOptions
21
+ options?: ApplyOptions,
22
22
  ): Promise<Awaited<Out>> {
23
23
  const scope = resource.Scope;
24
24
  try {
@@ -29,6 +29,14 @@ export async function apply<Out extends Resource>(
29
29
  if (provider === undefined) {
30
30
  throw new Error(`Provider "${resource.Kind}" not found`);
31
31
  }
32
+ if (scope.phase === "read") {
33
+ if (state === undefined) {
34
+ throw new Error(
35
+ `Resource "${resource.FQN}" not found and running in 'read' phase.`,
36
+ );
37
+ }
38
+ return state.output as Awaited<Out>;
39
+ }
32
40
  if (state === undefined) {
33
41
  state = {
34
42
  kind: resource.Kind,
@@ -79,7 +87,7 @@ export async function apply<Out extends Resource>(
79
87
 
80
88
  if (!quiet) {
81
89
  console.log(
82
- `${phase === "create" ? "Create" : "Update"}: "${resource.FQN}"`
90
+ `${phase === "create" ? "Create" : "Update"}: "${resource.FQN}"`,
83
91
  );
84
92
  }
85
93
 
@@ -99,7 +107,7 @@ export async function apply<Out extends Resource>(
99
107
  replace: () => {
100
108
  if (isReplaced) {
101
109
  console.warn(
102
- `Resource ${resource.Kind} ${resource.FQN} is already marked as REPLACE`
110
+ `Resource ${resource.Kind} ${resource.FQN} is already marked as REPLACE`,
103
111
  );
104
112
  return;
105
113
  }
@@ -112,11 +120,11 @@ export async function apply<Out extends Resource>(
112
120
  {
113
121
  isResource: true,
114
122
  },
115
- async () => provider.handler.bind(ctx)(resource.ID, props)
123
+ async () => provider.handler.bind(ctx)(resource.ID, props),
116
124
  );
117
125
  if (!quiet) {
118
126
  console.log(
119
- `${phase === "create" ? "Created" : "Updated"}: "${resource.FQN}"`
127
+ `${phase === "create" ? "Created" : "Updated"}: "${resource.FQN}"`,
120
128
  );
121
129
  }
122
130
 
package/src/aws/bucket.ts CHANGED
@@ -136,99 +136,96 @@ export const Bucket = Resource(
136
136
  client.send(
137
137
  new DeleteBucketCommand({
138
138
  Bucket: props.bucketName,
139
- })
140
- )
139
+ }),
140
+ ),
141
141
  );
142
142
  return this.destroy();
143
- } else {
144
- try {
145
- // Check if bucket exists
143
+ }
144
+ try {
145
+ // Check if bucket exists
146
+ await client.send(
147
+ new HeadBucketCommand({
148
+ Bucket: props.bucketName,
149
+ }),
150
+ );
151
+
152
+ // Update tags if they changed and bucket exists
153
+ if (this.phase === "update" && props.tags) {
146
154
  await client.send(
147
- new HeadBucketCommand({
155
+ new PutBucketTaggingCommand({
148
156
  Bucket: props.bucketName,
149
- })
157
+ Tagging: {
158
+ TagSet: Object.entries(props.tags).map(([Key, Value]) => ({
159
+ Key,
160
+ Value,
161
+ })),
162
+ },
163
+ }),
150
164
  );
151
-
152
- // Update tags if they changed and bucket exists
153
- if (this.phase === "update" && props.tags) {
154
- await client.send(
155
- new PutBucketTaggingCommand({
156
- Bucket: props.bucketName,
165
+ }
166
+ } catch (error: any) {
167
+ if (error.name === "NotFound") {
168
+ // Create bucket if it doesn't exist
169
+ await client.send(
170
+ new CreateBucketCommand({
171
+ Bucket: props.bucketName,
172
+ // Add tags during creation if specified
173
+ ...(props.tags && {
157
174
  Tagging: {
158
175
  TagSet: Object.entries(props.tags).map(([Key, Value]) => ({
159
176
  Key,
160
177
  Value,
161
178
  })),
162
179
  },
163
- })
164
- );
165
- }
166
- } catch (error: any) {
167
- if (error.name === "NotFound") {
168
- // Create bucket if it doesn't exist
169
- await client.send(
170
- new CreateBucketCommand({
171
- Bucket: props.bucketName,
172
- // Add tags during creation if specified
173
- ...(props.tags && {
174
- Tagging: {
175
- TagSet: Object.entries(props.tags).map(([Key, Value]) => ({
176
- Key,
177
- Value,
178
- })),
179
- },
180
- }),
181
- })
182
- );
183
- } else {
184
- throw error;
185
- }
180
+ }),
181
+ }),
182
+ );
183
+ } else {
184
+ throw error;
186
185
  }
186
+ }
187
187
 
188
- // Get bucket details
189
- const [locationResponse, versioningResponse, aclResponse] =
190
- await Promise.all([
191
- client.send(
192
- new GetBucketLocationCommand({ Bucket: props.bucketName })
193
- ),
194
- client.send(
195
- new GetBucketVersioningCommand({ Bucket: props.bucketName })
196
- ),
197
- client.send(new GetBucketAclCommand({ Bucket: props.bucketName })),
198
- ]);
188
+ // Get bucket details
189
+ const [locationResponse, versioningResponse, aclResponse] =
190
+ await Promise.all([
191
+ client.send(new GetBucketLocationCommand({ Bucket: props.bucketName })),
192
+ client.send(
193
+ new GetBucketVersioningCommand({ Bucket: props.bucketName }),
194
+ ),
195
+ client.send(new GetBucketAclCommand({ Bucket: props.bucketName })),
196
+ ]);
199
197
 
200
- const region = locationResponse.LocationConstraint || "us-east-1";
198
+ const region = locationResponse.LocationConstraint || "us-east-1";
201
199
 
202
- // Get tags if they exist
203
- let tags = props.tags;
204
- if (!tags) {
205
- try {
206
- const taggingResponse = await client.send(
207
- new GetBucketTaggingCommand({ Bucket: props.bucketName })
208
- );
209
- tags = Object.fromEntries(
210
- taggingResponse.TagSet?.map(({ Key, Value }) => [Key, Value]) || []
211
- );
212
- } catch (error: any) {
213
- if (error.name !== "NoSuchTagSet") {
214
- throw error;
215
- }
200
+ // Get tags if they exist
201
+ let tags = props.tags;
202
+ if (!tags) {
203
+ try {
204
+ const taggingResponse = await client.send(
205
+ new GetBucketTaggingCommand({ Bucket: props.bucketName }),
206
+ );
207
+ tags = Object.fromEntries(
208
+ taggingResponse.TagSet?.map(({ Key, Value }) => [Key, Value]) || [],
209
+ );
210
+ } catch (error: any) {
211
+ if (error.name !== "NoSuchTagSet") {
212
+ throw error;
216
213
  }
217
214
  }
218
-
219
- return this({
220
- bucketName: props.bucketName,
221
- arn: `arn:aws:s3:::${props.bucketName}`,
222
- bucketDomainName: `${props.bucketName}.s3.amazonaws.com`,
223
- bucketRegionalDomainName: `${props.bucketName}.s3.${region}.amazonaws.com`,
224
- region,
225
- hostedZoneId: getHostedZoneId(region),
226
- versioningEnabled: versioningResponse.Status === "Enabled",
227
- acl: aclResponse.Grants?.[0]?.Permission?.toLowerCase(),
228
- ...(tags && { tags }),
229
- });
230
215
  }
231
- }
216
+
217
+ return this({
218
+ bucketName: props.bucketName,
219
+ arn: `arn:aws:s3:::${props.bucketName}`,
220
+ bucketDomainName: `${props.bucketName}.s3.amazonaws.com`,
221
+ bucketRegionalDomainName: `${props.bucketName}.s3.${region}.amazonaws.com`,
222
+ region,
223
+ hostedZoneId: getHostedZoneId(region),
224
+ versioningEnabled: versioningResponse.Status === "Enabled",
225
+ acl: aclResponse.Grants?.[0]?.Permission?.toLowerCase(),
226
+ ...(tags && { tags }),
227
+ });
228
+ },
232
229
  );
233
230
 
234
231
  /**