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
@@ -1,543 +1,191 @@
1
- import type * as aws from "@distilled.cloud/aws";
2
- import * as cf from "@distilled.cloud/cloudflare";
3
- // import { NodeServices } from "@effect/platform-node";
4
- import { expect, it } from "@effect/vitest";
5
- import { Logger } from "effect";
6
- import * as Config from "effect/Config";
7
- import * as ConfigProvider from "effect/ConfigProvider";
1
+ import { it } from "@effect/vitest";
8
2
  import * as Effect from "effect/Effect";
9
- import * as FileSystem from "effect/FileSystem";
10
- import * as Layer from "effect/Layer";
11
- import * as Path from "effect/Path";
12
- import { MinimumLogLevel } from "effect/References";
13
- import * as Scope from "effect/Scope";
14
- import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
15
- import * as HttpClient from "effect/unstable/http/HttpClient";
16
- import * as NodePath from "node:path";
17
3
  import {
18
4
  afterAll as vitestAfterAll,
5
+ afterEach as vitestAfterEach,
19
6
  beforeAll as vitestBeforeAll,
7
+ beforeEach as vitestBeforeEach,
20
8
  } from "vitest";
21
- import * as AWS from "../AWS/index.ts";
22
- import * as Cloudflare from "../Cloudflare/index.ts";
23
9
 
24
- import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
25
- import { AlchemyContext, AlchemyContextLive } from "../AlchemyContext.ts";
26
- import { apply } from "../Apply.ts";
27
- import { Artifacts, provideFreshArtifactStore } from "../Artifacts.ts";
28
- import { AuthProviders } from "../Auth/AuthProvider.ts";
29
- import * as AWSCredentials from "../AWS/Credentials.ts";
30
- import * as AWSEnvironment from "../AWS/Environment.ts";
31
- import * as AWSRegion from "../AWS/Region.ts";
32
- import type { Command } from "../Build/Command.ts";
33
- import type { Cli } from "../Cli/index.ts";
34
- import { LoggingCli } from "../Cli/LoggingCli.ts";
35
- import {
36
- buildNamespaceTree,
37
- flattenTree,
38
- type DerivedAction,
39
- } from "../Cli/NamespaceTree.ts";
40
- import { ExecutionContext } from "../ExecutionContext.ts";
41
- import type { Input } from "../Input.ts";
42
- import type { Output } from "../Output.ts";
43
- import * as Plan from "../Plan.ts";
44
- import type { Provider } from "../Provider.ts";
45
- import * as Server from "../Server/index.ts";
46
- import * as Serverless from "../Serverless/index.ts";
47
- import * as Stack from "../Stack.ts";
48
- import * as Stage from "../Stage.ts";
49
- import * as State from "../State/index.ts";
50
- import { PlatformServices } from "../Util/PlatformServices.ts";
51
-
52
- export const expectEmptyObject = () =>
53
- expect.toSatisfy(
54
- (deletions) => Object.keys(deletions).length === 0,
55
- "empty object",
56
- );
57
-
58
- export const expectPropExpr = (identifier: string, src: any) =>
59
- expect.objectContaining({
60
- kind: "PropExpr",
61
- identifier,
62
- expr: expect.objectContaining({
63
- kind: "ResourceExpr",
64
- src,
65
- }),
66
- });
67
-
68
- type Provided =
69
- | Scope.Scope
70
- | Stack.Stack
71
- | State.State
72
- | Stage.Stage
73
- | AlchemyContext
74
- | HttpClient.HttpClient
75
- | FileSystem.FileSystem
76
- | Path.Path
77
- | aws.Credentials.Credentials
78
- | aws.Region.Region
79
- | Cli
80
- | ExecutionContext
81
- | Server.ProcessContext
82
- | Server.ServerHost
83
- | Serverless.FunctionContext
84
- | AWSEnvironment.AWSEnvironment
85
- | Artifacts
86
- | Provider<Command>
87
- | AWS.Providers
88
- | Cloudflare.Providers
89
- | ChildProcessSpawner
90
- | AuthProviders;
91
-
92
- const quietLogger = Logger.make(() => {
93
- // console.log(options.message);
94
- });
95
-
96
- const platform = Layer.mergeAll(
97
- PlatformServices,
98
- FetchHttpClient.layer,
99
- Logger.layer([process.env.VERBOSE ? Logger.consolePretty() : quietLogger]),
100
- );
101
-
102
- const awsEnvironment = Layer.unwrap(
103
- Effect.gen(function* () {
104
- const _profileName = yield* Config.string("AWS_PROFILE").pipe(
105
- Config.withDefault("default"),
106
- );
107
- const LOCAL = yield* Config.boolean("LOCAL").pipe(
108
- Config.withDefault(false),
109
- );
110
- const LOCALSTACK_ENDPOINT = yield* Config.string(
111
- "LOCALSTACK_ENDPOINT",
112
- ).pipe(Config.withDefault("http://localhost.localstack.cloud:4566"));
113
-
114
- if (LOCAL) {
115
- return AWSEnvironment.makeEnvironment({
116
- accountId: "000000000000",
117
- region: "us-east-1",
118
- credentials: {
119
- accessKeyId: "test",
120
- secretAccessKey: "test",
121
- sessionToken: "test",
122
- },
123
- endpoint: LOCALSTACK_ENDPOINT,
124
- });
125
- }
126
- return Layer.effect(
127
- AWSEnvironment.AWSEnvironment,
128
- AWSEnvironment.loadDefault(),
129
- ).pipe(Layer.orDie);
130
- }),
131
- );
132
-
133
- const awsProviders = Layer.provideMerge(
134
- AWS.providers(),
135
- Layer.provideMerge(
136
- Layer.mergeAll(AWSRegion.fromEnvironment, AWSCredentials.fromEnvironment),
137
- awsEnvironment,
138
- ),
139
- );
140
-
141
- const cfProviders = Layer.provideMerge(
142
- Cloudflare.providers(),
143
- Layer.mergeAll(cf.CredentialsFromEnv, FetchHttpClient.layer),
144
- );
145
-
146
- const deriveStackName = (testPath: string, suffix: string) => {
147
- const testDir = testPath.includes("/test/")
148
- ? (testPath.split("/test/").pop() ?? "")
149
- : NodePath.basename(testPath);
150
- const testPathWithoutExt = testDir.replace(/\.[^.]+$/, "");
151
- return `${testPathWithoutExt}-${suffix}`
152
- .replaceAll(/[^a-zA-Z0-9_]/g, "-")
153
- .replace(/-+/g, "-");
154
- };
155
-
156
- const runWithContext = <A, Err>(
157
- stackName: string,
158
- effect: Effect.Effect<A, Err, Provided>,
159
- options: {
160
- state?: Layer.Layer<State.State, never, Stack.Stack>;
161
- providers?: boolean;
162
- } = {},
163
- ): Effect.Effect<
164
- A,
165
- aws.Credentials.CredentialsError | Config.ConfigError | Err,
166
- never
167
- > => {
168
- const stack = Layer.effect(
169
- Stack.Stack,
170
- Effect.succeed({
171
- name: stackName,
172
- stage: "test",
173
- resources: {},
174
- bindings: {},
175
- }),
176
- );
177
-
178
- const alchemy = Layer.provideMerge(
179
- Layer.mergeAll(options.state ?? State.localState(), LoggingCli),
180
- Layer.mergeAll(stack, AlchemyContextLive),
181
- );
182
-
183
- const context = {
184
- Type: "Test",
185
- id: "Test",
186
- env: {},
187
- exports: {},
188
- listen: () => Effect.void,
189
- serve: () => Effect.void,
190
- get: <T>(_key: string) => Effect.succeed<T>(undefined as T),
191
- set: (_id: string, _output: Output) =>
192
- Effect.sync(() => _id.replaceAll(/[^a-zA-Z0-9]/g, "_")),
193
- };
194
-
195
- // Test harness does not fully close `Req`; runtime provides enough for tests.
196
- // @ts-expect-error Residual requirement channel on `effect` after ConfigProvider.
197
- return Effect.gen(function* () {
198
- const configProvider = ConfigProvider.orElse(
199
- yield* ConfigProvider.fromDotEnv({ path: ".env" }),
200
- ConfigProvider.fromEnv(),
201
- );
202
- return yield* effect.pipe(
203
- Effect.provideService(ConfigProvider.ConfigProvider, configProvider),
204
- );
205
- }).pipe(
206
- Effect.provide(
207
- Layer.provideMerge(
208
- options.providers === false
209
- ? Layer.empty
210
- : Layer.mergeAll(awsProviders, cfProviders),
211
- Layer.provideMerge(alchemy, platform),
212
- ),
213
- ),
214
- Effect.provideService(Stage.Stage, "test"),
215
- Effect.provideService(AuthProviders, {}),
216
- Effect.provideService(ExecutionContext, context as any),
217
- Effect.provideService(Server.ServerHost, {
218
- run: (_effect) => Effect.void,
219
- }),
220
- Effect.provideService(
221
- MinimumLogLevel,
222
- process.env.DEBUG ? "Debug" : "Info",
223
- ),
224
- Effect.provide(PlatformServices),
225
- );
226
- };
227
-
228
- export function test(
229
- name: string,
230
- options: {
231
- timeout?: number;
232
- state?: Layer.Layer<State.State, never, Stack.Stack>;
233
- providers?: boolean;
234
- },
235
- testCase: Effect.Effect<void, any, Provided>,
236
- ): void;
237
-
238
- export function test(
239
- name: string,
240
- testCase: Effect.Effect<void, any, Provided>,
241
- ): void;
242
-
243
- export function test(
244
- name: string,
245
- ...args:
246
- | [
247
- {
248
- timeout?: number;
249
- state?: Layer.Layer<State.State, never, Stack.Stack>;
250
- providers?: boolean;
251
- },
252
- Effect.Effect<void, any, Provided>,
253
- ]
254
- | [Effect.Effect<void, any, Provided>]
255
- ) {
256
- const [options = {}, testCase] =
257
- args.length === 1 ? [undefined, args[0]] : args;
258
-
259
- const testPath = expect.getState().testPath ?? "";
260
- const stackName = deriveStackName(testPath, name);
261
- const effect = runWithContext(stackName, testCase, options);
262
-
263
- return it.live(name, () => effect, options.timeout);
10
+ import type { AlchemyContext } from "../AlchemyContext.ts";
11
+ import type { CompiledStack } from "../Stack.ts";
12
+ import type { Stage } from "../Stage.ts";
13
+ import * as Core from "./Core.ts";
14
+
15
+ export type MakeOptions<ROut = any> = Core.MakeOptions<ROut>;
16
+ export type ScratchStack = Core.ScratchStack;
17
+ export type TestEffect<A, R = never> = Core.TestEffect<A, R>;
18
+
19
+ type TestOptions = number | { timeout?: number };
20
+
21
+ const timeoutOf = (opts: TestOptions | undefined): number | undefined =>
22
+ typeof opts === "number" ? opts : opts?.timeout;
23
+
24
+ interface TestFn {
25
+ (name: string, eff: TestEffect<void>, options?: TestOptions): void;
26
+ skip: (name: string, eff: TestEffect<void>, options?: TestOptions) => void;
27
+ skipIf: (
28
+ condition: boolean,
29
+ ) => (name: string, eff: TestEffect<void>, options?: TestOptions) => void;
30
+ only: (name: string, eff: TestEffect<void>, options?: TestOptions) => void;
31
+ todo: (name: string, eff: TestEffect<void>, options?: TestOptions) => void;
32
+ provider: ProviderFn;
264
33
  }
265
34
 
266
- export namespace test {
267
- export function skip(
35
+ interface ProviderFn {
36
+ (
268
37
  name: string,
269
- options: {
270
- timeout?: number;
271
- state?: Layer.Layer<State.State, never, Stack.Stack>;
272
- providers?: boolean;
273
- },
274
- testCase: Effect.Effect<void, any, Provided>,
38
+ fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
39
+ options?: TestOptions,
275
40
  ): void;
276
-
277
- export function skip(
41
+ skip: (
278
42
  name: string,
279
- testCase: Effect.Effect<void, any, Provided>,
280
- ): void;
281
-
282
- export function skip(
43
+ fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
44
+ options?: TestOptions,
45
+ ) => void;
46
+ skipIf: (
47
+ condition: boolean,
48
+ ) => (
283
49
  name: string,
284
- ...args:
285
- | [
286
- {
287
- timeout?: number;
288
- state?: Layer.Layer<State.State, never, Stack.Stack>;
289
- providers?: boolean;
290
- },
291
- Effect.Effect<void, any, Provided>,
292
- ]
293
- | [Effect.Effect<void, any, Provided>]
294
- ) {
295
- const [options = {}, _testCase] =
296
- args.length === 1 ? [undefined, args[0]] : args;
297
- it.skip(name, () => {}, options.timeout);
298
- }
299
-
300
- export function skipIf(condition: boolean) {
301
- return function (
302
- name: string,
303
- ...args:
304
- | [
305
- {
306
- timeout?: number;
307
- state?: Layer.Layer<State.State, never, Stack.Stack>;
308
- providers?: boolean;
309
- },
310
- Effect.Effect<void, any, Provided>,
311
- ]
312
- | [Effect.Effect<void, any, Provided>]
313
- ) {
314
- if (condition) {
315
- const [options = {}, _testCase] =
316
- args.length === 1 ? [undefined, args[0]] : args;
317
- it.skip(name, () => {}, options.timeout);
318
- } else {
319
- test(name, ...(args as [Effect.Effect<void, any, Provided>]));
320
- }
321
- };
322
- }
323
-
324
- export const state = (resources: Record<string, State.ResourceState> = {}) =>
325
- Layer.effect(
326
- State.State,
327
- Effect.gen(function* () {
328
- const stack = yield* Stack.Stack;
329
- return State.InMemoryService({
330
- [stack.name]: {
331
- [stack.stage]: resources,
332
- },
333
- });
334
- }),
335
- );
336
-
337
- export const defaultState = (
338
- resources: Record<string, State.ResourceState> = {},
339
- other?: {
340
- [stack: string]: {
341
- [stage: string]: {
342
- [resourceId: string]: State.ResourceState;
343
- };
344
- };
345
- },
346
- ) =>
347
- Layer.succeed(
348
- State.State,
349
- State.InMemoryService({
350
- ["test-app"]: {
351
- ["test-stage"]: resources,
352
- },
353
- ...other,
354
- }),
355
- );
356
-
357
- export const deploy = <A, Err = never, Req = any>(
358
- effect: Effect.Effect<A, Err, Req>,
359
- ): Effect.Effect<Input.Resolve<A>, Err, Req | Stack.Stack> =>
360
- Stack.Stack.use((stack) =>
361
- effect.pipe(
362
- // @ts-expect-error - test harness reuses ambient runtime context
363
- Stack.make({
364
- name: stack.name,
365
- providers: Layer.effectContext(Effect.context<never>()),
366
- state: State.localState(),
367
- stack: {
368
- ...stack,
369
- resources: {},
370
- bindings: {},
371
- output: {},
372
- },
373
- }),
374
- Effect.flatMap(Plan.make),
375
- Effect.tap((plan) => Effect.logInfo(formatPlan(plan))),
376
- Effect.flatMap(apply),
377
- provideFreshArtifactStore,
378
- ),
379
- );
50
+ fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
51
+ options?: TestOptions,
52
+ ) => void;
380
53
  }
381
54
 
382
- type AnyAction = Plan.CRUD["action"] | Plan.BindingAction | DerivedAction;
55
+ interface BeforeAllFn {
56
+ <A>(eff: TestEffect<A>, options?: TestOptions): Effect.Effect<A>;
57
+ }
383
58
 
384
- const formatPlan = (plan: Plan.Plan) => {
385
- const items = [
386
- ...Object.values(plan.resources),
387
- ...Object.values(plan.deletions),
388
- ] as Plan.CRUD[];
59
+ interface BeforeEachFn {
60
+ (eff: TestEffect<void>, options?: TestOptions): void;
61
+ }
389
62
 
390
- if (items.length === 0) {
391
- return "Plan: no changes planned";
392
- }
63
+ interface AfterAllFn {
64
+ (eff: TestEffect<any>, options?: TestOptions): void;
65
+ skipIf: (
66
+ predicate: boolean,
67
+ ) => (eff: TestEffect<any>, options?: TestOptions) => void;
68
+ }
393
69
 
394
- const counts = items.reduce(
395
- (acc, item) => {
396
- acc[item.action] += 1;
397
- return acc;
398
- },
399
- {
400
- create: 0,
401
- update: 0,
402
- delete: 0,
403
- noop: 0,
404
- replace: 0,
405
- },
406
- );
70
+ interface AfterEachFn {
71
+ (eff: TestEffect<void>, options?: TestOptions): void;
72
+ }
407
73
 
408
- const summary = (["create", "update", "delete", "replace"] as const)
409
- .filter((action) => counts[action] > 0)
410
- .map((action) => `${counts[action]} to ${action}`)
411
- .join(" | ");
74
+ export interface TestApi {
75
+ test: TestFn;
76
+ beforeAll: BeforeAllFn;
77
+ beforeEach: BeforeEachFn;
78
+ afterAll: AfterAllFn;
79
+ afterEach: AfterEachFn;
80
+ deploy: <A>(
81
+ stack: TestEffect<CompiledStack<A>, Stage | AlchemyContext>,
82
+ options?: { stage?: string },
83
+ ) => ReturnType<typeof Core.deploy<A>>;
84
+ destroy: (
85
+ stack: TestEffect<CompiledStack, Stage | AlchemyContext>,
86
+ options?: { stage?: string },
87
+ ) => ReturnType<typeof Core.destroy>;
88
+ }
412
89
 
413
- const flatItems = flattenTree(buildNamespaceTree(items));
414
- const lines = [`Plan: ${summary}`];
90
+ const DEFAULT_TIMEOUT = 120_000;
415
91
 
416
- for (const item of flatItems) {
417
- const indent = " ".repeat(item.depth);
418
- const icon = getActionIcon(item.action);
92
+ /**
93
+ * Build the per-file test API. See {@link "./Bun.ts"} for the same shape
94
+ * over `bun:test`. Vitest variant uses `@effect/vitest`'s `it.live` so
95
+ * Effect-aware tests stay first-class.
96
+ */
97
+ export const make = <ROut = any>(options: MakeOptions<ROut>): TestApi => {
98
+ const wrap = <A>(eff: TestEffect<A>) => Core.toEffect(eff, options);
99
+ const runEff = <A>(eff: TestEffect<A>) => Core.run(eff, options);
419
100
 
420
- if (item.type === "namespace") {
421
- lines.push(`${indent}${icon} ${item.id}`);
422
- continue;
423
- }
101
+ const test = ((name, eff, opts) => {
102
+ it.live(name, () => wrap(eff), timeoutOf(opts));
103
+ }) as TestFn;
424
104
 
425
- if (item.type === "binding") {
426
- lines.push(`${indent}${icon} ${item.bindingSid}`);
427
- continue;
105
+ test.skip = (name, _eff, opts) => {
106
+ it.skip(name, () => {}, timeoutOf(opts));
107
+ };
108
+ test.skipIf = (condition) => (name, eff, opts) => {
109
+ if (condition) {
110
+ it.skip(name, () => {}, timeoutOf(opts));
111
+ } else {
112
+ it.live(name, () => wrap(eff), timeoutOf(opts));
428
113
  }
114
+ };
115
+ test.only = (name, eff, opts) => {
116
+ it.only(name, () => wrap(eff) as Effect.Effect<any>, timeoutOf(opts));
117
+ };
118
+ test.todo = (name, _eff, _opts) => {
119
+ it.todo(name);
120
+ };
429
121
 
430
- const bindingSuffix =
431
- item.bindingCount && item.bindingCount > 0
432
- ? ` (${item.bindingCount} bindings)`
433
- : "";
434
- lines.push(
435
- `${indent}${icon} ${item.id} (${item.resourceType})${bindingSuffix}`,
122
+ const wrapProvider = (
123
+ name: string,
124
+ fn: (stack: ScratchStack) => Effect.Effect<void, any, any>,
125
+ ) => {
126
+ const scratch = Core.scratchStack(options, name);
127
+ return Core.toEffect(
128
+ Core.withProviders(fn(scratch), options, scratch.name),
129
+ { ...options, state: scratch.state },
436
130
  );
437
- }
438
-
439
- return lines.join("\n");
440
- };
441
-
442
- const getActionIcon = (action: AnyAction): string =>
443
- ({
444
- create: "+",
445
- update: "~",
446
- delete: "-",
447
- noop: "•",
448
- replace: "!",
449
- mixed: "*",
450
- })[action] ?? "?";
451
-
452
- export function skip(
453
- name: string,
454
- options: {
455
- timeout?: number;
456
- state?: Layer.Layer<State.State, never, Stack.Stack>;
457
- providers?: boolean;
458
- },
459
- testCase: Effect.Effect<void, any, Provided>,
460
- ): void;
461
-
462
- export function skip(
463
- name: string,
464
- testCase: Effect.Effect<void, any, Provided>,
465
- ): void;
466
-
467
- export function skip(
468
- name: string,
469
- ...args:
470
- | [
471
- {
472
- timeout?: number;
473
- state?: Layer.Layer<State.State, never, Stack.Stack>;
474
- providers?: boolean;
475
- },
476
- Effect.Effect<void, any, Provided>,
477
- ]
478
- | [Effect.Effect<void, any, Provided>]
479
- ) {
480
- const [options = {}, _testCase] =
481
- args.length === 1 ? [undefined, args[0]] : args;
482
- it.skip(name, () => {}, options.timeout);
483
- }
131
+ };
484
132
 
485
- export function skipIf(condition: boolean) {
486
- return function (
487
- name: string,
488
- ...args:
489
- | [
490
- {
491
- timeout?: number;
492
- state?: Layer.Layer<State.State, never, Stack.Stack>;
493
- providers?: boolean;
494
- },
495
- Effect.Effect<void, any, Provided>,
496
- ]
497
- | [Effect.Effect<void, any, Provided>]
498
- ) {
133
+ const provider = ((name, fn, opts) => {
134
+ it.live(name, () => wrapProvider(name, fn), timeoutOf(opts));
135
+ }) as ProviderFn;
136
+ provider.skip = (name, _fn, opts) => {
137
+ it.skip(name, () => {}, timeoutOf(opts));
138
+ };
139
+ provider.skipIf = (condition) => (name, fn, opts) => {
499
140
  if (condition) {
500
- const [options = {}, _testCase] =
501
- args.length === 1 ? [undefined, args[0]] : args;
502
- it.skip(name, () => {}, options.timeout);
141
+ it.skip(name, () => {}, timeoutOf(opts));
503
142
  } else {
504
- test(name, ...(args as [Effect.Effect<void, any, Provided>]));
143
+ it.live(name, () => wrapProvider(name, fn), timeoutOf(opts));
505
144
  }
506
145
  };
507
- }
146
+ test.provider = provider;
147
+
148
+ const beforeAll: BeforeAllFn = <A>(
149
+ eff: TestEffect<A>,
150
+ hookOptions?: TestOptions,
151
+ ) => {
152
+ let result: A;
153
+ vitestBeforeAll(
154
+ () => runEff(eff).then((v) => (result = v)),
155
+ timeoutOf(hookOptions) ?? DEFAULT_TIMEOUT,
156
+ );
157
+ return Effect.sync(() => result);
158
+ };
508
159
 
509
- export function beforeAll<A, E = never, R extends Provided = never>(
510
- effect: Effect.Effect<A, E, R>,
511
- options?: { timeout?: number; stackName?: string },
512
- ): void {
513
- const testPath = expect.getState().testPath ?? "";
514
- const stackName = options?.stackName ?? deriveStackName(testPath, "suite");
160
+ const beforeEach: BeforeEachFn = (eff, hookOptions) => {
161
+ vitestBeforeEach(() => runEff(eff), timeoutOf(hookOptions));
162
+ };
515
163
 
516
- vitestBeforeAll(
517
- () => Effect.runPromise(Effect.scoped(runWithContext(stackName, effect))),
518
- options?.timeout,
519
- );
520
- }
164
+ const afterAll = ((eff, hookOptions) => {
165
+ vitestAfterAll(
166
+ () => runEff(eff),
167
+ timeoutOf(hookOptions) ?? DEFAULT_TIMEOUT,
168
+ );
169
+ }) as AfterAllFn;
170
+ afterAll.skipIf = (predicate) => (eff, hookOptions) => {
171
+ if (predicate) return;
172
+ vitestAfterAll(
173
+ () => runEff(eff),
174
+ timeoutOf(hookOptions) ?? DEFAULT_TIMEOUT,
175
+ );
176
+ };
521
177
 
522
- export function afterAll<A, E, R extends Provided = never>(
523
- effect: Effect.Effect<A, E, R>,
524
- options?: { timeout?: number; stackName?: string },
525
- ): void {
526
- const testPath = expect.getState().testPath ?? "";
527
- const stackName = options?.stackName ?? deriveStackName(testPath, "suite");
178
+ const afterEach: AfterEachFn = (eff, hookOptions) => {
179
+ vitestAfterEach(() => runEff(eff), timeoutOf(hookOptions));
180
+ };
528
181
 
529
- vitestAfterAll(
530
- () => Effect.runPromise(Effect.scoped(runWithContext(stackName, effect))),
531
- options?.timeout,
532
- );
533
- }
534
- export const destroy = () =>
535
- Stack.Stack.use((stack) =>
536
- Plan.make({
537
- name: stack.name,
538
- stage: stack.stage,
539
- resources: {},
540
- bindings: {},
541
- output: {},
542
- }).pipe(Effect.flatMap(apply), provideFreshArtifactStore),
543
- );
182
+ return {
183
+ test,
184
+ beforeAll,
185
+ beforeEach,
186
+ afterAll,
187
+ afterEach,
188
+ deploy: (stack, callOpts) => Core.deploy(options, stack, callOpts),
189
+ destroy: (stack, callOpts) => Core.destroy(options, stack, callOpts),
190
+ };
191
+ };