alchemy 2.0.0-beta.25 → 2.0.0-beta.27

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 (240) hide show
  1. package/bin/alchemy.d.ts +2 -0
  2. package/bin/alchemy.d.ts.map +1 -0
  3. package/bin/alchemy.js +3 -57288
  4. package/bin/alchemy.js.map +1 -1
  5. package/bin/alchemy.ts +3 -59
  6. package/bin/exec.d.ts +2 -0
  7. package/bin/exec.d.ts.map +1 -0
  8. package/bin/exec.js +2360 -0
  9. package/bin/exec.js.map +1 -0
  10. package/bin/exec.ts +3 -21
  11. package/lib/AWS/AuthProvider.d.ts +1 -1
  12. package/lib/AWS/AuthProvider.d.ts.map +1 -1
  13. package/lib/AWS/Lambda/BucketEventSource.d.ts +1 -1
  14. package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
  15. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts +1 -1
  16. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts.map +1 -1
  17. package/lib/AWS/Lambda/QueueEventSource.d.ts +1 -1
  18. package/lib/AWS/Lambda/QueueEventSource.d.ts.map +1 -1
  19. package/lib/AWS/Lambda/StreamEventSource.d.ts +1 -1
  20. package/lib/AWS/Lambda/StreamEventSource.d.ts.map +1 -1
  21. package/lib/AWS/Lambda/TableEventSource.d.ts +1 -1
  22. package/lib/AWS/Lambda/TableEventSource.d.ts.map +1 -1
  23. package/lib/AWS/Lambda/TopicEventSource.d.ts +1 -1
  24. package/lib/AWS/Lambda/TopicEventSource.d.ts.map +1 -1
  25. package/lib/Auth/AuthProvider.d.ts.map +1 -1
  26. package/lib/Auth/AuthProvider.js +20 -3
  27. package/lib/Auth/AuthProvider.js.map +1 -1
  28. package/lib/Auth/Lock.d.ts +12 -0
  29. package/lib/Auth/Lock.d.ts.map +1 -0
  30. package/lib/Auth/Lock.js +27 -0
  31. package/lib/Auth/Lock.js.map +1 -0
  32. package/lib/Axiom/AuthProvider.d.ts +1 -1
  33. package/lib/Axiom/AuthProvider.d.ts.map +1 -1
  34. package/lib/Axiom/Credentials.d.ts +1 -1
  35. package/lib/Axiom/Credentials.d.ts.map +1 -1
  36. package/lib/Bundle/Bundle.d.ts +3 -3
  37. package/lib/Cli/commands/_shared.d.ts +51 -0
  38. package/lib/Cli/commands/_shared.d.ts.map +1 -0
  39. package/lib/Cli/commands/_shared.js +176 -0
  40. package/lib/Cli/commands/_shared.js.map +1 -0
  41. package/lib/Cli/commands/bootstrap.d.ts +6 -0
  42. package/lib/Cli/commands/bootstrap.d.ts.map +1 -0
  43. package/lib/Cli/commands/bootstrap.js +106 -0
  44. package/lib/Cli/commands/bootstrap.js.map +1 -0
  45. package/lib/Cli/commands/deploy.d.ts +56 -0
  46. package/lib/Cli/commands/deploy.d.ts.map +1 -0
  47. package/lib/Cli/commands/deploy.js +128 -0
  48. package/lib/Cli/commands/deploy.js.map +1 -0
  49. package/lib/Cli/commands/dev.d.ts +10 -0
  50. package/lib/Cli/commands/dev.d.ts.map +1 -0
  51. package/lib/Cli/commands/dev.js +45 -0
  52. package/lib/Cli/commands/dev.js.map +1 -0
  53. package/lib/Cli/commands/login.d.ts +10 -0
  54. package/lib/Cli/commands/login.d.ts.map +1 -0
  55. package/lib/Cli/commands/login.js +59 -0
  56. package/lib/Cli/commands/login.js.map +1 -0
  57. package/lib/Cli/commands/logs.d.ts +12 -0
  58. package/lib/Cli/commands/logs.d.ts.map +1 -0
  59. package/lib/Cli/commands/logs.js +99 -0
  60. package/lib/Cli/commands/logs.js.map +1 -0
  61. package/lib/Cli/commands/profile.d.ts +3 -0
  62. package/lib/Cli/commands/profile.d.ts.map +1 -0
  63. package/lib/Cli/commands/profile.js +76 -0
  64. package/lib/Cli/commands/profile.js.map +1 -0
  65. package/lib/Cli/commands/state.d.ts +3 -0
  66. package/lib/Cli/commands/state.d.ts.map +1 -0
  67. package/lib/Cli/commands/state.js +221 -0
  68. package/lib/Cli/commands/state.js.map +1 -0
  69. package/lib/Cli/commands/tail.d.ts +9 -0
  70. package/lib/Cli/commands/tail.d.ts.map +1 -0
  71. package/lib/Cli/commands/tail.js +93 -0
  72. package/lib/Cli/commands/tail.js.map +1 -0
  73. package/lib/Cli/exec.d.ts +3 -0
  74. package/lib/Cli/exec.d.ts.map +1 -0
  75. package/lib/Cli/exec.js +12 -0
  76. package/lib/Cli/exec.js.map +1 -0
  77. package/lib/Cli/index.d.ts +2 -1
  78. package/lib/Cli/index.d.ts.map +1 -1
  79. package/lib/Cli/index.js +2 -5
  80. package/lib/Cli/index.js.map +1 -1
  81. package/lib/Cli/main.d.ts +3 -0
  82. package/lib/Cli/main.d.ts.map +1 -0
  83. package/lib/Cli/main.js +40 -0
  84. package/lib/Cli/main.js.map +1 -0
  85. package/lib/Cli/{InkCLI.d.ts → tui/InkCLI.d.ts} +1 -1
  86. package/lib/Cli/tui/InkCLI.d.ts.map +1 -0
  87. package/lib/Cli/{InkCLI.js → tui/InkCLI.js} +1 -1
  88. package/lib/Cli/tui/InkCLI.js.map +1 -0
  89. package/lib/Cli/{components → tui/components}/ApprovePlan.d.ts +1 -1
  90. package/lib/Cli/tui/components/ApprovePlan.d.ts.map +1 -0
  91. package/lib/Cli/tui/components/ApprovePlan.js.map +1 -0
  92. package/lib/Cli/{components → tui/components}/Plan.d.ts +1 -1
  93. package/lib/Cli/tui/components/Plan.d.ts.map +1 -0
  94. package/lib/Cli/{components → tui/components}/Plan.js +1 -1
  95. package/lib/Cli/tui/components/Plan.js.map +1 -0
  96. package/lib/Cli/{components → tui/components}/PlanProgress.d.ts +3 -3
  97. package/lib/Cli/tui/components/PlanProgress.d.ts.map +1 -0
  98. package/lib/Cli/{components → tui/components}/PlanProgress.js +1 -1
  99. package/lib/Cli/tui/components/PlanProgress.js.map +1 -0
  100. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts +1 -1
  101. package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -1
  102. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts +1 -1
  103. package/lib/Cloudflare/ApiToken/UserApiToken.d.ts.map +1 -1
  104. package/lib/Cloudflare/Auth/AuthProvider.d.ts +1 -1
  105. package/lib/Cloudflare/Auth/AuthProvider.d.ts.map +1 -1
  106. package/lib/Cloudflare/Auth/AuthProvider.js +0 -4
  107. package/lib/Cloudflare/Auth/AuthProvider.js.map +1 -1
  108. package/lib/Cloudflare/CloudflareEnvironment.d.ts +1 -1
  109. package/lib/Cloudflare/CloudflareEnvironment.d.ts.map +1 -1
  110. package/lib/Cloudflare/Container/ContainerApplication.d.ts +1 -1
  111. package/lib/Cloudflare/Container/ContainerApplication.d.ts.map +1 -1
  112. package/lib/Cloudflare/Credentials.d.ts +1 -1
  113. package/lib/Cloudflare/Credentials.d.ts.map +1 -1
  114. package/lib/Cloudflare/D1/D1Clone.d.ts +1 -1
  115. package/lib/Cloudflare/D1/D1Clone.d.ts.map +1 -1
  116. package/lib/Cloudflare/D1/D1Import.d.ts +1 -1
  117. package/lib/Cloudflare/D1/D1Import.d.ts.map +1 -1
  118. package/lib/Cloudflare/D1/D1Migrations.d.ts +1 -1
  119. package/lib/Cloudflare/D1/D1Migrations.d.ts.map +1 -1
  120. package/lib/Cloudflare/KV/KVNamespace.d.ts +1 -1
  121. package/lib/Cloudflare/KV/KVNamespace.d.ts.map +1 -1
  122. package/lib/Cloudflare/Logs.d.ts +1 -1
  123. package/lib/Cloudflare/Logs.d.ts.map +1 -1
  124. package/lib/Cloudflare/Queue/Queue.d.ts +1 -1
  125. package/lib/Cloudflare/Queue/Queue.d.ts.map +1 -1
  126. package/lib/Cloudflare/Queue/QueueConsumer.d.ts +1 -1
  127. package/lib/Cloudflare/Queue/QueueConsumer.d.ts.map +1 -1
  128. package/lib/Cloudflare/SecretsStore/Secret.d.ts +1 -1
  129. package/lib/Cloudflare/SecretsStore/Secret.d.ts.map +1 -1
  130. package/lib/Cloudflare/SecretsStore/SecretsStore.d.ts +1 -1
  131. package/lib/Cloudflare/SecretsStore/SecretsStore.d.ts.map +1 -1
  132. package/lib/Cloudflare/SecretsStore/SecretsStore.js +5 -5
  133. package/lib/Cloudflare/SecretsStore/SecretsStore.js.map +1 -1
  134. package/lib/Cloudflare/StateStore/Api.d.ts +13 -0
  135. package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
  136. package/lib/Cloudflare/StateStore/Api.js +15 -2
  137. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  138. package/lib/Cloudflare/StateStore/State.d.ts +3 -3
  139. package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
  140. package/lib/Cloudflare/StateStore/State.js.map +1 -1
  141. package/lib/Cloudflare/Workers/Assets.d.ts +1 -1
  142. package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
  143. package/lib/Cloudflare/Workers/Worker.d.ts +2 -2
  144. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  145. package/lib/Cloudflare/Workers/Worker.js +14 -12
  146. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  147. package/lib/Cloudflare/Workers/Workflow.d.ts +1 -1
  148. package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
  149. package/lib/Deploy.d.ts +1 -1
  150. package/lib/Deploy.d.ts.map +1 -1
  151. package/lib/Destroy.d.ts +1 -1
  152. package/lib/Destroy.d.ts.map +1 -1
  153. package/lib/GitHub/AuthProvider.d.ts +1 -1
  154. package/lib/GitHub/AuthProvider.d.ts.map +1 -1
  155. package/lib/GitHub/Credentials.d.ts +1 -1
  156. package/lib/GitHub/Credentials.d.ts.map +1 -1
  157. package/lib/GitHub/Providers.d.ts +1 -1
  158. package/lib/GitHub/Providers.d.ts.map +1 -1
  159. package/lib/Output.d.ts.map +1 -1
  160. package/lib/Output.js +13 -2
  161. package/lib/Output.js.map +1 -1
  162. package/lib/Provider.d.ts +1 -1
  163. package/lib/Provider.d.ts.map +1 -1
  164. package/lib/Stack.js +2 -2
  165. package/lib/Stack.js.map +1 -1
  166. package/lib/Telemetry/Layer.d.ts.map +1 -1
  167. package/lib/Telemetry/Layer.js +13 -1
  168. package/lib/Telemetry/Layer.js.map +1 -1
  169. package/lib/Test/Bun.d.ts +59 -28
  170. package/lib/Test/Bun.d.ts.map +1 -1
  171. package/lib/Test/Bun.js +77 -81
  172. package/lib/Test/Bun.js.map +1 -1
  173. package/lib/Test/Core.d.ts +73 -0
  174. package/lib/Test/Core.d.ts.map +1 -0
  175. package/lib/Test/Core.js +98 -0
  176. package/lib/Test/Core.js.map +1 -0
  177. package/lib/Test/Vitest.d.ts +53 -72
  178. package/lib/Test/Vitest.d.ts.map +1 -1
  179. package/lib/Test/Vitest.js +73 -232
  180. package/lib/Test/Vitest.js.map +1 -1
  181. package/lib/Test/index.d.ts +0 -1
  182. package/lib/Test/index.d.ts.map +1 -1
  183. package/lib/Test/index.js +0 -1
  184. package/lib/Test/index.js.map +1 -1
  185. package/lib/Util/index.d.ts +1 -0
  186. package/lib/Util/index.d.ts.map +1 -1
  187. package/lib/Util/index.js +1 -0
  188. package/lib/Util/index.js.map +1 -1
  189. package/lib/index.d.ts +7 -0
  190. package/lib/index.d.ts.map +1 -1
  191. package/lib/index.js +6 -0
  192. package/lib/index.js.map +1 -1
  193. package/lib/tsconfig.test.tsbuildinfo +1 -0
  194. package/package.json +68 -52
  195. package/src/Auth/AuthProvider.ts +22 -4
  196. package/src/Auth/Lock.ts +35 -0
  197. package/{bin → src/Cli}/commands/_shared.ts +5 -5
  198. package/{bin → src/Cli}/commands/bootstrap.ts +18 -14
  199. package/{bin → src/Cli}/commands/deploy.ts +16 -16
  200. package/{bin → src/Cli}/commands/dev.ts +13 -3
  201. package/{bin → src/Cli}/commands/login.ts +9 -9
  202. package/{bin → src/Cli}/commands/logs.ts +11 -11
  203. package/{bin → src/Cli}/commands/profile.ts +8 -8
  204. package/{bin → src/Cli}/commands/state.ts +47 -26
  205. package/{bin → src/Cli}/commands/tail.ts +11 -11
  206. package/src/Cli/exec.ts +23 -0
  207. package/src/Cli/index.ts +2 -5
  208. package/src/Cli/main.ts +60 -0
  209. package/src/Cli/{InkCLI.tsx → tui/InkCLI.tsx} +3 -3
  210. package/src/Cli/{components → tui/components}/ApprovePlan.tsx +1 -1
  211. package/src/Cli/{components → tui/components}/Plan.tsx +6 -2
  212. package/src/Cli/{components → tui/components}/PlanProgress.tsx +7 -3
  213. package/src/Cloudflare/Auth/AuthProvider.ts +0 -5
  214. package/src/Cloudflare/SecretsStore/SecretsStore.ts +5 -5
  215. package/src/Cloudflare/StateStore/Api.ts +15 -2
  216. package/src/Cloudflare/StateStore/State.ts +3 -9
  217. package/src/Cloudflare/Workers/Worker.ts +29 -44
  218. package/src/Output.ts +10 -2
  219. package/src/Provider.ts +1 -1
  220. package/src/Stack.ts +2 -2
  221. package/src/Telemetry/Layer.ts +13 -1
  222. package/src/Test/Bun.ts +164 -140
  223. package/src/Test/Core.ts +225 -0
  224. package/src/Test/Vitest.ts +157 -509
  225. package/src/Test/index.ts +0 -1
  226. package/src/Util/index.ts +1 -0
  227. package/src/index.ts +14 -0
  228. package/lib/Cli/InkCLI.d.ts.map +0 -1
  229. package/lib/Cli/InkCLI.js.map +0 -1
  230. package/lib/Cli/components/ApprovePlan.d.ts.map +0 -1
  231. package/lib/Cli/components/ApprovePlan.js.map +0 -1
  232. package/lib/Cli/components/Plan.d.ts.map +0 -1
  233. package/lib/Cli/components/Plan.js.map +0 -1
  234. package/lib/Cli/components/PlanProgress.d.ts.map +0 -1
  235. package/lib/Cli/components/PlanProgress.js.map +0 -1
  236. package/lib/cli/index.d.ts +0 -413
  237. package/lib/cli/index.d.ts.map +0 -1
  238. package/lib/cli/index.js +0 -10
  239. package/lib/cli/index.js.map +0 -1
  240. /package/lib/Cli/{components → tui/components}/ApprovePlan.js +0 -0
@@ -0,0 +1,225 @@
1
+ import { ConfigProvider } from "effect/ConfigProvider";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Logger from "effect/Logger";
5
+ import * as Option from "effect/Option";
6
+ import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
7
+
8
+ import type { AlchemyContext } from "../AlchemyContext.ts";
9
+ import { AlchemyContextLive } from "../AlchemyContext.ts";
10
+ import { apply } from "../Apply.ts";
11
+ import { provideFreshArtifactStore } from "../Artifacts.ts";
12
+ import { AuthProviders } from "../Auth/AuthProvider.ts";
13
+ import { withProfileOverride } from "../Auth/Profile.ts";
14
+ import { LoggingCli } from "../Cli/LoggingCli.ts";
15
+ import { deploy as _deploy } from "../Deploy.ts";
16
+ import { destroy as _destroy } from "../Destroy.ts";
17
+ import type { Input } from "../Input.ts";
18
+ import * as Plan from "../Plan.ts";
19
+ import {
20
+ type CompiledStack,
21
+ make as makeStack,
22
+ Stack,
23
+ type StackEffect,
24
+ type StackServices,
25
+ } from "../Stack.ts";
26
+ import { Stage } from "../Stage.ts";
27
+ import * as State from "../State/index.ts";
28
+ import { TelemetryLive } from "../Telemetry/Layer.ts";
29
+ import { loadConfigProvider } from "../Util/ConfigProvider.ts";
30
+ import { PlatformServices } from "../Util/PlatformServices.ts";
31
+
32
+ /**
33
+ * Configuration shared by every test in a file. Pass to `Test.make(...)`.
34
+ */
35
+ export interface MakeOptions<ROut = any> {
36
+ /** Provider layer for the stack — e.g. `AWS.providers()`, `Cloudflare.providers()`. */
37
+ providers: Layer.Layer<ROut, never, StackServices>;
38
+ /** State store for top-level `deploy(Stack)` / `destroy(Stack)`; defaults to {@link State.localState}. */
39
+ state?: Layer.Layer<State.State, never, StackServices>;
40
+ /** Override `ALCHEMY_PROFILE`; otherwise resolved from env / .env. */
41
+ profile?: string;
42
+ /** Default stage for deploy/destroy (default `"test"`). */
43
+ stage?: string;
44
+ }
45
+
46
+ export type TestEffect<A, Req = never> = StackEffect<A, any, Req>;
47
+
48
+ const platformLayer = Layer.mergeAll(PlatformServices, FetchHttpClient.layer);
49
+
50
+ const alchemyLayer = Layer.mergeAll(
51
+ LoggingCli,
52
+ Logger.layer([Logger.consolePretty()], { mergeWithExisting: true }),
53
+ AlchemyContextLive,
54
+ );
55
+
56
+ /**
57
+ * Build the per-test runtime and return a self-contained Effect.
58
+ *
59
+ * Mirrors {@link "../bin/alchemy.ts"} composition: ConfigProvider via
60
+ * `loadConfigProvider` + `withProfileOverride`, an empty `AuthProviders`
61
+ * registry that the user's `providers` layer populates, the platform layers,
62
+ * and the configured state store. Adapters wrap this into runner-specific
63
+ * thunks (`bun.test` -> `runPromise`, `it.live` -> as-is).
64
+ */
65
+ export const toEffect = <A>(
66
+ effect: TestEffect<A>,
67
+ options: MakeOptions,
68
+ ): Effect.Effect<A, any, never> =>
69
+ Effect.gen(function* () {
70
+ const base = yield* loadConfigProvider(Option.none());
71
+ const configProvider = withProfileOverride(base, options.profile);
72
+ return yield* effect.pipe(
73
+ provideFreshArtifactStore,
74
+ Effect.provide(Layer.succeed(ConfigProvider, configProvider)),
75
+ );
76
+ }).pipe(
77
+ Effect.provideService(AuthProviders, {}),
78
+ Effect.provide(options.state ?? State.localState()),
79
+ Effect.provide(Layer.provideMerge(alchemyLayer, platformLayer)),
80
+ Effect.scoped,
81
+ ) as Effect.Effect<A, any, never>;
82
+
83
+ /** Promise wrapper around {@link toEffect} for `bun.test`-style runners. */
84
+ export const run = <A>(
85
+ effect: TestEffect<A>,
86
+ options: MakeOptions,
87
+ ): Promise<A> => Effect.runPromise(toEffect(effect, options));
88
+
89
+ /**
90
+ * Wrap an effect so it runs with `options.providers` + a placeholder Stack +
91
+ * Stage in scope. Used by `test.provider` so user code can call provider SDK
92
+ * APIs (e.g. `DynamoDB.describeTable`) directly inside the test body.
93
+ */
94
+ export const withProviders = <A, E, R, ROut>(
95
+ effect: Effect.Effect<A, E, R>,
96
+ options: MakeOptions<ROut>,
97
+ stackName: string,
98
+ ): Effect.Effect<A, E, Exclude<R, ROut | Stack | Stage>> =>
99
+ effect.pipe(
100
+ Effect.provide(options.providers as Layer.Layer<any, never, any>),
101
+ Effect.provide(
102
+ Layer.succeed(Stack, {
103
+ name: stackName,
104
+ stage: options.stage ?? "test",
105
+ resources: {},
106
+ bindings: {},
107
+ }),
108
+ ),
109
+ Effect.provide(Layer.succeed(Stage, options.stage ?? "test")),
110
+ ) as Effect.Effect<A, E, Exclude<R, ROut | Stack | Stage>>;
111
+
112
+ /**
113
+ * Curried `deploy` for the test factory: bakes in the configured stage and
114
+ * adds the telemetry layer the CLI uses, so `beforeAll(deploy(Stack))` works
115
+ * the same way as `alchemy deploy`.
116
+ */
117
+ export const deploy = <A>(
118
+ options: MakeOptions,
119
+ stack: TestEffect<CompiledStack<A>, Stage | AlchemyContext>,
120
+ callOptions?: { stage?: string },
121
+ ) =>
122
+ _deploy({
123
+ stack: stack as Effect.Effect<CompiledStack<A>, never, any>,
124
+ stage: callOptions?.stage ?? options.stage ?? "test",
125
+ }).pipe(Effect.provide(TelemetryLive));
126
+
127
+ export const destroy = (
128
+ options: MakeOptions,
129
+ stack: TestEffect<CompiledStack, Stage | AlchemyContext>,
130
+ callOptions?: { stage?: string },
131
+ ) =>
132
+ _destroy({
133
+ stack: stack as Effect.Effect<CompiledStack, never, any>,
134
+ stage: callOptions?.stage ?? options.stage ?? "test",
135
+ }).pipe(Effect.provide(TelemetryLive));
136
+
137
+ /**
138
+ * In-test scratch stack handed to `test.provider(name, (stack) => ...)`.
139
+ *
140
+ * Each scratch stack owns a private in-memory state store that is shared
141
+ * between successive `deploy`/`destroy` calls AND visible to the user's test
142
+ * body (so `yield* State` / `state.get(...)` see the same store the deploys
143
+ * mutated). This makes create / update / replace / delete paths exercisable
144
+ * without polluting `.alchemy/` or other tests in the same file.
145
+ */
146
+ export interface ScratchStack<ROut = any> {
147
+ readonly name: string;
148
+ /** The shared in-memory state Layer for this scratch. @internal */
149
+ readonly state: Layer.Layer<State.State, never, never>;
150
+ deploy<A, E, R>(
151
+ effect: Effect.Effect<A, E, R>,
152
+ ): Effect.Effect<Input.Resolve<A>, any, Exclude<R, ROut | StackServices>>;
153
+ destroy(): Effect.Effect<void, any, never>;
154
+ }
155
+
156
+ const sanitizeStackName = (name: string) =>
157
+ name.replaceAll(/[^a-zA-Z0-9_]/g, "-").replace(/-+/g, "-");
158
+
159
+ /**
160
+ * Build a fresh `ScratchStack` for `test.provider`. Allocates a private
161
+ * in-memory state store so the test is isolated from `.alchemy/` and from
162
+ * other tests in the same file.
163
+ */
164
+ export const scratchStack = <ROut>(
165
+ options: MakeOptions<ROut>,
166
+ name: string,
167
+ ): ScratchStack<ROut> => {
168
+ const stage = options.stage ?? "test";
169
+ const stackName = sanitizeStackName(name);
170
+ const inMemory: Record<
171
+ string,
172
+ Record<string, Record<string, State.ResourceState>>
173
+ > = {};
174
+ const stateLayer = Layer.succeed(
175
+ State.State,
176
+ State.InMemoryService(inMemory),
177
+ );
178
+
179
+ const buildAndApply = (effect: Effect.Effect<any, any, any>) =>
180
+ (effect as Effect.Effect<any, any, never>).pipe(
181
+ makeStack({
182
+ name: stackName,
183
+ providers: options.providers,
184
+ state: stateLayer,
185
+ } as any) as any,
186
+ Effect.flatMap((compiled: any) =>
187
+ Plan.make(compiled).pipe(
188
+ Effect.flatMap(apply),
189
+ Effect.provide(compiled.services),
190
+ ),
191
+ ),
192
+ Effect.provide(Layer.succeed(Stage, stage)),
193
+ provideFreshArtifactStore,
194
+ );
195
+
196
+ return {
197
+ name: stackName,
198
+ state: stateLayer,
199
+ deploy: ((effect: Effect.Effect<any, any, any>) =>
200
+ buildAndApply(effect)) as ScratchStack<ROut>["deploy"],
201
+ destroy: () =>
202
+ Plan.make({
203
+ name: stackName,
204
+ stage,
205
+ resources: {},
206
+ bindings: {},
207
+ output: {},
208
+ }).pipe(
209
+ Effect.flatMap(apply),
210
+ Effect.asVoid,
211
+ Effect.provide(stateLayer),
212
+ Effect.provide(options.providers as Layer.Layer<any, never, any>),
213
+ Effect.provide(
214
+ Layer.succeed(Stack, {
215
+ name: stackName,
216
+ stage,
217
+ resources: {},
218
+ bindings: {},
219
+ }),
220
+ ),
221
+ Effect.provide(Layer.succeed(Stage, stage)),
222
+ provideFreshArtifactStore,
223
+ ) as Effect.Effect<void, any, never>,
224
+ };
225
+ };