@reventlessdev/reventless-seed 1.0.0-alpha.2 → 1.0.0-alpha.20

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 (158) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/lib/bs/.compiler.log +2 -2
  3. package/lib/bs/.sourcedirs.json +1 -0
  4. package/lib/bs/ReventlessSeed.cmi +0 -0
  5. package/lib/bs/ReventlessSeed.cmt +0 -0
  6. package/lib/bs/ReventlessSeed.mlmap +5 -0
  7. package/lib/bs/compiler-info.json +2 -2
  8. package/lib/bs/src/Seed-ReventlessSeed.cmi +0 -0
  9. package/lib/bs/src/Seed-ReventlessSeed.cmt +0 -0
  10. package/lib/bs/src/Seed.ast +0 -0
  11. package/lib/bs/src/Seed.res +6 -3
  12. package/lib/bs/src/Seed.res.mjs +3 -0
  13. package/lib/bs/src/Seed_Client-ReventlessSeed.cmi +0 -0
  14. package/lib/bs/src/Seed_Client-ReventlessSeed.cmj +0 -0
  15. package/lib/bs/src/Seed_Client-ReventlessSeed.cmt +0 -0
  16. package/lib/bs/src/Seed_Client.ast +0 -0
  17. package/lib/bs/src/Seed_Client.res +228 -15
  18. package/lib/bs/src/Seed_Client.res.mjs +196 -23
  19. package/lib/bs/src/Seed_Connect-ReventlessSeed.cmi +0 -0
  20. package/lib/bs/src/Seed_Connect-ReventlessSeed.cmj +0 -0
  21. package/lib/bs/src/Seed_Connect-ReventlessSeed.cmt +0 -0
  22. package/lib/bs/src/Seed_Connect.ast +0 -0
  23. package/lib/bs/src/Seed_Connect.res +27 -18
  24. package/lib/bs/src/Seed_Connect.res.mjs +10 -6
  25. package/lib/bs/src/Seed_Prompt-ReventlessSeed.cmi +0 -0
  26. package/lib/bs/src/Seed_Prompt-ReventlessSeed.cmj +0 -0
  27. package/lib/bs/src/Seed_Prompt-ReventlessSeed.cmt +0 -0
  28. package/lib/bs/src/Seed_Prompt.ast +0 -0
  29. package/lib/bs/src/Seed_Prompt.res +110 -35
  30. package/lib/bs/src/Seed_Prompt.res.mjs +73 -8
  31. package/lib/bs/src/Seed_Random-ReventlessSeed.cmt +0 -0
  32. package/lib/bs/src/Seed_Runner-ReventlessSeed.cmi +0 -0
  33. package/lib/bs/src/Seed_Runner-ReventlessSeed.cmj +0 -0
  34. package/lib/bs/src/Seed_Runner-ReventlessSeed.cmt +0 -0
  35. package/lib/bs/src/Seed_Runner.ast +0 -0
  36. package/lib/bs/src/Seed_Runner.res +38 -7
  37. package/lib/bs/src/Seed_Runner.res.mjs +19 -1
  38. package/lib/bs/src/Seed_Types-ReventlessSeed.cmt +0 -0
  39. package/lib/bs/src/Seed_Upload-ReventlessSeed.cmi +0 -0
  40. package/lib/bs/src/Seed_Upload-ReventlessSeed.cmj +0 -0
  41. package/lib/bs/src/Seed_Upload-ReventlessSeed.cmt +0 -0
  42. package/lib/bs/src/Seed_Upload.ast +0 -0
  43. package/lib/bs/src/Seed_Upload.res +40 -42
  44. package/lib/bs/src/Seed_Upload.res.mjs +65 -69
  45. package/lib/bs/src/Seed_Users-ReventlessSeed.cmi +0 -0
  46. package/lib/bs/src/Seed_Users-ReventlessSeed.cmj +0 -0
  47. package/lib/bs/src/Seed_Users-ReventlessSeed.cmt +0 -0
  48. package/lib/bs/src/Seed_Users.ast +0 -0
  49. package/lib/bs/src/Seed_Users.res +80 -0
  50. package/lib/bs/src/Seed_Users.res.mjs +90 -0
  51. package/lib/bs/tests/Seed_ClientTest-ReventlessSeed.cmi +0 -0
  52. package/lib/bs/tests/Seed_ClientTest-ReventlessSeed.cmj +0 -0
  53. package/lib/bs/tests/Seed_ClientTest-ReventlessSeed.cmt +0 -0
  54. package/lib/bs/tests/Seed_ClientTest.ast +0 -0
  55. package/lib/bs/tests/Seed_ClientTest.res +157 -0
  56. package/lib/bs/tests/Seed_ClientTest.res.mjs +167 -0
  57. package/lib/bs/tests/Seed_PromptTest-ReventlessSeed.cmi +0 -0
  58. package/lib/bs/tests/Seed_PromptTest-ReventlessSeed.cmj +0 -0
  59. package/lib/bs/tests/Seed_PromptTest-ReventlessSeed.cmt +0 -0
  60. package/lib/bs/tests/Seed_PromptTest.ast +0 -0
  61. package/lib/bs/tests/Seed_PromptTest.res +59 -0
  62. package/lib/bs/tests/Seed_PromptTest.res.mjs +63 -0
  63. package/lib/bs/tests/Seed_RandomTest-ReventlessSeed.cmi +0 -0
  64. package/lib/bs/tests/Seed_RandomTest-ReventlessSeed.cmj +0 -0
  65. package/lib/bs/tests/Seed_RandomTest-ReventlessSeed.cmt +0 -0
  66. package/lib/bs/tests/Seed_RandomTest.ast +0 -0
  67. package/lib/bs/tests/Seed_RandomTest.res +160 -0
  68. package/lib/bs/tests/Seed_RandomTest.res.mjs +208 -0
  69. package/lib/bs/tests/Seed_UsersTest-ReventlessSeed.cmi +0 -0
  70. package/lib/bs/tests/Seed_UsersTest-ReventlessSeed.cmj +0 -0
  71. package/lib/bs/tests/Seed_UsersTest-ReventlessSeed.cmt +0 -0
  72. package/lib/bs/tests/Seed_UsersTest.ast +0 -0
  73. package/lib/bs/tests/Seed_UsersTest.res +92 -0
  74. package/lib/bs/tests/Seed_UsersTest.res.mjs +95 -0
  75. package/lib/ocaml/.compiler.log +2 -2
  76. package/lib/ocaml/ReventlessSeed.cmi +0 -0
  77. package/lib/ocaml/ReventlessSeed.cmt +0 -0
  78. package/lib/ocaml/ReventlessSeed.mlmap +5 -0
  79. package/lib/ocaml/Seed-ReventlessSeed.cmi +0 -0
  80. package/lib/ocaml/Seed-ReventlessSeed.cmt +0 -0
  81. package/lib/ocaml/Seed.ast +0 -0
  82. package/lib/ocaml/Seed.res +6 -3
  83. package/lib/ocaml/Seed_Client-ReventlessSeed.cmi +0 -0
  84. package/lib/ocaml/Seed_Client-ReventlessSeed.cmj +0 -0
  85. package/lib/ocaml/Seed_Client-ReventlessSeed.cmt +0 -0
  86. package/lib/ocaml/Seed_Client.ast +0 -0
  87. package/lib/ocaml/Seed_Client.res +228 -15
  88. package/lib/ocaml/Seed_ClientTest-ReventlessSeed.cmi +0 -0
  89. package/lib/ocaml/Seed_ClientTest-ReventlessSeed.cmj +0 -0
  90. package/lib/ocaml/Seed_ClientTest-ReventlessSeed.cmt +0 -0
  91. package/lib/ocaml/Seed_ClientTest.ast +0 -0
  92. package/lib/ocaml/Seed_ClientTest.res +157 -0
  93. package/lib/ocaml/Seed_Connect-ReventlessSeed.cmi +0 -0
  94. package/lib/ocaml/Seed_Connect-ReventlessSeed.cmj +0 -0
  95. package/lib/ocaml/Seed_Connect-ReventlessSeed.cmt +0 -0
  96. package/lib/ocaml/Seed_Connect.ast +0 -0
  97. package/lib/ocaml/Seed_Connect.res +27 -18
  98. package/lib/ocaml/Seed_Prompt-ReventlessSeed.cmi +0 -0
  99. package/lib/ocaml/Seed_Prompt-ReventlessSeed.cmj +0 -0
  100. package/lib/ocaml/Seed_Prompt-ReventlessSeed.cmt +0 -0
  101. package/lib/ocaml/Seed_Prompt.ast +0 -0
  102. package/lib/ocaml/Seed_Prompt.res +110 -35
  103. package/lib/ocaml/Seed_PromptTest-ReventlessSeed.cmi +0 -0
  104. package/lib/ocaml/Seed_PromptTest-ReventlessSeed.cmj +0 -0
  105. package/lib/ocaml/Seed_PromptTest-ReventlessSeed.cmt +0 -0
  106. package/lib/ocaml/Seed_PromptTest.ast +0 -0
  107. package/lib/ocaml/Seed_PromptTest.res +59 -0
  108. package/lib/ocaml/Seed_Random-ReventlessSeed.cmt +0 -0
  109. package/lib/ocaml/Seed_RandomTest-ReventlessSeed.cmi +0 -0
  110. package/lib/ocaml/Seed_RandomTest-ReventlessSeed.cmj +0 -0
  111. package/lib/ocaml/Seed_RandomTest-ReventlessSeed.cmt +0 -0
  112. package/lib/ocaml/Seed_RandomTest.ast +0 -0
  113. package/lib/ocaml/Seed_RandomTest.res +160 -0
  114. package/lib/ocaml/Seed_Runner-ReventlessSeed.cmi +0 -0
  115. package/lib/ocaml/Seed_Runner-ReventlessSeed.cmj +0 -0
  116. package/lib/ocaml/Seed_Runner-ReventlessSeed.cmt +0 -0
  117. package/lib/ocaml/Seed_Runner.ast +0 -0
  118. package/lib/ocaml/Seed_Runner.res +38 -7
  119. package/lib/ocaml/Seed_Types-ReventlessSeed.cmt +0 -0
  120. package/lib/ocaml/Seed_Upload-ReventlessSeed.cmi +0 -0
  121. package/lib/ocaml/Seed_Upload-ReventlessSeed.cmj +0 -0
  122. package/lib/ocaml/Seed_Upload-ReventlessSeed.cmt +0 -0
  123. package/lib/ocaml/Seed_Upload.ast +0 -0
  124. package/lib/ocaml/Seed_Upload.res +40 -42
  125. package/lib/ocaml/Seed_Users-ReventlessSeed.cmi +0 -0
  126. package/lib/ocaml/Seed_Users-ReventlessSeed.cmj +0 -0
  127. package/lib/ocaml/Seed_Users-ReventlessSeed.cmt +0 -0
  128. package/lib/ocaml/Seed_Users.ast +0 -0
  129. package/lib/ocaml/Seed_Users.res +80 -0
  130. package/lib/ocaml/Seed_UsersTest-ReventlessSeed.cmi +0 -0
  131. package/lib/ocaml/Seed_UsersTest-ReventlessSeed.cmj +0 -0
  132. package/lib/ocaml/Seed_UsersTest-ReventlessSeed.cmt +0 -0
  133. package/lib/ocaml/Seed_UsersTest.ast +0 -0
  134. package/lib/ocaml/Seed_UsersTest.res +92 -0
  135. package/package.json +7 -2
  136. package/rescript.json +9 -1
  137. package/src/Seed.res +6 -3
  138. package/src/Seed.res.mjs +3 -0
  139. package/src/Seed_Client.res +228 -15
  140. package/src/Seed_Client.res.mjs +196 -23
  141. package/src/Seed_Connect.res +27 -18
  142. package/src/Seed_Connect.res.mjs +10 -6
  143. package/src/Seed_Prompt.res +110 -35
  144. package/src/Seed_Prompt.res.mjs +73 -8
  145. package/src/Seed_Runner.res +38 -7
  146. package/src/Seed_Runner.res.mjs +19 -1
  147. package/src/Seed_Upload.res +40 -42
  148. package/src/Seed_Upload.res.mjs +65 -69
  149. package/src/Seed_Users.res +80 -0
  150. package/src/Seed_Users.res.mjs +90 -0
  151. package/tests/Seed_ClientTest.res +157 -0
  152. package/tests/Seed_ClientTest.res.mjs +167 -0
  153. package/tests/Seed_PromptTest.res +59 -0
  154. package/tests/Seed_PromptTest.res.mjs +63 -0
  155. package/tests/Seed_RandomTest.res +160 -0
  156. package/tests/Seed_RandomTest.res.mjs +208 -0
  157. package/tests/Seed_UsersTest.res +92 -0
  158. package/tests/Seed_UsersTest.res.mjs +95 -0
@@ -1,27 +1,26 @@
1
- // Seed-time asset upload against the framework's presign-shaped upload contract.
1
+ // Seed-time asset upload against the framework's upload contract (route B).
2
2
  //
3
- // One code path for both providers: the AWS `Upload_Presign_S3` Function URL and
4
- // the local dev upload route expose the SAME shape — POST `{fileName,contentType}`
5
- // → `{uploadUrl, storageRef}`, then PUT the bytes to `uploadUrl`. `uploadAsset`
6
- // resolves `Ok(storageRef)` — the same same-origin `/{prefix}/{key}` ref a command
7
- // stores and the UI renders.
3
+ // Minting is a GraphQL mutation on the domain API — `Upload_Presign(store, fileName,
4
+ // contentType)` — authenticated by the same bearer the seed already uses for commands
5
+ // (no separate endpoint, no anonymous surface). `uploadAsset` issues it through the
6
+ // shared `Seed_Client`, then PUTs the bytes to the returned `uploadUrl` and resolves
7
+ // `Ok(storageRef)` — the same `/{prefix}/{key}` ref a command stores and the UI renders.
8
8
  //
9
- // The seed runs under Node, so the returned `uploadUrl` is resolved to an
10
- // absolute URL against the endpoint's origin: AWS returns an absolute presigned
11
- // S3 URL (unchanged), while the local route returns a same-origin relative
12
- // `/{prefix}/{key}` that Node's `fetch` cannot PUT to until it is made absolute.
9
+ // The seed runs under Node, so a relative `uploadUrl` (the local dev server returns a
10
+ // same-origin `/{prefix}/{key}`) is resolved to an absolute URL against the client's
11
+ // endpoint origin before the PUT; AWS returns an absolute presigned S3 URL (used as-is).
12
+ // The PUT itself is unauthenticated — presigned on AWS, open on the local route.
13
13
 
14
14
  type fetchInit = {method: string, headers: dict<string>, body: string}
15
15
  type response
16
16
 
17
17
  @val external fetch: (string, fetchInit) => promise<response> = "fetch"
18
- @send external responseJson: response => promise<JSON.t> = "json"
19
18
  @send external responseText: response => promise<string> = "text"
20
19
  @get external responseOk: response => bool = "ok"
21
20
  @get external responseStatus: response => int = "status"
22
21
 
23
- // Resolve a possibly-relative URL against a base (WHATWG URL semantics: an
24
- // absolute `input` ignores `base`, a relative one is resolved against it).
22
+ // Resolve a possibly-relative URL against a base (WHATWG URL semantics: an absolute
23
+ // `input` ignores `base`, a relative one is resolved against it).
25
24
  type url
26
25
  @new external makeUrl: (string, string) => url = "URL"
27
26
  @get external urlHref: url => string = "href"
@@ -38,51 +37,50 @@ let asString = (json: JSON.t): option<string> =>
38
37
  | _ => None
39
38
  }
40
39
 
41
- // A `fetch` that reports an unreachable endpoint as `Error` instead of throwing,
42
- // so the two-step upload can thread failures without exceptions.
40
+ // Whether this run should skip uploads entirely. `SEED_SKIP_UPLOADS` seeds domain data
41
+ // fast, or skips a broken/absent upload path without editing the data set.
42
+ let uploadsSkipped = (): bool => Seed_Prompt.envValue("SEED_SKIP_UPLOADS")->Option.isSome
43
+
44
+ // A `fetch` that reports an unreachable endpoint as `Error` instead of throwing.
43
45
  let tryFetch = async (url: string, init: fetchInit): result<response, string> =>
44
46
  try Ok(await fetch(url, init)) catch {
45
47
  | _ => Error(`cannot reach ${url}`)
46
48
  }
47
49
 
48
50
  /**
49
- * Uploads `bytes` under `fileName`/`contentType` through `uploadEndpoint` and
51
+ * Uploads `bytes` under `fileName`/`contentType` into `store` (the qualified
52
+ * `{plugin}.{store}` the asset's field declares, e.g. `"Catalog.productImages"`) and
50
53
  * resolves `Ok(storageRef)` (the stored `/{prefix}/{key}` ref), or `Error(msg)`
51
- * describing the step that failed. `authToken`, when supplied, is sent as a
52
- * Bearer on the presign request (the endpoint decides whether it is required;
53
- * the PUT itself is unauthenticated — presigned on AWS, open locally).
54
+ * describing the step that failed. Mints through the domain API's `Upload_Presign`
55
+ * mutation on `client`, then PUTs the bytes to the returned `uploadUrl`.
54
56
  */
55
57
  let uploadAsset = async (
56
- ~uploadEndpoint: string,
58
+ ~client: Seed_Client.t,
59
+ ~store: string,
57
60
  ~bytes: string,
58
61
  ~fileName: string,
59
62
  ~contentType: string,
60
- ~authToken: option<string>=?,
61
63
  ): result<string, string> => {
62
- let presignHeaders = Dict.fromArray([("content-type", "application/json")])
63
- authToken->Option.forEach(t => presignHeaders->Dict.set("authorization", `Bearer ${t}`))
64
- let presignBody = JSON.stringify(
65
- JSON.Encode.object(
66
- Dict.fromArray([
67
- ("fileName", JSON.Encode.string(fileName)),
68
- ("contentType", JSON.Encode.string(contentType)),
69
- ]),
70
- ),
71
- )
64
+ // GraphQL string literals: JSON-quote the argument values (JSON string escaping is a
65
+ // superset-safe subset of GraphQL's for these ASCII-ish inputs).
66
+ let q = s => JSON.stringify(JSON.Encode.string(s))
67
+ let query = `mutation { r: Upload_Presign(store: ${q(store)}, fileName: ${q(fileName)}, contentType: ${q(contentType)}) { uploadUrl storageRef } }`
68
+
69
+ let presign = try Ok(await Seed_Client.gql(client, ~query, ~label="Upload_Presign")) catch {
70
+ | Seed_Types.Failed(m) => Error(m)
71
+ | _ => Error("Upload_Presign: unexpected error")
72
+ }
72
73
 
73
- switch await tryFetch(uploadEndpoint, {method: "POST", headers: presignHeaders, body: presignBody}) {
74
- | Error(m) => Error(`presign: ${m} — is the platform running?`)
75
- | Ok(presignRes) if !(presignRes->responseOk) =>
76
- let detail = await presignRes->responseText
77
- Error(`presign failed with HTTP ${(presignRes->responseStatus)->Int.toString}: ${detail}`)
78
- | Ok(presignRes) =>
79
- let json = await presignRes->responseJson
74
+ switch presign {
75
+ | Error(m) => Error(`presign: ${m}`)
76
+ | Ok(data) =>
77
+ let r = data->field("r")->Option.getOr(JSON.Encode.null)
80
78
  switch (
81
- json->field("uploadUrl")->Option.flatMap(asString),
82
- json->field("storageRef")->Option.flatMap(asString),
79
+ r->field("uploadUrl")->Option.flatMap(asString),
80
+ r->field("storageRef")->Option.flatMap(asString),
83
81
  ) {
84
82
  | (Some(uploadUrl), Some(storageRef)) =>
85
- let putUrl = urlHref(makeUrl(uploadUrl, uploadEndpoint))
83
+ let putUrl = urlHref(makeUrl(uploadUrl, Seed_Client.endpoint(client)))
86
84
  switch await tryFetch(
87
85
  putUrl,
88
86
  {method: "PUT", headers: Dict.fromArray([("content-type", contentType)]), body: bytes},
@@ -93,7 +91,7 @@ let uploadAsset = async (
93
91
  let detail = await putRes->responseText
94
92
  Error(`upload PUT failed with HTTP ${(putRes->responseStatus)->Int.toString}: ${detail}`)
95
93
  }
96
- | _ => Error("presign response missing uploadUrl/storageRef")
94
+ | _ => Error("Upload_Presign response missing uploadUrl/storageRef")
97
95
  }
98
96
  }
99
97
  }
Binary file
@@ -0,0 +1,80 @@
1
+ // The dev accounts a seed run can log in as, read from `.reventless/users.yaml`.
2
+ //
3
+ // Both platforms keep that file beside the package the seed runs from: locally
4
+ // the in-memory auth adapter loads it at startup, and on AWS it is the record of
5
+ // what was created in the Cognito pool. Either way it already holds the
6
+ // credentials an operator would otherwise retype, so the credential prompt
7
+ // offers its entries instead of asking for a password nobody remembers.
8
+ //
9
+ // Absence is normal and silent — the caller falls back to asking. Nothing here
10
+ // throws: a missing, unreadable or malformed file yields no accounts.
11
+
12
+ @module("yaml") external parseYaml: string => JSON.t = "parse"
13
+
14
+ /** One account: what to log in as, and what to send with it. */
15
+ type user = {username: string, password: string, groups: array<string>}
16
+
17
+ let asString = (json: JSON.t): option<string> =>
18
+ switch json {
19
+ | String(s) => Some(s)
20
+ | _ => None
21
+ }
22
+
23
+ let userOf = (json: JSON.t): option<user> =>
24
+ switch json {
25
+ | Object(obj) =>
26
+ switch (
27
+ obj->Dict.get("username")->Option.flatMap(asString),
28
+ obj->Dict.get("password")->Option.flatMap(asString),
29
+ ) {
30
+ | (Some(username), Some(password)) =>
31
+ let groups = switch obj->Dict.get("groups") {
32
+ | Some(JSON.Array(items)) => items->Array.filterMap(asString)
33
+ | _ => []
34
+ }
35
+ Some({username, password, groups})
36
+ | _ => None
37
+ }
38
+ | _ => None
39
+ }
40
+
41
+ /**
42
+ * The accounts a users.yaml document defines, in the order it defines them —
43
+ * the order is what makes "the first one" a stable default.
44
+ *
45
+ * An entry without both a username and a password is skipped rather than
46
+ * rejected: the file is hand-maintained, and one incomplete entry should not
47
+ * cost the operator the rest of the list.
48
+ */
49
+ let parseString = (yamlText: string): array<user> => {
50
+ let doc = try parseYaml(yamlText) catch {
51
+ | _ => JSON.Null
52
+ }
53
+ switch doc {
54
+ | Array(items) => items->Array.filterMap(userOf)
55
+ | _ => []
56
+ }
57
+ }
58
+
59
+ /** Where the file lives for the platform being seeded — the seed runs from that
60
+ package's directory, which is the same cwd the local auth adapter reads. */
61
+ let defaultPath = (): string => NodePath.join([NodeProcess.cwd(), ".reventless", "users.yaml"])
62
+
63
+ /**
64
+ * The accounts on disk, with the path they came from. `None` when there is no
65
+ * readable file with at least one usable account, which the caller reads as
66
+ * "ask for credentials instead".
67
+ */
68
+ let load = (~path: option<string>=?): option<(string, array<user>)> => {
69
+ let file = path->Option.getOr(defaultPath())
70
+ let users = try {
71
+ NodeFs.existsSync(file) ? parseString(NodeFs.readFileSync(file)) : []
72
+ } catch {
73
+ | _ => []
74
+ }
75
+ users->Array.length == 0 ? None : Some((file, users))
76
+ }
77
+
78
+ /** Menu label: the username, plus whatever groups the file records for it. */
79
+ let label = (u: user): string =>
80
+ u.groups->Array.length == 0 ? u.username : `${u.username} [${u.groups->Array.join(", ")}]`
Binary file
@@ -0,0 +1,92 @@
1
+ open JestGlobals
2
+
3
+ // `.reventless/users.yaml` is hand-maintained and shared with the auth adapter,
4
+ // so the seed reads it defensively: it must survive the comment blocks both
5
+ // copies carry, must not reorder the accounts (the first one is the prompt's
6
+ // default, so order IS the contract), and must never throw — a broken file costs
7
+ // the menu, not the run.
8
+
9
+ let document = `
10
+ # Local dev users — a comment block like the real file carries.
11
+
12
+ - username: admin
13
+ password: admin
14
+ groups: [Admin, Shopper]
15
+ userId: local-admin
16
+
17
+ - username: shopper
18
+ password: Shopper-2026!
19
+ groups: [Shopper]
20
+
21
+ - username: noGroups
22
+ password: pw
23
+ `
24
+
25
+ describe("Seed_Users.parseString:", () => {
26
+ testSync("reads every account, in the order the file defines them", () => {
27
+ let users = Seed_Users.parseString(document)
28
+ expect(users->Array.map(u => u.Seed_Users.username))->Expect.toEqual([
29
+ "admin",
30
+ "shopper",
31
+ "noGroups",
32
+ ])
33
+ })
34
+
35
+ testSync("carries the password beside the username", () => {
36
+ let users = Seed_Users.parseString(document)
37
+ expect(users->Array.map(u => u.Seed_Users.password))->Expect.toEqual([
38
+ "admin",
39
+ "Shopper-2026!",
40
+ "pw",
41
+ ])
42
+ })
43
+
44
+ testSync("keeps the groups, and reads a missing groups key as none", () => {
45
+ let users = Seed_Users.parseString(document)
46
+ expect(users->Array.map(u => u.Seed_Users.groups))->Expect.toEqual([
47
+ ["Admin", "Shopper"],
48
+ ["Shopper"],
49
+ [],
50
+ ])
51
+ })
52
+
53
+ // An entry half-written by hand is the likely flaw in this file. Dropping it
54
+ // keeps the other accounts selectable; failing the parse would not.
55
+ testSync("skips an entry that names no password", () => {
56
+ let users = Seed_Users.parseString(`
57
+ - username: admin
58
+ password: admin
59
+ - username: halfWritten
60
+ groups: [Shopper]
61
+ `)
62
+ expect(users->Array.map(u => u.Seed_Users.username))->Expect.toEqual(["admin"])
63
+ })
64
+
65
+ testSync("yields no accounts for a document that is not a list", () => {
66
+ expect(Seed_Users.parseString("users: {}")->Array.length)->Expect.toBe(0)
67
+ })
68
+
69
+ // The whole point of returning accounts rather than raising: the caller falls
70
+ // back to prompting, and it can only do that if nothing here escapes.
71
+ testSync("yields no accounts for malformed YAML instead of throwing", () => {
72
+ expect(Seed_Users.parseString("- username: [unclosed")->Array.length)->Expect.toBe(0)
73
+ })
74
+ })
75
+
76
+ describe("Seed_Users.load:", () => {
77
+ testSync("is silent about a file that is not there", () => {
78
+ expect(Seed_Users.load(~path="/nonexistent/.reventless/users.yaml"))->Expect.toBe(None)
79
+ })
80
+ })
81
+
82
+ describe("Seed_Users.label:", () => {
83
+ testSync("names the groups an account carries", () => {
84
+ expect(
85
+ Seed_Users.label({username: "admin", password: "x", groups: ["Admin", "Shopper"]}),
86
+ )->Expect.toBe("admin [Admin, Shopper]")
87
+ })
88
+
89
+ testSync("is the bare username when the file records no groups", () => {
90
+ expect(Seed_Users.label({username: "admin", password: "x", groups: []}))->Expect.toBe("admin")
91
+ })
92
+ })
package/package.json CHANGED
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-seed",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.20",
4
4
  "description": "Reusable harness for seeding a Reventless platform through its public GraphQL command API",
5
5
  "license": "Apache-2.0",
6
+ "dependencies": {
7
+ "yaml": "^2.8.3",
8
+ "@reventlessdev/rescript-node": "2.0.0-alpha.10"
9
+ },
6
10
  "devDependencies": {
7
- "rescript": "12.3.0"
11
+ "rescript": "12.3.0",
12
+ "@reventlessdev/rescript-jest": "1.0.0-alpha.10"
8
13
  },
9
14
  "peerDependencies": {
10
15
  "rescript": "12.3.0"
package/rescript.json CHANGED
@@ -12,9 +12,17 @@
12
12
  {
13
13
  "dir": "src",
14
14
  "subdirs": true
15
+ },
16
+ {
17
+ "dir": "tests",
18
+ "subdirs": true,
19
+ "type": "dev"
15
20
  }
16
21
  ],
17
- "dependencies": [],
22
+ "dependencies": [
23
+ "@reventlessdev/rescript-node",
24
+ "@reventlessdev/rescript-jest"
25
+ ],
18
26
  "compiler-flags": [],
19
27
  "suffix": ".res.mjs"
20
28
  }
package/src/Seed.res CHANGED
@@ -10,15 +10,17 @@ module Client = Seed_Client
10
10
  module Runner = Seed_Runner
11
11
  module Upload = Seed_Upload
12
12
  module Prompt = Seed_Prompt
13
+ module Users = Seed_Users
13
14
  module Connect = Seed_Connect
14
15
 
15
16
  exception Failed = Seed_Types.Failed
16
17
 
17
- // A live target a data set seeds against: an authenticated client, the upload
18
- // endpoint (empty when the deployment serves no uploads), and a label.
18
+ // A live target a data set seeds against: an authenticated client, whether uploads
19
+ // are skipped this run, and a label. Uploads mint through the domain API's
20
+ // `Upload_Presign` mutation on the client (route B), so no upload endpoint is carried.
19
21
  type connection = Seed_Connect.connection = {
20
22
  client: Seed_Client.t,
21
- uploadEndpoint: string,
23
+ uploadsSkipped: bool,
22
24
  label: string,
23
25
  }
24
26
 
@@ -27,6 +29,7 @@ type dataSet = Seed_Runner.dataSet = {
27
29
  name: string,
28
30
  label: string,
29
31
  seed: connection => promise<unit>,
32
+ probeViews?: array<string>,
30
33
  }
31
34
 
32
35
  type rec value = Seed_Types.value =
package/src/Seed.res.mjs CHANGED
@@ -12,6 +12,8 @@ let Upload;
12
12
 
13
13
  let Prompt;
14
14
 
15
+ let Users;
16
+
15
17
  let Connect;
16
18
 
17
19
  let Failed = Seed_Types$ReventlessSeed.Failed;
@@ -30,6 +32,7 @@ export {
30
32
  Runner,
31
33
  Upload,
32
34
  Prompt,
35
+ Users,
33
36
  Connect,
34
37
  Failed,
35
38
  mutation,