@reventlessdev/reventless-seed-aws 1.0.0-alpha.4 → 1.0.0-alpha.6

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 (42) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/bs/.compiler.log +2 -2
  3. package/lib/bs/.sourcedirs.json +1 -0
  4. package/lib/bs/compiler-info.json +2 -2
  5. package/lib/bs/src/ReventlessSeedAws.ast +0 -0
  6. package/lib/bs/src/ReventlessSeedAws.cmi +0 -0
  7. package/lib/bs/src/ReventlessSeedAws.cmj +0 -0
  8. package/lib/bs/src/ReventlessSeedAws.cmt +0 -0
  9. package/lib/bs/src/ReventlessSeedAws.res +131 -75
  10. package/lib/bs/src/ReventlessSeedAws.res.mjs +63 -30
  11. package/lib/bs/src/ReventlessSeedAws_Reset.ast +0 -0
  12. package/lib/bs/src/ReventlessSeedAws_Reset.cmi +0 -0
  13. package/lib/bs/src/ReventlessSeedAws_Reset.cmt +0 -0
  14. package/lib/bs/src/ReventlessSeedAws_Reset.res +8 -15
  15. package/lib/bs/tests/EndpointResolutionTest.ast +0 -0
  16. package/lib/bs/tests/EndpointResolutionTest.cmi +0 -0
  17. package/lib/bs/tests/EndpointResolutionTest.cmj +0 -0
  18. package/lib/bs/tests/EndpointResolutionTest.cmt +0 -0
  19. package/lib/bs/tests/EndpointResolutionTest.res +180 -0
  20. package/lib/bs/tests/EndpointResolutionTest.res.mjs +208 -0
  21. package/lib/ocaml/.compiler.log +2 -2
  22. package/lib/ocaml/EndpointResolutionTest.ast +0 -0
  23. package/lib/ocaml/EndpointResolutionTest.cmi +0 -0
  24. package/lib/ocaml/EndpointResolutionTest.cmj +0 -0
  25. package/lib/ocaml/EndpointResolutionTest.cmt +0 -0
  26. package/lib/ocaml/EndpointResolutionTest.res +180 -0
  27. package/lib/ocaml/ReventlessSeedAws.ast +0 -0
  28. package/lib/ocaml/ReventlessSeedAws.cmi +0 -0
  29. package/lib/ocaml/ReventlessSeedAws.cmj +0 -0
  30. package/lib/ocaml/ReventlessSeedAws.cmt +0 -0
  31. package/lib/ocaml/ReventlessSeedAws.res +131 -75
  32. package/lib/ocaml/ReventlessSeedAws_Reset.ast +0 -0
  33. package/lib/ocaml/ReventlessSeedAws_Reset.cmi +0 -0
  34. package/lib/ocaml/ReventlessSeedAws_Reset.cmt +0 -0
  35. package/lib/ocaml/ReventlessSeedAws_Reset.res +8 -15
  36. package/package.json +6 -4
  37. package/rescript.json +8 -1
  38. package/src/ReventlessSeedAws.res +131 -75
  39. package/src/ReventlessSeedAws.res.mjs +63 -30
  40. package/src/ReventlessSeedAws_Reset.res +8 -15
  41. package/tests/EndpointResolutionTest.res +180 -0
  42. package/tests/EndpointResolutionTest.res.mjs +208 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 1.0.0-alpha.6 (2026-07-31)
7
+
8
+ **Note:** Version bump only for package @reventlessdev/reventless-seed-aws
9
+
10
+
11
+
12
+
13
+
14
+ # 1.0.0-alpha.5 (2026-07-31)
15
+
16
+ ### Bug Fixes
17
+
18
+ * **seed:** address a deployment's per-store upload endpoints ([45b226e](https://github.com/ReventlessDev/reventless-core/commit/45b226e00604af9fd28b6fb0b75ba7d3414791f2))
19
+
20
+
6
21
  # 1.0.0-alpha.4 (2026-07-27)
7
22
 
8
23
  ### Features
@@ -1,2 +1,2 @@
1
- #Start(1785181007711)
2
- #Done(1785181012329)
1
+ #Start(1785535392961)
2
+ #Done(1785535392990)
@@ -0,0 +1 @@
1
+ {"cmt_scan":[{"also_scan_build_root":true,"build_root":"lib/bs","scan_dirs":["src","tests"]}],"dirs":["src","tests"],"generated":[],"pkgs":[["@reventlessdev/rescript-aws-sdk","/home/runner/work/reventless-core/reventless-core/rescript/aws-sdk"],["@reventlessdev/rescript-jest","/home/runner/work/reventless-core/reventless-core/rescript/jest"],["@reventlessdev/rescript-node","/home/runner/work/reventless-core/reventless-core/rescript/node"],["@reventlessdev/reventless-seed","/home/runner/work/reventless-core/reventless-core/reventless/seed"]],"version":2}
@@ -2,7 +2,7 @@
2
2
  "version": "12.3.0",
3
3
  "bsc_path": "/home/runner/work/reventless-core/reventless-core/node_modules/@rescript/linux-x64/bin/bsc.exe",
4
4
  "bsc_hash": "75e3a59c95cc953608fd3dd6ea6ed68141bba4414a20a52f114261b8a48385fa",
5
- "rescript_config_hash": "3d482ebc161b93b84503a064c1b55dbce9106efcb53cc320aba2d3d291d8ab86",
5
+ "rescript_config_hash": "c18a0067c363fd1290f6e7f6fe6212d2e16e201c1702fac667962047f88ed468",
6
6
  "runtime_path": "/home/runner/work/reventless-core/reventless-core/node_modules/@rescript/runtime",
7
- "generated_at": "1785181012338"
7
+ "generated_at": "1785535392996"
8
8
  }
Binary file
Binary file
Binary file
Binary file
@@ -14,11 +14,6 @@ open ReventlessSeed
14
14
 
15
15
  // ── Node bindings ─────────────────────────────────────────────────────────────
16
16
 
17
- @val @scope("process") external processEnv: dict<string> = "env"
18
-
19
- type execOptions = {cwd: string, encoding: string, env?: dict<string>}
20
- @module("node:child_process")
21
- external execFileSync: (string, array<string>, execOptions) => string = "execFileSync"
22
17
 
23
18
  type fetchInit = {method: string, headers: dict<string>, body?: string}
24
19
  type response
@@ -41,6 +36,18 @@ let asString = (json: JSON.t): option<string> =>
41
36
  | _ => None
42
37
  }
43
38
 
39
+ // Non-string members are dropped rather than failing the whole map: one
40
+ // malformed entry should not cost a run every endpoint it could have used.
41
+ let asStringDict = (json: JSON.t): dict<string> =>
42
+ switch json {
43
+ | Object(obj) =>
44
+ obj
45
+ ->Dict.toArray
46
+ ->Array.filterMap(((k, v)) => v->asString->Option.map(s => (k, s)))
47
+ ->Dict.fromArray
48
+ | _ => Dict.make()
49
+ }
50
+
44
51
  // ── Stack discovery ───────────────────────────────────────────────────────────
45
52
 
46
53
  // Which Pulumi backend the `pulumi` subprocess reads from. When `backend` is
@@ -53,13 +60,17 @@ let envForBackend = (backend: option<string>): option<dict<string>> =>
53
60
  switch backend {
54
61
  | None => None
55
62
  | Some(url) =>
56
- let copy = processEnv->Dict.toArray->Dict.fromArray
63
+ let copy = NodeProcess.env->Dict.toArray->Dict.fromArray
57
64
  copy->Dict.set("PULUMI_BACKEND_URL", url)
58
65
  Some(copy)
59
66
  }
60
67
 
61
68
  let pulumi = (~projectDir: string, ~backend: option<string>, args: array<string>): string =>
62
- execFileSync("pulumi", args, {cwd: projectDir, encoding: "utf8", env: ?envForBackend(backend)})
69
+ NodeChildProcess.execFileSync(
70
+ "pulumi",
71
+ args,
72
+ {cwd: projectDir, encoding: "utf8", env: ?envForBackend(backend)},
73
+ )
63
74
 
64
75
  // Stacks that actually exist in the Pulumi backend for this project — the names
65
76
  // `pulumi stack output --stack <name>` accepts. A `Pulumi.<stack>.yaml` config
@@ -147,70 +158,118 @@ let resolveField = (~envKey: string, ~fromSource: option<string>, ~human: string
147
158
  }
148
159
  }
149
160
 
150
- // Returns (graphqlEndpoint, uploadEndpoint, cognitoRegion, cognitoClientId).
151
- // `uploadEndpoint` is "" when the deployment publishes none the data set's
152
- // upload phase no-ops on empty.
153
- let resolveEndpoints = async (~projectDir: string, ~backend: option<string>, ~stack: string): (
154
- string,
155
- string,
156
- string,
157
- string,
158
- ) => {
159
- let outputs = stackOutputs(~projectDir, ~backend, stack)
160
- switch outputs->field("hostShellUrl")->Option.flatMap(asString) {
161
- | Some(hostShellUrl) =>
162
- let cfg = await fetchConfig(hostShellUrl)
161
+ // Same precedence, for a value whose absence is a legitimate answer rather than
162
+ // a broken deployment: a stack that declares no store publishes no upload
163
+ // endpoint, and that must stay a no-op at the data set instead of failing the
164
+ // run before a single command is sent.
165
+ let optionalField = (~envKey: string, ~fromSource: option<string>): string =>
166
+ switch Seed.Prompt.envValue(envKey) {
167
+ | Some(v) => v
168
+ | None => fromSource->Option.getOr("")
169
+ }
170
+
171
+ // Which document publishes the deployment's endpoints. A stack that serves a
172
+ // host shell publishes them in the shell's `config.json` under the client's key
173
+ // names; one that does not publishes them as stack outputs under Pulumi's. The
174
+ // two arms name different keys for the same values, which is why this is a
175
+ // variant and not a merged lookup.
176
+ type source = HostShellConfig(JSON.t) | StackOutputs(JSON.t)
177
+
178
+ type endpoints = {
179
+ graphql: string,
180
+ // The legacy single presign service; "" when the deployment publishes none.
181
+ uploadEndpoint: string,
182
+ // Qualified `{plugin}.{store}` → that store's presign endpoint. Empty when the
183
+ // deployment declares no store.
184
+ uploadEndpoints: dict<string>,
185
+ cognitoRegion: string,
186
+ cognitoClientId: string,
187
+ }
188
+
189
+ /**
190
+ * The endpoints a source document publishes — pure, so the branch selection and
191
+ * every key it reads can be exercised with synthetic documents.
192
+ *
193
+ * `uploadEndpoints` is read on **both** arms. It was read on neither: the
194
+ * host-shell arm looked only at the singular `uploadEndpoint`, and the
195
+ * stack-output arm had no upload lookup at all, so a platform that publishes its
196
+ * stores and no host shell resolved "" and reported itself as serving no
197
+ * uploads.
198
+ *
199
+ * `REVENTLESS_UPLOAD_ENDPOINT` still overrides the singular endpoint and does
200
+ * not touch the map: one string cannot express a map, so it could only ever
201
+ * override one store and would need a naming convention to say which.
202
+ */
203
+ let endpointsFrom = (~stack: string, source: source): endpoints =>
204
+ switch source {
205
+ | HostShellConfig(cfg) =>
163
206
  let fromCfg = key => cfg->field(key)->Option.flatMap(asString)
164
- let endpoint = resolveField(
165
- ~envKey="REVENTLESS_GRAPHQL_ENDPOINT",
166
- ~fromSource=fromCfg("apiEndpoint"),
167
- ~human="apiEndpoint",
168
- )
169
- let uploadEndpoint = resolveField(
170
- ~envKey="REVENTLESS_UPLOAD_ENDPOINT",
171
- ~fromSource=fromCfg("uploadEndpoint"),
172
- ~human="uploadEndpoint",
173
- )
174
- let region = resolveField(
175
- ~envKey="AWS_REGION",
176
- ~fromSource=fromCfg("region"),
177
- ~human="region",
178
- )
179
- let clientId = resolveField(
180
- ~envKey="COGNITO_CLIENT_ID",
181
- ~fromSource=fromCfg("cognitoClientId"),
182
- ~human="cognitoClientId",
183
- )
184
- (endpoint, uploadEndpoint, region, clientId)
185
- | None =>
207
+ {
208
+ graphql: resolveField(
209
+ ~envKey="REVENTLESS_GRAPHQL_ENDPOINT",
210
+ ~fromSource=fromCfg("apiEndpoint"),
211
+ ~human="apiEndpoint",
212
+ ),
213
+ uploadEndpoint: optionalField(
214
+ ~envKey="REVENTLESS_UPLOAD_ENDPOINT",
215
+ ~fromSource=fromCfg("uploadEndpoint"),
216
+ ),
217
+ uploadEndpoints: cfg->field("uploadEndpoints")->Option.mapOr(Dict.make(), asStringDict),
218
+ cognitoRegion: resolveField(
219
+ ~envKey="AWS_REGION",
220
+ ~fromSource=fromCfg("region"),
221
+ ~human="region",
222
+ ),
223
+ cognitoClientId: resolveField(
224
+ ~envKey="COGNITO_CLIENT_ID",
225
+ ~fromSource=fromCfg("cognitoClientId"),
226
+ ~human="cognitoClientId",
227
+ ),
228
+ }
229
+ | StackOutputs(outputs) =>
186
230
  let out = key => outputs->field(key)->Option.flatMap(asString)
187
- let endpoint = switch (
188
- Seed.Prompt.envValue("REVENTLESS_GRAPHQL_ENDPOINT"),
189
- out("domainMergedApiEndpoint"),
190
- out("domainApiEndpoint"),
191
- ) {
192
- | (Some(v), _, _) | (_, Some(v), _) | (_, _, Some(v)) => v
193
- | _ =>
194
- throw(
195
- Seed.Failed(
196
- `stack "${stack}" exports neither domainMergedApiEndpoint nor domainApiEndpoint`,
197
- ),
198
- )
231
+ {
232
+ graphql: switch (
233
+ Seed.Prompt.envValue("REVENTLESS_GRAPHQL_ENDPOINT"),
234
+ out("domainMergedApiEndpoint"),
235
+ out("domainApiEndpoint"),
236
+ ) {
237
+ | (Some(v), _, _) | (_, Some(v), _) | (_, _, Some(v)) => v
238
+ | _ =>
239
+ throw(
240
+ Seed.Failed(
241
+ `stack "${stack}" exports neither domainMergedApiEndpoint nor domainApiEndpoint`,
242
+ ),
243
+ )
244
+ },
245
+ // No stack output carries the legacy single service — it exists only in a
246
+ // host shell's config.json — so here the env override is its only source.
247
+ uploadEndpoint: optionalField(~envKey="REVENTLESS_UPLOAD_ENDPOINT", ~fromSource=None),
248
+ uploadEndpoints: outputs->field("uploadEndpoints")->Option.mapOr(Dict.make(), asStringDict),
249
+ cognitoRegion: resolveField(
250
+ ~envKey="AWS_REGION",
251
+ ~fromSource=out("cognitoRegion"),
252
+ ~human="cognitoRegion",
253
+ ),
254
+ cognitoClientId: resolveField(
255
+ ~envKey="COGNITO_CLIENT_ID",
256
+ ~fromSource=out("cognitoUserPoolClientId"),
257
+ ~human="cognitoUserPoolClientId",
258
+ ),
199
259
  }
200
- let region = resolveField(
201
- ~envKey="AWS_REGION",
202
- ~fromSource=out("cognitoRegion"),
203
- ~human="cognitoRegion",
204
- )
205
- let clientId = resolveField(
206
- ~envKey="COGNITO_CLIENT_ID",
207
- ~fromSource=out("cognitoUserPoolClientId"),
208
- ~human="cognitoUserPoolClientId",
209
- )
210
- // Absent → "" → the data set skips its upload phase.
211
- let uploadEndpoint = Seed.Prompt.envValue("REVENTLESS_UPLOAD_ENDPOINT")->Option.getOr("")
212
- (endpoint, uploadEndpoint, region, clientId)
213
260
  }
261
+
262
+ let resolveEndpoints = async (
263
+ ~projectDir: string,
264
+ ~backend: option<string>,
265
+ ~stack: string,
266
+ ): endpoints => {
267
+ let outputs = stackOutputs(~projectDir, ~backend, stack)
268
+ let source = switch outputs->field("hostShellUrl")->Option.flatMap(asString) {
269
+ | Some(hostShellUrl) => HostShellConfig(await fetchConfig(hostShellUrl))
270
+ | None => StackOutputs(outputs)
271
+ }
272
+ endpointsFrom(~stack, source)
214
273
  }
215
274
 
216
275
  // ── Cognito login ─────────────────────────────────────────────────────────────
@@ -302,15 +361,12 @@ let connect = (~projectDir: string=".", ~stack=?, ~backend=?, ()): (
302
361
  | None => backend
303
362
  }
304
363
  let stackName = await resolveStack(~projectDir, ~backend, ~stack)
305
- let (endpoint, uploadEndpoint, region, clientId) = await resolveEndpoints(
306
- ~projectDir,
307
- ~backend,
308
- ~stack=stackName,
309
- )
364
+ let eps = await resolveEndpoints(~projectDir, ~backend, ~stack=stackName)
310
365
  await Seed.Connect.make(
311
366
  ~label=stackName,
312
- ~endpoint,
313
- ~uploadEndpoint,
314
- ~login=cognito(~region, ~clientId),
367
+ ~endpoint=eps.graphql,
368
+ ~uploadEndpoint=eps.uploadEndpoint,
369
+ ~uploadEndpoints=eps.uploadEndpoints,
370
+ ~login=cognito(~region=eps.cognitoRegion, ~clientId=eps.cognitoClientId),
315
371
  )
316
372
  }
@@ -19,6 +19,20 @@ function asString(json) {
19
19
  }
20
20
  }
21
21
 
22
+ function asStringDict(json) {
23
+ if (typeof json === "object" && json !== null && !Array.isArray(json)) {
24
+ return Object.fromEntries(Stdlib_Array.filterMap(Object.entries(json), param => {
25
+ let k = param[0];
26
+ return Stdlib_Option.map(asString(param[1]), s => [
27
+ k,
28
+ s
29
+ ]);
30
+ }));
31
+ } else {
32
+ return {};
33
+ }
34
+ }
35
+
22
36
  function envForBackend(backend) {
23
37
  if (backend === undefined) {
24
38
  return;
@@ -152,34 +166,39 @@ function resolveField(envKey, fromSource, human) {
152
166
  };
153
167
  }
154
168
 
155
- async function resolveEndpoints(projectDir, backend, stack) {
156
- let outputs = stackOutputs(projectDir, backend, stack);
157
- let hostShellUrl = Stdlib_Option.flatMap(field(outputs, "hostShellUrl"), asString);
158
- if (hostShellUrl !== undefined) {
159
- let cfg = await fetchConfig(hostShellUrl);
169
+ function optionalField(envKey, fromSource) {
170
+ let v = Seed_Prompt$ReventlessSeed.envValue(envKey);
171
+ if (v !== undefined) {
172
+ return v;
173
+ } else {
174
+ return Stdlib_Option.getOr(fromSource, "");
175
+ }
176
+ }
177
+
178
+ function endpointsFrom(stack, source) {
179
+ if (source.TAG === "HostShellConfig") {
180
+ let cfg = source._0;
160
181
  let fromCfg = key => Stdlib_Option.flatMap(field(cfg, key), asString);
161
- let endpoint = resolveField("REVENTLESS_GRAPHQL_ENDPOINT", fromCfg("apiEndpoint"), "apiEndpoint");
162
- let uploadEndpoint = resolveField("REVENTLESS_UPLOAD_ENDPOINT", fromCfg("uploadEndpoint"), "uploadEndpoint");
163
- let region = resolveField("AWS_REGION", fromCfg("region"), "region");
164
- let clientId = resolveField("COGNITO_CLIENT_ID", fromCfg("cognitoClientId"), "cognitoClientId");
165
- return [
166
- endpoint,
167
- uploadEndpoint,
168
- region,
169
- clientId
170
- ];
182
+ return {
183
+ graphql: resolveField("REVENTLESS_GRAPHQL_ENDPOINT", fromCfg("apiEndpoint"), "apiEndpoint"),
184
+ uploadEndpoint: optionalField("REVENTLESS_UPLOAD_ENDPOINT", fromCfg("uploadEndpoint")),
185
+ uploadEndpoints: Stdlib_Option.mapOr(field(cfg, "uploadEndpoints"), {}, asStringDict),
186
+ cognitoRegion: resolveField("AWS_REGION", fromCfg("region"), "region"),
187
+ cognitoClientId: resolveField("COGNITO_CLIENT_ID", fromCfg("cognitoClientId"), "cognitoClientId")
188
+ };
171
189
  }
190
+ let outputs = source._0;
172
191
  let out = key => Stdlib_Option.flatMap(field(outputs, key), asString);
173
192
  let match = Seed_Prompt$ReventlessSeed.envValue("REVENTLESS_GRAPHQL_ENDPOINT");
174
193
  let match$1 = out("domainMergedApiEndpoint");
175
194
  let match$2 = out("domainApiEndpoint");
176
- let endpoint$1;
195
+ let tmp;
177
196
  if (match !== undefined) {
178
- endpoint$1 = match;
197
+ tmp = match;
179
198
  } else if (match$1 !== undefined) {
180
- endpoint$1 = match$1;
199
+ tmp = match$1;
181
200
  } else if (match$2 !== undefined) {
182
- endpoint$1 = match$2;
201
+ tmp = match$2;
183
202
  } else {
184
203
  throw {
185
204
  RE_EXN_ID: Seed$ReventlessSeed.Failed,
@@ -187,15 +206,26 @@ async function resolveEndpoints(projectDir, backend, stack) {
187
206
  Error: new Error()
188
207
  };
189
208
  }
190
- let region$1 = resolveField("AWS_REGION", out("cognitoRegion"), "cognitoRegion");
191
- let clientId$1 = resolveField("COGNITO_CLIENT_ID", out("cognitoUserPoolClientId"), "cognitoUserPoolClientId");
192
- let uploadEndpoint$1 = Stdlib_Option.getOr(Seed_Prompt$ReventlessSeed.envValue("REVENTLESS_UPLOAD_ENDPOINT"), "");
193
- return [
194
- endpoint$1,
195
- uploadEndpoint$1,
196
- region$1,
197
- clientId$1
198
- ];
209
+ return {
210
+ graphql: tmp,
211
+ uploadEndpoint: optionalField("REVENTLESS_UPLOAD_ENDPOINT", undefined),
212
+ uploadEndpoints: Stdlib_Option.mapOr(field(outputs, "uploadEndpoints"), {}, asStringDict),
213
+ cognitoRegion: resolveField("AWS_REGION", out("cognitoRegion"), "cognitoRegion"),
214
+ cognitoClientId: resolveField("COGNITO_CLIENT_ID", out("cognitoUserPoolClientId"), "cognitoUserPoolClientId")
215
+ };
216
+ }
217
+
218
+ async function resolveEndpoints(projectDir, backend, stack) {
219
+ let outputs = stackOutputs(projectDir, backend, stack);
220
+ let hostShellUrl = Stdlib_Option.flatMap(field(outputs, "hostShellUrl"), asString);
221
+ let source = hostShellUrl !== undefined ? ({
222
+ TAG: "HostShellConfig",
223
+ _0: await fetchConfig(hostShellUrl)
224
+ }) : ({
225
+ TAG: "StackOutputs",
226
+ _0: outputs
227
+ });
228
+ return endpointsFrom(stack, source);
199
229
  }
200
230
 
201
231
  function cognito(region, clientId) {
@@ -282,14 +312,15 @@ function connect($staropt$star, stack, backend, param) {
282
312
  let url = Seed_Prompt$ReventlessSeed.envValue("SEED_PULUMI_BACKEND");
283
313
  let backend$1 = url !== undefined ? url : backend;
284
314
  let stackName = await resolveStack(projectDir, backend$1, stack);
285
- let match = await resolveEndpoints(projectDir, backend$1, stackName);
286
- return await Seed_Connect$ReventlessSeed.make(stackName, match[0], match[1], cognito(match[2], match[3]), undefined);
315
+ let eps = await resolveEndpoints(projectDir, backend$1, stackName);
316
+ return await Seed_Connect$ReventlessSeed.make(stackName, eps.graphql, eps.uploadEndpoint, eps.uploadEndpoints, cognito(eps.cognitoRegion, eps.cognitoClientId), undefined);
287
317
  };
288
318
  }
289
319
 
290
320
  export {
291
321
  field,
292
322
  asString,
323
+ asStringDict,
293
324
  envForBackend,
294
325
  pulumi,
295
326
  deployedStacks,
@@ -298,6 +329,8 @@ export {
298
329
  resolveStack,
299
330
  fetchConfig,
300
331
  resolveField,
332
+ optionalField,
333
+ endpointsFrom,
301
334
  resolveEndpoints,
302
335
  cognito,
303
336
  connect,
@@ -48,14 +48,7 @@ module Ddb = AwsSdk.DynamoDb
48
48
  module Rgt = AwsSdk.ResourceGroupsTaggingApi
49
49
  module S3 = AwsSdk.S3
50
50
 
51
- @scope("process") @val external exit: int => unit = "exit"
52
- @scope("process") @val external processEnv: dict<string> = "env"
53
- @module("node:fs") external readFileSync: (string, string) => string = "readFileSync"
54
51
 
55
- type stream
56
- @scope("process") @val external stdin: stream = "stdin"
57
- // `process.stdin.isTTY` is `true` interactively and `undefined` under a pipe/CI.
58
- @get external isTTY: stream => option<bool> = "isTTY"
59
52
 
60
53
  // The Pulumi project name, which the framework stamps as `reventless:platform` on
61
54
  // every resource (`Plugin.res`: `platformName = getProjectName()`). Discovery
@@ -66,7 +59,7 @@ type stream
66
59
  // the target project's Pulumi.yaml — the same dir the pulumi subprocess uses.
67
60
  let projectName = (~projectDir: string): string => {
68
61
  let path = `${projectDir}/Pulumi.yaml`
69
- let raw = try readFileSync(path, "utf8") catch {
62
+ let raw = try NodeFs.readFileSync(path) catch {
70
63
  | _ => throw(Seed.Failed(`could not read ${path} to scope the wipe to this Pulumi project.`))
71
64
  }
72
65
  switch raw
@@ -561,7 +554,7 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
561
554
  ),
562
555
  )
563
556
  }
564
- processEnv->Dict.set("AWS_REGION", region)
557
+ NodeProcess.env->Dict.set("AWS_REGION", region)
565
558
 
566
559
  // Discover + count each target, scoped to its own project via the platform
567
560
  // tag so a same-named stack from another project is never touched.
@@ -597,7 +590,7 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
597
590
  Seed.Prompt.close()
598
591
  Console.log("")
599
592
  Console.log(`Nothing to reset — the selected scope already reads empty in ${region}.`)
600
- exit(0)
593
+ NodeProcess.exit(0)
601
594
  }
602
595
 
603
596
  // Confirmation. Interactively, re-typing the exact stack name IS the
@@ -606,7 +599,7 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
606
599
  // TTY (CI/scripts) there is nothing to type into, so
607
600
  // REVENTLESS_WIPE_CONFIRM=<stack> is the equivalent opt-in. Dry-run is the
608
601
  // default either way.
609
- let interactive = stdin->isTTY->Option.getOr(false)
602
+ let interactive = NodeProcess.stdin->NodeProcess.isTTY->Option.getOr(false)
610
603
  let confirmed = if interactive {
611
604
  let typed = await Seed.Prompt.ask(
612
605
  `About to permanently empty ${total->Int.toString} item(s)/object(s) across the selected scope of "${stack}". ` ++
@@ -624,7 +617,7 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
624
617
  ? "Dry run — nothing was deleted."
625
618
  : `Dry run — nothing was deleted. Set REVENTLESS_WIPE_CONFIRM=${stack} to empty this scope non-interactively.`,
626
619
  )
627
- exit(0)
620
+ NodeProcess.exit(0)
628
621
  }
629
622
 
630
623
  for i in 0 to resolvedList->Array.length - 1 {
@@ -681,19 +674,19 @@ let run = (~stack=?, ~backend=?, ~targets: array<target>, ()): unit => {
681
674
 
682
675
  Console.log("")
683
676
  Console.log(`Reset complete — the selected scope of "${stack}" reads empty and is re-seedable.`)
684
- exit(0)
677
+ NodeProcess.exit(0)
685
678
  } catch {
686
679
  | Seed.Failed(message) =>
687
680
  Seed.Prompt.close()
688
681
  Console.error("")
689
682
  Console.error(`Reset aborted — ${message}`)
690
- exit(1)
683
+ NodeProcess.exit(1)
691
684
  | exn =>
692
685
  Seed.Prompt.close()
693
686
  Console.error("")
694
687
  Console.error("Reset aborted with an unexpected error:")
695
688
  Console.error(exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("unknown"))
696
- exit(1)
689
+ NodeProcess.exit(1)
697
690
  }
698
691
  }
699
692
  go()->ignore