alchemy 0.1.18 → 0.2.1

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 (330) hide show
  1. package/README.md +506 -0
  2. package/lib/alchemy.d.ts +67 -0
  3. package/lib/alchemy.js +48 -0
  4. package/lib/apply.d.ts +3 -20
  5. package/lib/apply.js +95 -82
  6. package/lib/aws/account-id.d.ts +0 -1
  7. package/lib/aws/account-id.js +0 -1
  8. package/lib/aws/bucket.d.ts +4 -7
  9. package/lib/aws/bucket.js +8 -12
  10. package/lib/aws/function.d.ts +4 -7
  11. package/lib/aws/function.js +9 -30
  12. package/lib/aws/index.d.ts +1 -1
  13. package/lib/aws/index.js +1 -1
  14. package/lib/aws/oidc/github-oidc-provider.d.ts +2 -6
  15. package/lib/aws/oidc/github-oidc-provider.js +10 -15
  16. package/lib/aws/oidc/index.d.ts +0 -1
  17. package/lib/aws/oidc/index.js +0 -1
  18. package/lib/aws/oidc/oidc-provider.d.ts +4 -6
  19. package/lib/aws/oidc/oidc-provider.js +9 -11
  20. package/lib/aws/policy-attachment.d.ts +9 -0
  21. package/lib/aws/policy-attachment.js +20 -0
  22. package/lib/aws/policy.d.ts +4 -16
  23. package/lib/aws/policy.js +9 -40
  24. package/lib/aws/queue.d.ts +4 -34
  25. package/lib/aws/queue.js +10 -20
  26. package/lib/aws/role.d.ts +4 -7
  27. package/lib/aws/role.js +17 -26
  28. package/lib/aws/ses.d.ts +4 -10
  29. package/lib/aws/ses.js +118 -131
  30. package/lib/aws/table.d.ts +4 -7
  31. package/lib/aws/table.js +8 -11
  32. package/lib/cloudflare/api.d.ts +0 -1
  33. package/lib/cloudflare/api.js +0 -1
  34. package/lib/cloudflare/asset-manifest.d.ts +0 -1
  35. package/lib/cloudflare/asset-manifest.js +0 -1
  36. package/lib/cloudflare/auth.d.ts +0 -1
  37. package/lib/cloudflare/auth.js +0 -1
  38. package/lib/cloudflare/bindings.d.ts +1 -1
  39. package/lib/cloudflare/bindings.js +3 -2
  40. package/lib/cloudflare/bound.d.ts +0 -1
  41. package/lib/cloudflare/bound.js +0 -1
  42. package/lib/cloudflare/bucket.d.ts +7 -15
  43. package/lib/cloudflare/bucket.js +12 -19
  44. package/lib/cloudflare/durable-object-namespace.d.ts +0 -3
  45. package/lib/cloudflare/durable-object-namespace.js +0 -4
  46. package/lib/cloudflare/generate-asset-manifest.d.ts +0 -1
  47. package/lib/cloudflare/generate-asset-manifest.js +1 -2
  48. package/lib/cloudflare/index.d.ts +2 -1
  49. package/lib/cloudflare/index.js +2 -1
  50. package/lib/cloudflare/kv-namespace.d.ts +6 -9
  51. package/lib/cloudflare/kv-namespace.js +16 -20
  52. package/lib/cloudflare/state.d.ts +0 -1
  53. package/lib/cloudflare/state.js +0 -1
  54. package/lib/cloudflare/static-site-router.d.ts +0 -1
  55. package/lib/cloudflare/static-site-router.js +0 -4
  56. package/lib/cloudflare/static-site.d.ts +5 -11
  57. package/lib/cloudflare/static-site.js +25 -37
  58. package/lib/cloudflare/types.d.ts +0 -1
  59. package/lib/cloudflare/types.js +0 -1
  60. package/lib/cloudflare/upload-asset-manifest.d.ts +0 -1
  61. package/lib/cloudflare/upload-asset-manifest.js +0 -1
  62. package/lib/cloudflare/worker-metadata.d.ts +0 -1
  63. package/lib/cloudflare/worker-metadata.js +0 -1
  64. package/lib/cloudflare/worker-migration.d.ts +0 -1
  65. package/lib/cloudflare/worker-migration.js +0 -1
  66. package/lib/cloudflare/worker.d.ts +8 -14
  67. package/lib/cloudflare/worker.js +38 -50
  68. package/lib/cloudflare/wrangler.json.d.ts +243 -0
  69. package/lib/cloudflare/wrangler.json.js +35 -0
  70. package/lib/cloudflare/zone-settings.d.ts +301 -0
  71. package/lib/cloudflare/zone-settings.js +1 -0
  72. package/lib/cloudflare/zone.d.ts +196 -0
  73. package/lib/cloudflare/zone.js +175 -0
  74. package/lib/context.d.ts +58 -0
  75. package/lib/context.js +36 -0
  76. package/lib/destroy.d.ts +10 -13
  77. package/lib/destroy.js +104 -43
  78. package/lib/{esbuild.d.ts → esbuild/bundle.d.ts} +4 -18
  79. package/lib/{esbuild.js → esbuild/bundle.js} +10 -24
  80. package/lib/esbuild/index.d.ts +1 -0
  81. package/lib/esbuild/index.js +1 -0
  82. package/lib/fs/file.d.ts +13 -0
  83. package/lib/fs/file.js +20 -0
  84. package/lib/fs/folder.d.ts +10 -0
  85. package/lib/fs/folder.js +16 -0
  86. package/lib/fs/index.d.ts +2 -0
  87. package/lib/fs/index.js +2 -0
  88. package/lib/github/client.d.ts +0 -1
  89. package/lib/github/client.js +0 -1
  90. package/lib/github/index.d.ts +0 -1
  91. package/lib/github/index.js +0 -1
  92. package/lib/github/secret.d.ts +4 -6
  93. package/lib/github/secret.js +10 -12
  94. package/lib/index.d.ts +6 -10
  95. package/lib/index.js +5 -9
  96. package/lib/project/index.d.ts +1 -0
  97. package/lib/project/index.js +1 -0
  98. package/lib/project/vite.d.ts +39 -0
  99. package/lib/project/vite.js +68 -0
  100. package/lib/resource.d.ts +34 -62
  101. package/lib/resource.js +29 -270
  102. package/lib/scope.d.ts +37 -14
  103. package/lib/scope.js +97 -23
  104. package/lib/secret.d.ts +2 -7
  105. package/lib/secret.js +7 -20
  106. package/lib/state.d.ts +14 -10
  107. package/lib/state.js +20 -14
  108. package/lib/stripe/index.d.ts +0 -1
  109. package/lib/stripe/index.js +0 -1
  110. package/lib/stripe/price.d.ts +4 -5
  111. package/lib/stripe/price.js +10 -20
  112. package/lib/stripe/product.d.ts +4 -5
  113. package/lib/stripe/product.js +10 -20
  114. package/lib/stripe/webhook.d.ts +4 -6
  115. package/lib/stripe/webhook.js +12 -25
  116. package/lib/test/bun.d.ts +19 -0
  117. package/lib/test/bun.js +55 -0
  118. package/lib/{utils → util}/content-type.d.ts +0 -1
  119. package/lib/{utils → util}/content-type.js +0 -1
  120. package/lib/{encrypt.d.ts → util/encrypt.d.ts} +0 -1
  121. package/lib/{encrypt.js → util/encrypt.js} +0 -1
  122. package/lib/{error.d.ts → util/ignore.d.ts} +0 -1
  123. package/lib/{error.js → util/ignore.js} +0 -1
  124. package/lib/{utils → util}/retry.d.ts +0 -1
  125. package/lib/{utils → util}/retry.js +0 -1
  126. package/lib/util/rm.d.ts +1 -0
  127. package/lib/util/rm.js +11 -0
  128. package/lib/util/serde.d.ts +3 -0
  129. package/lib/{serde.js → util/serde.js} +19 -10
  130. package/lib/util/slugify.d.ts +1 -0
  131. package/lib/util/slugify.js +3 -0
  132. package/package.json +7 -5
  133. package/src/alchemy.ts +127 -0
  134. package/src/apply.ts +116 -115
  135. package/src/aws/bucket.ts +11 -14
  136. package/src/aws/function.ts +13 -33
  137. package/src/aws/index.ts +1 -0
  138. package/src/aws/oidc/github-oidc-provider.ts +14 -19
  139. package/src/aws/oidc/oidc-provider.ts +19 -12
  140. package/src/aws/policy-attachment.ts +51 -0
  141. package/src/aws/policy.ts +15 -60
  142. package/src/aws/queue.ts +17 -21
  143. package/src/aws/role.ts +25 -28
  144. package/src/aws/ses.ts +150 -164
  145. package/src/aws/table.ts +15 -15
  146. package/src/cloudflare/bindings.ts +8 -0
  147. package/src/cloudflare/bucket.ts +39 -47
  148. package/src/cloudflare/durable-object-namespace.ts +0 -10
  149. package/src/cloudflare/generate-asset-manifest.ts +1 -1
  150. package/src/cloudflare/index.ts +2 -0
  151. package/src/cloudflare/kv-namespace.ts +35 -33
  152. package/src/cloudflare/static-site-router.ts +0 -3
  153. package/src/cloudflare/static-site.ts +55 -71
  154. package/src/cloudflare/worker.ts +106 -112
  155. package/src/cloudflare/wrangler.json.ts +309 -0
  156. package/src/cloudflare/zone-settings.ts +368 -0
  157. package/src/cloudflare/zone.ts +491 -0
  158. package/src/context.ts +122 -0
  159. package/src/destroy.ts +124 -73
  160. package/src/{esbuild.ts → esbuild/bundle.ts} +16 -28
  161. package/src/esbuild/index.ts +1 -0
  162. package/src/fs/file.ts +39 -0
  163. package/src/fs/folder.ts +32 -0
  164. package/src/fs/index.ts +2 -0
  165. package/src/github/secret.ts +19 -16
  166. package/src/index.ts +7 -15
  167. package/src/project/index.ts +1 -0
  168. package/src/project/vite.ts +152 -0
  169. package/src/resource.ts +101 -462
  170. package/src/scope.ts +125 -36
  171. package/src/secret.ts +9 -32
  172. package/src/state.ts +38 -21
  173. package/src/stripe/price.ts +17 -22
  174. package/src/stripe/product.ts +17 -22
  175. package/src/stripe/webhook.ts +21 -27
  176. package/src/test/bun.ts +105 -0
  177. package/src/util/rm.ts +11 -0
  178. package/src/util/serde.ts +53 -0
  179. package/src/util/slugify.ts +3 -0
  180. package/lib/$.d.ts +0 -12
  181. package/lib/$.d.ts.map +0 -1
  182. package/lib/$.js +0 -42
  183. package/lib/$.js.map +0 -1
  184. package/lib/alchemize.d.ts +0 -47
  185. package/lib/alchemize.d.ts.map +0 -1
  186. package/lib/alchemize.js +0 -116
  187. package/lib/alchemize.js.map +0 -1
  188. package/lib/apply.d.ts.map +0 -1
  189. package/lib/apply.js.map +0 -1
  190. package/lib/aws/account-id.d.ts.map +0 -1
  191. package/lib/aws/account-id.js.map +0 -1
  192. package/lib/aws/bucket.d.ts.map +0 -1
  193. package/lib/aws/bucket.js.map +0 -1
  194. package/lib/aws/function.d.ts.map +0 -1
  195. package/lib/aws/function.js.map +0 -1
  196. package/lib/aws/index.d.ts.map +0 -1
  197. package/lib/aws/index.js.map +0 -1
  198. package/lib/aws/oidc/github-oidc-provider.d.ts.map +0 -1
  199. package/lib/aws/oidc/github-oidc-provider.js.map +0 -1
  200. package/lib/aws/oidc/index.d.ts.map +0 -1
  201. package/lib/aws/oidc/index.js.map +0 -1
  202. package/lib/aws/oidc/oidc-provider.d.ts.map +0 -1
  203. package/lib/aws/oidc/oidc-provider.js.map +0 -1
  204. package/lib/aws/policy.d.ts.map +0 -1
  205. package/lib/aws/policy.js.map +0 -1
  206. package/lib/aws/queue.d.ts.map +0 -1
  207. package/lib/aws/queue.js.map +0 -1
  208. package/lib/aws/role.d.ts.map +0 -1
  209. package/lib/aws/role.js.map +0 -1
  210. package/lib/aws/ses.d.ts.map +0 -1
  211. package/lib/aws/ses.js.map +0 -1
  212. package/lib/aws/table.d.ts.map +0 -1
  213. package/lib/aws/table.js.map +0 -1
  214. package/lib/cloudflare/api.d.ts.map +0 -1
  215. package/lib/cloudflare/api.js.map +0 -1
  216. package/lib/cloudflare/asset-manifest.d.ts.map +0 -1
  217. package/lib/cloudflare/asset-manifest.js.map +0 -1
  218. package/lib/cloudflare/auth.d.ts.map +0 -1
  219. package/lib/cloudflare/auth.js.map +0 -1
  220. package/lib/cloudflare/bindings.d.ts.map +0 -1
  221. package/lib/cloudflare/bindings.js.map +0 -1
  222. package/lib/cloudflare/bound.d.ts.map +0 -1
  223. package/lib/cloudflare/bound.js.map +0 -1
  224. package/lib/cloudflare/bucket.d.ts.map +0 -1
  225. package/lib/cloudflare/bucket.js.map +0 -1
  226. package/lib/cloudflare/durable-object-namespace.d.ts.map +0 -1
  227. package/lib/cloudflare/durable-object-namespace.js.map +0 -1
  228. package/lib/cloudflare/generate-asset-manifest.d.ts.map +0 -1
  229. package/lib/cloudflare/generate-asset-manifest.js.map +0 -1
  230. package/lib/cloudflare/index.d.ts.map +0 -1
  231. package/lib/cloudflare/index.js.map +0 -1
  232. package/lib/cloudflare/kv-namespace.d.ts.map +0 -1
  233. package/lib/cloudflare/kv-namespace.js.map +0 -1
  234. package/lib/cloudflare/state.d.ts.map +0 -1
  235. package/lib/cloudflare/state.js.map +0 -1
  236. package/lib/cloudflare/static-site-router.d.ts.map +0 -1
  237. package/lib/cloudflare/static-site-router.js.map +0 -1
  238. package/lib/cloudflare/static-site.d.ts.map +0 -1
  239. package/lib/cloudflare/static-site.js.map +0 -1
  240. package/lib/cloudflare/types.d.ts.map +0 -1
  241. package/lib/cloudflare/types.js.map +0 -1
  242. package/lib/cloudflare/upload-asset-manifest.d.ts.map +0 -1
  243. package/lib/cloudflare/upload-asset-manifest.js.map +0 -1
  244. package/lib/cloudflare/worker-metadata.d.ts.map +0 -1
  245. package/lib/cloudflare/worker-metadata.js.map +0 -1
  246. package/lib/cloudflare/worker-migration.d.ts.map +0 -1
  247. package/lib/cloudflare/worker-migration.js.map +0 -1
  248. package/lib/cloudflare/worker.d.ts.map +0 -1
  249. package/lib/cloudflare/worker.js.map +0 -1
  250. package/lib/destroy.d.ts.map +0 -1
  251. package/lib/destroy.js.map +0 -1
  252. package/lib/encrypt.d.ts.map +0 -1
  253. package/lib/encrypt.js.map +0 -1
  254. package/lib/error.d.ts.map +0 -1
  255. package/lib/error.js.map +0 -1
  256. package/lib/esbuild.d.ts.map +0 -1
  257. package/lib/esbuild.js.map +0 -1
  258. package/lib/fs.d.ts +0 -12
  259. package/lib/fs.d.ts.map +0 -1
  260. package/lib/fs.js +0 -41
  261. package/lib/fs.js.map +0 -1
  262. package/lib/github/client.d.ts.map +0 -1
  263. package/lib/github/client.js.map +0 -1
  264. package/lib/github/index.d.ts.map +0 -1
  265. package/lib/github/index.js.map +0 -1
  266. package/lib/github/secret.d.ts.map +0 -1
  267. package/lib/github/secret.js.map +0 -1
  268. package/lib/global.d.ts +0 -16
  269. package/lib/global.d.ts.map +0 -1
  270. package/lib/global.js +0 -42
  271. package/lib/global.js.map +0 -1
  272. package/lib/index.d.ts.map +0 -1
  273. package/lib/index.js.map +0 -1
  274. package/lib/input.d.ts +0 -7
  275. package/lib/input.d.ts.map +0 -1
  276. package/lib/input.js +0 -2
  277. package/lib/input.js.map +0 -1
  278. package/lib/main.d.ts +0 -3
  279. package/lib/main.d.ts.map +0 -1
  280. package/lib/main.js +0 -32
  281. package/lib/main.js.map +0 -1
  282. package/lib/output.d.ts +0 -25
  283. package/lib/output.d.ts.map +0 -1
  284. package/lib/output.js +0 -23
  285. package/lib/output.js.map +0 -1
  286. package/lib/print.d.ts +0 -7
  287. package/lib/print.d.ts.map +0 -1
  288. package/lib/print.js +0 -10
  289. package/lib/print.js.map +0 -1
  290. package/lib/resource.d.ts.map +0 -1
  291. package/lib/resource.js.map +0 -1
  292. package/lib/scope.d.ts.map +0 -1
  293. package/lib/scope.js.map +0 -1
  294. package/lib/secret.d.ts.map +0 -1
  295. package/lib/secret.js.map +0 -1
  296. package/lib/serde.d.ts +0 -3
  297. package/lib/serde.d.ts.map +0 -1
  298. package/lib/serde.js.map +0 -1
  299. package/lib/slug.d.ts +0 -2
  300. package/lib/slug.d.ts.map +0 -1
  301. package/lib/slug.js +0 -4
  302. package/lib/slug.js.map +0 -1
  303. package/lib/state.d.ts.map +0 -1
  304. package/lib/state.js.map +0 -1
  305. package/lib/stripe/index.d.ts.map +0 -1
  306. package/lib/stripe/index.js.map +0 -1
  307. package/lib/stripe/price.d.ts.map +0 -1
  308. package/lib/stripe/price.js.map +0 -1
  309. package/lib/stripe/product.d.ts.map +0 -1
  310. package/lib/stripe/product.js.map +0 -1
  311. package/lib/stripe/webhook.d.ts.map +0 -1
  312. package/lib/stripe/webhook.js.map +0 -1
  313. package/lib/utils/content-type.d.ts.map +0 -1
  314. package/lib/utils/content-type.js.map +0 -1
  315. package/lib/utils/retry.d.ts.map +0 -1
  316. package/lib/utils/retry.js.map +0 -1
  317. package/src/$.ts +0 -69
  318. package/src/alchemize.ts +0 -195
  319. package/src/fs.ts +0 -47
  320. package/src/global.ts +0 -63
  321. package/src/input.ts +0 -19
  322. package/src/main.ts +0 -34
  323. package/src/output.ts +0 -75
  324. package/src/print.ts +0 -18
  325. package/src/serde.ts +0 -46
  326. package/src/slug.ts +0 -3
  327. /package/src/{utils → util}/content-type.ts +0 -0
  328. /package/src/{encrypt.ts → util/encrypt.ts} +0 -0
  329. /package/src/{error.ts → util/ignore.ts} +0 -0
  330. /package/src/{utils → util}/retry.ts +0 -0
package/src/destroy.ts CHANGED
@@ -1,92 +1,143 @@
1
- import { defaultStage, defaultStateStore } from "./global";
2
- import type { Output } from "./output";
3
- import { Provider, ResourceID, isResource } from "./resource";
4
- import { type Scope, rootScope } from "./scope";
5
- import type { State, StateStore } from "./state";
1
+ import { alchemy } from "./alchemy";
2
+ import { context } from "./context";
3
+ import { PROVIDERS, type Provider, type Resource } from "./resource";
4
+ import { Scope } from "./scope";
5
+
6
+ export class DestroyedSignal extends Error {}
6
7
 
7
8
  export interface DestroyOptions {
8
- stage?: string;
9
- stateStore?: StateStore;
10
- scope?: Scope;
11
9
  quiet?: boolean;
10
+ strategy?: "sequential" | "parallel";
12
11
  }
13
12
 
14
- /**
15
- * Prune all resources from an Output and "down", i.e. that branches from it.
16
- */
17
- export async function destroy<T>(
18
- output: Output<T>,
19
- options?: DestroyOptions,
20
- ): Promise<void>;
13
+ function isScopeArgs(a: any): a is [scope: Scope, options?: DestroyOptions] {
14
+ return a[0] instanceof Scope;
15
+ }
21
16
 
22
17
  /**
23
- * @internal
18
+ * Prune all resources from an Output and "down", i.e. that branches from it.
24
19
  */
25
- export async function destroy<T>(
26
- stage: string,
27
- scope: Scope,
28
- resourceID: ResourceID,
29
- resourceState: State,
30
- resourceProvider: Provider,
31
- stateStore: StateStore,
32
- options: DestroyOptions,
33
- ): Promise<void>;
34
-
35
- export async function destroy<T>(
20
+ export async function destroy<Type extends string>(
36
21
  ...args:
37
- | [Output<T>, DestroyOptions?]
38
- | [string, Scope, ResourceID, State, Provider, StateStore, DestroyOptions]
22
+ | [scope: Scope, options?: DestroyOptions]
23
+ | [resource: Resource<Type> | undefined | null, options?: DestroyOptions]
39
24
  ): Promise<void> {
40
- let resourceID: ResourceID;
41
- let resourceState: State;
42
- let resourceProvider: Provider;
43
- let stage: string;
44
- let stateStore: StateStore;
45
- let scope: Scope | undefined = undefined;
46
- let options: DestroyOptions | undefined = undefined;
47
- if (args.length === 7) {
48
- stage = args[0];
49
- scope = args[1];
50
- resourceID = args[2];
51
- resourceState = args[3];
52
- resourceProvider = args[4];
53
- stateStore = args[5];
54
- options = args[6];
55
- } else if (isResource(args[0])) {
56
- const resource = args[0];
57
- // stage = args[1]?.stage ?? defaultStage;
58
- resourceID = resource[ResourceID];
59
- stage = args[1]?.stage ?? defaultStage;
60
- scope = args[1]?.scope ?? rootScope;
61
- const statePath = scope.getScopePath(stage);
62
- stateStore = args[1]?.stateStore ?? new defaultStateStore(statePath);
63
- // First destroy all dependencies
64
- const _resourceState = await stateStore.get(resourceID);
65
- if (_resourceState === undefined) {
66
- // we have no record of this resource, we must assume it's already deleted
67
- return;
68
- }
69
- resourceState = _resourceState;
70
- resourceProvider = resource[Provider];
71
- options = args[1];
72
- } else {
73
- console.log(args[0]);
74
- throw new Error("Not implemented: must handle destroy a Output chain");
25
+ if (isScopeArgs(args)) {
26
+ const [scope, options] = args;
27
+ const strategy = options?.strategy ?? "sequential";
28
+ // destroy all active resources
29
+ await destroy.all(Array.from(scope.resources.values()), {
30
+ strategy,
31
+ });
32
+ // then detect orphans and destroy them
33
+ const orphans = await scope.state.all();
34
+ await destroy.all(
35
+ Object.values(orphans).map((orphan) => ({
36
+ ...orphan.output,
37
+ Scope: scope,
38
+ })),
39
+ options,
40
+ );
41
+ // finally, destroy the scope container
42
+ await scope.deinit();
43
+ return;
44
+ }
45
+
46
+ const [instance, options] = args;
47
+
48
+ if (!instance) {
49
+ return;
50
+ }
51
+
52
+ const Provider: Provider<Type> | undefined = PROVIDERS.get(instance.Kind);
53
+ if (!Provider) {
54
+ throw new Error(
55
+ `Cannot destroy resource type ${instance.Kind} - no provider found. You may need to import the provider in your alchemy.config.ts.`,
56
+ );
75
57
  }
58
+
59
+ const scope = instance.Scope;
60
+ if (!scope) {
61
+ console.warn(`Resource "${instance.FQN}" has no scope`);
62
+ }
63
+ const quiet = options?.quiet ?? scope.quiet;
64
+
76
65
  try {
77
- await resourceProvider.delete(
78
- stage,
66
+ if (!quiet) {
67
+ console.log(`Delete: "${instance.FQN}"`);
68
+ }
69
+
70
+ const state = (await scope.state.get(instance.ID))!;
71
+
72
+ if (state === undefined) {
73
+ console.warn(`Resource "${instance.FQN}" not found`);
74
+ return;
75
+ }
76
+
77
+ const ctx = context({
79
78
  scope,
80
- resourceID,
81
- resourceState,
82
- resourceState.inputs as [],
83
- options ?? {
84
- quiet: false,
79
+ phase: "delete",
80
+ kind: instance.Kind,
81
+ id: instance.ID,
82
+ fqn: instance.FQN,
83
+ seq: instance.Seq,
84
+ state,
85
+ replace: () => {
86
+ throw new Error("Cannot replace a resource that is being deleted");
85
87
  },
86
- );
88
+ });
89
+
90
+ let nestedScope: Scope | undefined;
91
+ try {
92
+ // BUG: this does not restore persisted scope
93
+ await alchemy.run(instance.ID, async (scope) => {
94
+ nestedScope = scope;
95
+ return await Provider.handler.bind(ctx)(instance.ID, state.props);
96
+ });
97
+ } catch (err) {
98
+ if (err instanceof DestroyedSignal) {
99
+ // TODO: should we fail if the DestroyedSignal is not thrown?
100
+ } else {
101
+ throw err;
102
+ }
103
+ }
104
+
105
+ if (nestedScope) {
106
+ await destroy(nestedScope, options);
107
+ }
108
+
109
+ await scope.delete(instance.ID);
110
+
111
+ if (!quiet) {
112
+ console.log(`Deleted: "${instance.FQN}"`);
113
+ }
87
114
  } catch (error) {
88
115
  console.error(error);
89
116
  throw error;
90
117
  }
91
- await stateStore.delete(resourceID);
118
+ }
119
+
120
+ export namespace destroy {
121
+ export async function all(resources: Resource[], options?: DestroyOptions) {
122
+ if (options?.strategy !== "parallel") {
123
+ const sorted = resources.sort((a, b) => b.Seq - a.Seq);
124
+ for (const resource of sorted) {
125
+ await destroy(resource);
126
+ }
127
+ } else {
128
+ await Promise.all(
129
+ resources.map((resource) => destroy(resource, options)),
130
+ );
131
+ }
132
+ }
133
+
134
+ export async function sequentially(
135
+ ...resources: (Resource<string> | undefined | null)[]
136
+ ) {
137
+ for (const resource of resources) {
138
+ if (resource) {
139
+ await destroy(resource);
140
+ }
141
+ }
142
+ }
92
143
  }
@@ -2,8 +2,8 @@ import * as esbuild from "esbuild";
2
2
  import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
- import { Resource } from "./resource";
6
-
5
+ import type { Context } from "../context";
6
+ import { Resource } from "../resource";
7
7
  export interface BundleProps {
8
8
  /**
9
9
  * Entry point for the bundle
@@ -47,7 +47,7 @@ export interface BundleProps {
47
47
  options?: Partial<esbuild.BuildOptions>;
48
48
  }
49
49
 
50
- export interface BundleOutput {
50
+ export interface Bundle extends Resource<"esbuild::Bundle"> {
51
51
  /**
52
52
  * Path to the bundled file
53
53
  */
@@ -58,23 +58,27 @@ export interface BundleOutput {
58
58
  hash: string;
59
59
  }
60
60
 
61
- export class Bundle extends Resource(
61
+ export const Bundle = Resource(
62
62
  "esbuild::Bundle",
63
63
  {
64
64
  alwaysUpdate: true,
65
65
  },
66
- async (ctx, props: BundleProps): Promise<BundleOutput> => {
66
+ async function (
67
+ this: Context<Bundle>,
68
+ id: string,
69
+ props: BundleProps,
70
+ ): Promise<Bundle> {
67
71
  // Determine output path
68
72
  const outputPath = getOutputPath(props);
69
73
 
70
74
  // Ensure output directory exists
71
75
  await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
72
76
 
73
- if (ctx.event === "delete") {
77
+ if (this.phase === "delete") {
74
78
  await fs.promises.unlink(outputPath).catch(() => {});
75
79
  // Also clean up sourcemap if it exists
76
80
  await fs.promises.unlink(outputPath + ".map").catch(() => {});
77
- return { path: outputPath, hash: "" };
81
+ return this.destroy();
78
82
  }
79
83
 
80
84
  const result = await bundle(props);
@@ -83,32 +87,16 @@ export class Bundle extends Resource(
83
87
  const hash = crypto.createHash("sha256").update(contents).digest("hex");
84
88
 
85
89
  // Store metadata in context
86
- await ctx.set("metafile", result.metafile);
87
- await ctx.set("hash", hash);
90
+ await this.set("metafile", result.metafile);
91
+ await this.set("hash", hash);
88
92
 
89
93
  // Return output info
90
- return {
94
+ return this({
91
95
  path: outputPath,
92
96
  hash,
93
- };
97
+ });
94
98
  },
95
- ) {
96
- /**
97
- * Get the metafile from the last build
98
- */
99
- public async getMetafile(ctx: {
100
- get<T>(key: string): Promise<T | undefined>;
101
- }) {
102
- return ctx.get<esbuild.Metafile>("metafile");
103
- }
104
-
105
- /**
106
- * Get the hash from the last build
107
- */
108
- public async getHash(ctx: { get<T>(key: string): Promise<T | undefined> }) {
109
- return ctx.get<string>("hash");
110
- }
111
- }
99
+ );
112
100
 
113
101
  export async function bundle(props: BundleProps) {
114
102
  const outputPath = getOutputPath(props);
@@ -0,0 +1 @@
1
+ export * from "./bundle";
package/src/fs/file.ts ADDED
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { Context } from "../context";
4
+ import { Resource } from "../resource";
5
+ import { ignore } from "../util/ignore";
6
+
7
+ export interface File extends Resource<"fs::File"> {
8
+ path: string;
9
+ content: string;
10
+ }
11
+
12
+ export const File = Resource(
13
+ "fs::File",
14
+ async function (
15
+ this: Context<File>,
16
+ id: string,
17
+ {
18
+ path: filePath,
19
+ content,
20
+ }: {
21
+ path: string;
22
+ content: string;
23
+ },
24
+ ): Promise<File> {
25
+ if (this.phase === "delete") {
26
+ await ignore("ENOENT", async () => fs.promises.unlink(filePath));
27
+ return this.destroy();
28
+ } else {
29
+ await fs.promises.mkdir(path.dirname(filePath), {
30
+ recursive: true,
31
+ });
32
+ await fs.promises.writeFile(filePath, content);
33
+ }
34
+ return this({
35
+ path: filePath,
36
+ content,
37
+ });
38
+ },
39
+ );
@@ -0,0 +1,32 @@
1
+ import fs from "node:fs";
2
+ import type { Context } from "../context";
3
+ import { Resource } from "../resource";
4
+ import { ignore } from "../util/ignore";
5
+
6
+ export interface Folder extends Resource<"fs::Folder"> {
7
+ path: string;
8
+ }
9
+
10
+ export const Folder = Resource(
11
+ "fs::Folder",
12
+ async function (
13
+ this: Context<Folder>,
14
+ id: string,
15
+ { path: dirPath }: { path: string },
16
+ ): Promise<Folder> {
17
+ if (this.phase === "delete") {
18
+ // we just do a best effort attempt
19
+ await ignore(["ENOENT", "ENOTEMPTY"], async () =>
20
+ fs.promises.rmdir(dirPath),
21
+ );
22
+ return this.destroy();
23
+ } else {
24
+ await ignore("EEXIST", async () =>
25
+ fs.promises.mkdir(dirPath, { recursive: true }),
26
+ );
27
+ }
28
+ return this({
29
+ path: dirPath,
30
+ });
31
+ },
32
+ );
@@ -0,0 +1,2 @@
1
+ export * from "./file";
2
+ export * from "./folder";
@@ -1,5 +1,6 @@
1
1
  import sodium from "libsodium-wrappers";
2
- import { type Context, Resource } from "../resource";
2
+ import type { Context } from "../context";
3
+ import { Resource } from "../resource";
3
4
  import type { Secret } from "../secret";
4
5
  import { createGitHubClient, verifyGitHubAuth } from "./client";
5
6
  /**
@@ -35,7 +36,9 @@ export interface GitHubSecretProps {
35
36
  /**
36
37
  * Output returned after Secret creation/update
37
38
  */
38
- export interface GitHubSecretOutput extends Omit<GitHubSecretProps, "value"> {
39
+ export interface GitHubSecretOutput
40
+ extends Resource<"github::Secret">,
41
+ Omit<GitHubSecretProps, "value"> {
39
42
  /**
40
43
  * The ID of the resource
41
44
  */
@@ -50,22 +53,24 @@ export interface GitHubSecretOutput extends Omit<GitHubSecretProps, "value"> {
50
53
  /**
51
54
  * Resource for managing GitHub repository secrets
52
55
  */
53
- export class GitHubSecret extends Resource(
56
+ export const GitHubSecret = Resource(
54
57
  "github::Secret",
55
- async (
56
- ctx: Context<GitHubSecretOutput>,
58
+ async function (
59
+ this: Context<GitHubSecretOutput>,
60
+ id: string,
57
61
  props: GitHubSecretProps,
58
- ): Promise<GitHubSecretOutput | void> => {
62
+ ): Promise<GitHubSecretOutput> {
59
63
  // Create authenticated Octokit client - will automatically handle token resolution
64
+ /// TODO: use fetch
60
65
  const octokit = await createGitHubClient({ token: props.token });
61
66
 
62
67
  // Verify authentication and permissions
63
- if (!ctx.quiet) {
68
+ if (!this.quiet) {
64
69
  await verifyGitHubAuth(octokit, props.owner, props.repository);
65
70
  }
66
71
 
67
- if (ctx.event === "delete") {
68
- if (ctx.output?.id) {
72
+ if (this.phase === "delete") {
73
+ if (this.output?.id) {
69
74
  try {
70
75
  // Delete the secret
71
76
  await octokit.rest.actions.deleteRepoSecret({
@@ -84,7 +89,7 @@ export class GitHubSecret extends Resource(
84
89
  }
85
90
 
86
91
  // Return void (a deleted resource has no content)
87
- return undefined;
92
+ return this.destroy();
88
93
  } else {
89
94
  try {
90
95
  // Get the repository's public key for encrypting secrets
@@ -102,7 +107,7 @@ export class GitHubSecret extends Resource(
102
107
  );
103
108
 
104
109
  // Create or update the secret with the encrypted value and key_id
105
- const response = await octokit.rest.actions.createOrUpdateRepoSecret({
110
+ await octokit.rest.actions.createOrUpdateRepoSecret({
106
111
  owner: props.owner,
107
112
  repo: props.repository,
108
113
  secret_name: props.name,
@@ -111,16 +116,14 @@ export class GitHubSecret extends Resource(
111
116
  });
112
117
 
113
118
  // GitHub doesn't return the secret details on create/update, so we need to construct it
114
- const output: GitHubSecretOutput = {
119
+ return this({
115
120
  id: `${props.owner}/${props.repository}/${props.name}`,
116
121
  owner: props.owner,
117
122
  repository: props.repository,
118
123
  name: props.name,
119
124
  token: props.token,
120
125
  updatedAt: new Date().toISOString(),
121
- };
122
-
123
- return output;
126
+ });
124
127
  } catch (error: any) {
125
128
  if (
126
129
  error.status === 403 &&
@@ -142,7 +145,7 @@ export class GitHubSecret extends Resource(
142
145
  }
143
146
  }
144
147
  },
145
- ) {}
148
+ );
146
149
 
147
150
  /**
148
151
  * Encrypt a value for GitHub using libsodium
package/src/index.ts CHANGED
@@ -1,16 +1,8 @@
1
- export * from "./$";
2
- export * from "./alchemize";
3
- export * from "./error";
4
- export * from "./global";
5
- export * from "./input";
6
- export * from "./output";
7
- export * from "./print";
8
- export {
9
- Resource,
10
- type Context,
11
- type CreateContext,
12
- type DeleteContext,
13
- type UpdateContext,
14
- } from "./resource";
1
+ export type * from "./context";
2
+ export * from "./resource";
15
3
  export * from "./secret";
16
- export type { FileSystemStateStore as StateStore } from "./state";
4
+ export * from "./state";
5
+ export * from "./util/ignore";
6
+
7
+ import { alchemy } from "./alchemy";
8
+ export default alchemy;
@@ -0,0 +1 @@
1
+ export * from "./vite";
@@ -0,0 +1,152 @@
1
+ import { exec } from "child_process";
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import { promisify } from "util";
5
+ import type { Context } from "../context";
6
+ import { Resource } from "../resource";
7
+ import { rm } from "../util/rm";
8
+ const execAsync = promisify(exec);
9
+
10
+ type ViteTemplate =
11
+ | "vanilla"
12
+ | "vanilla-ts"
13
+ | "vue"
14
+ | "vue-ts"
15
+ | "react"
16
+ | "react-ts"
17
+ | "react-swc"
18
+ | "react-swc-ts"
19
+ | "preact"
20
+ | "preact-ts"
21
+ | "lit"
22
+ | "lit-ts"
23
+ | "svelte"
24
+ | "svelte-ts"
25
+ | "solid"
26
+ | "solid-ts"
27
+ | "qwik"
28
+ | "qwik-ts";
29
+
30
+ export interface ViteProjectProps {
31
+ /**
32
+ * The name/path of the project
33
+ */
34
+ name: string;
35
+ /**
36
+ * The Vite template to use
37
+ */
38
+ template: ViteTemplate;
39
+ /**
40
+ * The extends to add to the tsconfig.json file
41
+ */
42
+ extends?: string;
43
+ /**
44
+ * The references to add to the tsconfig.json file
45
+ */
46
+ references?: string[];
47
+ /**
48
+ * @default false
49
+ */
50
+ tailwind?: boolean;
51
+ /**
52
+ * Force overwrite the project config tfiles during the update phase
53
+ *
54
+ * @default false
55
+ */
56
+ overwrite?: boolean;
57
+ }
58
+
59
+ export interface ViteProject extends ViteProjectProps, Resource {
60
+ /**
61
+ * The name/path of the project
62
+ */
63
+ name: string;
64
+ }
65
+
66
+ export const ViteProject = Resource(
67
+ "project::ViteProject",
68
+ async function (
69
+ this: Context<ViteProject>,
70
+ id: string,
71
+ props: ViteProjectProps,
72
+ ): Promise<ViteProject> {
73
+ if (this.phase === "delete") {
74
+ try {
75
+ if (await fs.exists(props.name)) {
76
+ // TODO: OS agnostic - fs.rm is slow to delete node_modules/
77
+ await execAsync("rm -rf " + props.name);
78
+ }
79
+ } catch (error) {
80
+ console.error(`Error deleting project ${id}:`, error);
81
+ }
82
+ return this.destroy();
83
+ }
84
+
85
+ if (this.phase === "update") {
86
+ if (props.overwrite) {
87
+ await modifyConfig(props);
88
+ } else {
89
+ console.warn(
90
+ "ViteProject does not support updates - the project must be recreated to change the template",
91
+ );
92
+ }
93
+ } else {
94
+ // Create phase
95
+ await execAsync(`bun create vite ${id} --template ${props.template}`);
96
+
97
+ await modifyConfig(props);
98
+ }
99
+
100
+ return this(props);
101
+ },
102
+ );
103
+
104
+ async function modifyConfig(props: ViteProjectProps) {
105
+ if (props.tailwind) {
106
+ await execAsync(`bun add tailwindcss @tailwindcss/vite`);
107
+
108
+ await fs.writeFile(
109
+ path.join(props.name, "vite.config.ts"),
110
+ `import tailwindcss from "@tailwindcss/vite";
111
+ import react from "@vitejs/plugin-react";
112
+ import { defineConfig } from "vite";
113
+
114
+ // https://vite.dev/config/
115
+ export default defineConfig({
116
+ plugins: [react(), tailwindcss()],
117
+ });`,
118
+ );
119
+
120
+ // Add Tailwind CSS import to index.css
121
+ const indexCssPath = path.join(props.name, "src", "index.css");
122
+ const currentCss = await fs.readFile(indexCssPath, "utf-8");
123
+ if (!currentCss.includes('@import "tailwindcss";')) {
124
+ await fs.writeFile(
125
+ indexCssPath,
126
+ '@import "tailwindcss";\n\n' + currentCss,
127
+ );
128
+ }
129
+ }
130
+
131
+ await Promise.all([
132
+ rm(path.join(props.name, "tsconfig.app.json")),
133
+ rm(path.join(props.name, "tsconfig.node.json")),
134
+ fs.writeFile(
135
+ path.join(props.name, "tsconfig.json"),
136
+ JSON.stringify(
137
+ {
138
+ extends: props.extends,
139
+ compilerOptions: {
140
+ types: ["@cloudflare/workers-types"],
141
+ allowImportingTsExtensions: true,
142
+ jsx: "react-jsx",
143
+ },
144
+ include: ["vite/*.ts", "src/**/*.ts", "src/**/*.tsx", "src/env.d.ts"],
145
+ references: props.references?.map((path) => ({ path })),
146
+ },
147
+ null,
148
+ 2,
149
+ ),
150
+ ),
151
+ ]);
152
+ }