@antelopejs/dms 0.15.1 → 0.16.0

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 (295) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +36 -26
  3. package/bin/control/base.mjs +9 -4
  4. package/bin/control/browser-client.mjs +24 -5
  5. package/bin/control/browser-wire.mjs +41 -0
  6. package/bin/control/browser-wire.test.mjs +96 -0
  7. package/bin/control/browser.mjs +78 -27
  8. package/bin/control/commands-stack.mjs +58 -8
  9. package/bin/control/commands-verify.mjs +47 -1
  10. package/bin/control/doctor.mjs +49 -4
  11. package/bin/control/failure.mjs +9 -0
  12. package/bin/control/journey.mjs +158 -30
  13. package/bin/control/journey.test.mjs +61 -0
  14. package/bin/control/json.mjs +229 -0
  15. package/bin/control/json.test.mjs +130 -0
  16. package/bin/control/mail.mjs +57 -7
  17. package/bin/control/mail.test.mjs +55 -0
  18. package/bin/control/mongo.mjs +205 -27
  19. package/bin/control/mongo.test.mjs +298 -0
  20. package/bin/control/options.mjs +33 -0
  21. package/bin/control/processes.mjs +24 -2
  22. package/bin/control/renderer.mjs +39 -0
  23. package/bin/control/renderer.test.mjs +324 -0
  24. package/bin/control/run-state.mjs +119 -6
  25. package/bin/control/run-state.test.mjs +171 -0
  26. package/bin/control/session.mjs +30 -14
  27. package/bin/control/stack.mjs +95 -24
  28. package/bin/control/tab.mjs +117 -18
  29. package/bin/control/verify/behaviour.mjs +44 -6
  30. package/bin/control/verify/behaviour.test.mjs +130 -22
  31. package/bin/control/verify/decode.mjs +220 -0
  32. package/bin/control/verify/decode.test.mjs +245 -0
  33. package/bin/control/verify/doors.mjs +1 -1
  34. package/bin/control/verify/doors.test.mjs +40 -0
  35. package/bin/control/verify/emit.mjs +135 -9
  36. package/bin/control/verify/formal.mjs +61 -10
  37. package/bin/control/verify/formal.test.mjs +19 -3
  38. package/bin/control/verify/judgement.mjs +22 -3
  39. package/bin/control/verify/map.mjs +25 -5
  40. package/bin/control/verify/publish.mjs +20 -14
  41. package/bin/control/verify/seeded.mjs +65 -16
  42. package/bin/control/verify/skill.mjs +27 -6
  43. package/bin/control/verify/types.mjs +29 -0
  44. package/bin/control/verify/verdict.mjs +72 -7
  45. package/bin/dms-control.mjs +8 -28
  46. package/checks/api-error-presentation.d.mts +61 -0
  47. package/dist/attachments/access.js +1 -1
  48. package/dist/attachments/access.js.map +1 -1
  49. package/dist/attachments/fields.d.ts +2 -2
  50. package/dist/attachments/fields.js +26 -13
  51. package/dist/attachments/fields.js.map +1 -1
  52. package/dist/attachments/index.d.ts +1 -0
  53. package/dist/attachments/index.js +1 -0
  54. package/dist/attachments/index.js.map +1 -1
  55. package/dist/attachments/read.js.map +1 -1
  56. package/dist/attachments/save.d.ts +1 -1
  57. package/dist/attachments/save.js +49 -17
  58. package/dist/attachments/save.js.map +1 -1
  59. package/dist/attachments/table-view.d.ts +1 -2
  60. package/dist/attachments/table-view.js +23 -13
  61. package/dist/attachments/table-view.js.map +1 -1
  62. package/dist/automation/events.d.ts +10 -10
  63. package/dist/automation/index.js +3 -4
  64. package/dist/automation/index.js.map +1 -1
  65. package/dist/automation/send-notification.d.ts +2 -2
  66. package/dist/automation/send-notification.js +4 -6
  67. package/dist/automation/send-notification.js.map +1 -1
  68. package/dist/crons/cleanup-user-invites.js.map +1 -1
  69. package/dist/crons/sweep-expired-sessions.js.map +1 -1
  70. package/dist/crons/sweep-stale-exports.js.map +1 -1
  71. package/dist/db/guard-row.d.ts +2 -3
  72. package/dist/db/guard-row.js +1 -2
  73. package/dist/db/guard-row.js.map +1 -1
  74. package/dist/db/models/attachments.model.d.ts +7 -5
  75. package/dist/db/models/setupToken.model.d.ts +7 -5
  76. package/dist/db/models/signInAttempts.model.d.ts +7 -5
  77. package/dist/db/models/systemState.model.d.ts +7 -5
  78. package/dist/db/models/userEmailChanges.model.d.ts +7 -5
  79. package/dist/db/models/userKnownDevices.model.d.ts +7 -5
  80. package/dist/db/models/userNotificationPreferences.model.d.ts +7 -5
  81. package/dist/db/models/userNotifications.model.d.ts +9 -6
  82. package/dist/db/models/userNotifications.model.js.map +1 -1
  83. package/dist/dev/module-update-notifications.js.map +1 -1
  84. package/dist/hooks/internal/sign-in-monitor.js +6 -6
  85. package/dist/hooks/internal/sign-in-monitor.js.map +1 -1
  86. package/dist/implementations/dms/page/controller.d.ts +2 -2
  87. package/dist/implementations/dms/page/controller.js +3 -1
  88. package/dist/implementations/dms/page/controller.js.map +1 -1
  89. package/dist/implementations/dms/page/dynamic-menu.js +3 -2
  90. package/dist/implementations/dms/page/dynamic-menu.js.map +1 -1
  91. package/dist/implementations/dms/page/notifications.d.ts +6 -2
  92. package/dist/implementations/dms/page/notifications.js +1 -0
  93. package/dist/implementations/dms/page/notifications.js.map +1 -1
  94. package/dist/implementations/dms/page/preview-pages.js +6 -3
  95. package/dist/implementations/dms/page/preview-pages.js.map +1 -1
  96. package/dist/implementations/dms/page/registry.js +10 -4
  97. package/dist/implementations/dms/page/registry.js.map +1 -1
  98. package/dist/implementations/dms/permissions.js +12 -12
  99. package/dist/implementations/dms/permissions.js.map +1 -1
  100. package/dist/implementations/dms-base/bulk-selection.d.ts +3 -4
  101. package/dist/implementations/dms-base/bulk-selection.js +18 -6
  102. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  103. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  104. package/dist/implementations/dms-base/footer-summary.js +44 -27
  105. package/dist/implementations/dms-base/footer-summary.js.map +1 -1
  106. package/dist/implementations/dms-base/row-rule-operand.d.ts +6 -0
  107. package/dist/implementations/dms-base/row-rule-operand.js +41 -0
  108. package/dist/implementations/dms-base/row-rule-operand.js.map +1 -0
  109. package/dist/implementations/dms-base/search-route.d.ts +9 -13
  110. package/dist/implementations/dms-base/search-route.js +7 -6
  111. package/dist/implementations/dms-base/search-route.js.map +1 -1
  112. package/dist/implementations/dms-base/services.d.ts +1 -0
  113. package/dist/implementations/dms-base/services.js +1 -0
  114. package/dist/implementations/dms-base/services.js.map +1 -1
  115. package/dist/implementations/dms-base/table-view.d.ts +5 -6
  116. package/dist/implementations/dms-base/table-view.js +17 -6
  117. package/dist/implementations/dms-base/table-view.js.map +1 -1
  118. package/dist/implementations/dms-html-render/index.js +16 -6
  119. package/dist/implementations/dms-html-render/index.js.map +1 -1
  120. package/dist/implementations/dms-notifications/delivery-guard.d.ts +3 -2
  121. package/dist/implementations/dms-notifications/delivery-guard.js.map +1 -1
  122. package/dist/implementations/dms-notifications/index.js.map +1 -1
  123. package/dist/index.js.map +1 -1
  124. package/dist/pages/public/preview/block-types.d.ts +1 -2
  125. package/dist/pages/public/preview/block-types.js.map +1 -1
  126. package/dist/pages/settings/overview/account-summary.js +4 -1
  127. package/dist/pages/settings/overview/account-summary.js.map +1 -1
  128. package/dist/pages/settings/users/account-activity-feed.js +3 -2
  129. package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
  130. package/dist/pages/settings/users/account-credentials.d.ts +1 -1
  131. package/dist/pages/settings/users/account-credentials.js +1 -2
  132. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  133. package/dist/pages/settings/users/account-data-store.d.ts +1 -1
  134. package/dist/pages/settings/users/account-data-store.js +1 -3
  135. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  136. package/dist/pages/settings/users/email-change.d.ts +2 -2
  137. package/dist/pages/settings/users/email-change.js +2 -3
  138. package/dist/pages/settings/users/email-change.js.map +1 -1
  139. package/dist/pages/settings/users/invite-extension-routes.d.ts +2 -2
  140. package/dist/pages/settings/users/invite-extension-routes.js +11 -4
  141. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  142. package/dist/pages/settings/users/invites.d.ts +2 -2
  143. package/dist/pages/settings/users/member-invite-batch.d.ts +2 -2
  144. package/dist/pages/settings/users/member-invite-batch.js +7 -3
  145. package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
  146. package/dist/pages/settings/users/member-roles-field.js +1 -4
  147. package/dist/pages/settings/users/member-roles-field.js.map +1 -1
  148. package/dist/pages/settings/users/members.d.ts +2 -3
  149. package/dist/pages/settings/users/members.js.map +1 -1
  150. package/dist/pages/settings/users/notifications.d.ts +2 -3
  151. package/dist/pages/settings/users/notifications.js +2 -1
  152. package/dist/pages/settings/users/notifications.js.map +1 -1
  153. package/dist/pages/settings/users/profile-helpers.d.ts +0 -1
  154. package/dist/pages/settings/users/profile-helpers.js +0 -17
  155. package/dist/pages/settings/users/profile-helpers.js.map +1 -1
  156. package/dist/pages/settings/users/profile.d.ts +3 -4
  157. package/dist/pages/settings/users/profile.js +12 -12
  158. package/dist/pages/settings/users/profile.js.map +1 -1
  159. package/dist/pages/settings/users/region.d.ts +3 -4
  160. package/dist/pages/settings/users/region.js.map +1 -1
  161. package/dist/pages/settings/users/regional-preferences.d.ts +8 -1
  162. package/dist/pages/settings/users/regional-preferences.js +10 -6
  163. package/dist/pages/settings/users/regional-preferences.js.map +1 -1
  164. package/dist/pages/settings/users/role-preview.js +3 -2
  165. package/dist/pages/settings/users/role-preview.js.map +1 -1
  166. package/dist/pages/settings/users/roles.d.ts +4 -5
  167. package/dist/pages/settings/users/roles.js.map +1 -1
  168. package/dist/pages/settings/users/security.d.ts +9 -12
  169. package/dist/pages/settings/users/security.js +11 -9
  170. package/dist/pages/settings/users/security.js.map +1 -1
  171. package/dist/pages/settings/users/two-factor-operations.d.ts +7 -7
  172. package/dist/pages/settings/users/two-factor-operations.js +5 -7
  173. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  174. package/dist/realtime/broker.js.map +1 -1
  175. package/dist/realtime/heartbeat.js.map +1 -1
  176. package/dist/realtime/redis-broker.js.map +1 -1
  177. package/dist/realtime/sessions.js.map +1 -1
  178. package/dist/realtime/sse.js +1 -2
  179. package/dist/realtime/sse.js.map +1 -1
  180. package/dist/routes/auth/forgot-password.d.ts +1 -1
  181. package/dist/routes/auth/forgot-password.js +1 -3
  182. package/dist/routes/auth/forgot-password.js.map +1 -1
  183. package/dist/routes/auth/login.d.ts +1 -1
  184. package/dist/routes/auth/login.js +1 -2
  185. package/dist/routes/auth/login.js.map +1 -1
  186. package/dist/routes/auth/logout.d.ts +1 -1
  187. package/dist/routes/auth/logout.js +1 -2
  188. package/dist/routes/auth/logout.js.map +1 -1
  189. package/dist/routes/auth/oauth/providers.js +30 -6
  190. package/dist/routes/auth/oauth/providers.js.map +1 -1
  191. package/dist/routes/auth/oauth/state.js +8 -5
  192. package/dist/routes/auth/oauth/state.js.map +1 -1
  193. package/dist/routes/auth/oauth/token-exchange.js +8 -1
  194. package/dist/routes/auth/oauth/token-exchange.js.map +1 -1
  195. package/dist/routes/auth/refresh.d.ts +1 -1
  196. package/dist/routes/auth/refresh.js +1 -2
  197. package/dist/routes/auth/refresh.js.map +1 -1
  198. package/dist/routes/auth/request-2fa-email.d.ts +1 -1
  199. package/dist/routes/auth/request-2fa-email.js +1 -2
  200. package/dist/routes/auth/request-2fa-email.js.map +1 -1
  201. package/dist/routes/auth/reset-password.d.ts +1 -1
  202. package/dist/routes/auth/reset-password.js +1 -3
  203. package/dist/routes/auth/reset-password.js.map +1 -1
  204. package/dist/routes/auth/session-handoff.d.ts +1 -1
  205. package/dist/routes/auth/session-handoff.js +1 -2
  206. package/dist/routes/auth/session-handoff.js.map +1 -1
  207. package/dist/routes/auth/signup.d.ts +1 -1
  208. package/dist/routes/auth/signup.js +1 -2
  209. package/dist/routes/auth/signup.js.map +1 -1
  210. package/dist/routes/auth/switch-tenant.d.ts +2 -3
  211. package/dist/routes/auth/switch-tenant.js +3 -2
  212. package/dist/routes/auth/switch-tenant.js.map +1 -1
  213. package/dist/routes/auth/validate-forgot-password-token.d.ts +1 -1
  214. package/dist/routes/auth/validate-forgot-password-token.js +1 -3
  215. package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
  216. package/dist/routes/auth/validate-invite-token.d.ts +1 -1
  217. package/dist/routes/auth/validate-invite-token.js +1 -3
  218. package/dist/routes/auth/validate-invite-token.js.map +1 -1
  219. package/dist/routes/auth/verify-2fa.d.ts +1 -1
  220. package/dist/routes/auth/verify-2fa.js +1 -2
  221. package/dist/routes/auth/verify-2fa.js.map +1 -1
  222. package/dist/routes/auth/verify-email.d.ts +1 -1
  223. package/dist/routes/auth/verify-email.js +1 -2
  224. package/dist/routes/auth/verify-email.js.map +1 -1
  225. package/dist/routes/auth.d.ts +14 -15
  226. package/dist/routes/auth.js +15 -13
  227. package/dist/routes/auth.js.map +1 -1
  228. package/dist/routes/files.d.ts +1 -2
  229. package/dist/routes/files.js.map +1 -1
  230. package/dist/routes/onboarding.d.ts +1 -2
  231. package/dist/routes/onboarding.js.map +1 -1
  232. package/dist/routes/realtime.js +5 -6
  233. package/dist/routes/realtime.js.map +1 -1
  234. package/dist/routes/store.d.ts +6 -7
  235. package/dist/routes/store.js +19 -13
  236. package/dist/routes/store.js.map +1 -1
  237. package/dist/store/catalog.d.ts +13 -5
  238. package/dist/store/catalog.js +19 -9
  239. package/dist/store/catalog.js.map +1 -1
  240. package/dist/store/compatibility.d.ts +1 -0
  241. package/dist/store/compatibility.js +24 -8
  242. package/dist/store/compatibility.js.map +1 -1
  243. package/dist/store/frontend-compatibility.js +15 -3
  244. package/dist/store/frontend-compatibility.js.map +1 -1
  245. package/dist/store/interfaces.js +35 -13
  246. package/dist/store/interfaces.js.map +1 -1
  247. package/dist/store/internal/frontend/frontend-modules.js +7 -3
  248. package/dist/store/internal/frontend/frontend-modules.js.map +1 -1
  249. package/dist/store/internal/frontend/frontend-ready.d.ts +2 -1
  250. package/dist/store/internal/frontend/frontend-ready.js +6 -4
  251. package/dist/store/internal/frontend/frontend-ready.js.map +1 -1
  252. package/dist/store/internal/frontend/manifest.js +6 -7
  253. package/dist/store/internal/frontend/manifest.js.map +1 -1
  254. package/dist/store/operations.js.map +1 -1
  255. package/dist/store/registry.js.map +1 -1
  256. package/dist/store/runtime.d.ts +13 -0
  257. package/dist/store/runtime.js +9 -1
  258. package/dist/store/runtime.js.map +1 -1
  259. package/dist/utils/admin-invite-email.js +3 -1
  260. package/dist/utils/admin-invite-email.js.map +1 -1
  261. package/dist/utils/email-subjects.js +3 -1
  262. package/dist/utils/email-subjects.js.map +1 -1
  263. package/dist/utils/export-jobs-run.js.map +1 -1
  264. package/dist/utils/export-jobs.js +3 -9
  265. package/dist/utils/export-jobs.js.map +1 -1
  266. package/dist/utils/index.d.ts +1 -0
  267. package/dist/utils/index.js +1 -0
  268. package/dist/utils/index.js.map +1 -1
  269. package/dist/utils/own-value.d.ts +1 -0
  270. package/dist/utils/own-value.js +7 -0
  271. package/dist/utils/own-value.js.map +1 -0
  272. package/dist/utils/row-action-rule-evaluator.js +31 -35
  273. package/dist/utils/row-action-rule-evaluator.js.map +1 -1
  274. package/dist/utils/sign-in-country.js +4 -2
  275. package/dist/utils/sign-in-country.js.map +1 -1
  276. package/dist/utils/tenant-export-archive.js +9 -14
  277. package/dist/utils/tenant-export-archive.js.map +1 -1
  278. package/dist/utils/user-agent.js +2 -1
  279. package/dist/utils/user-agent.js.map +1 -1
  280. package/dist/validation/auth.schema.d.ts +10 -10
  281. package/dist/validation/regional-preferences.schema.d.ts +2 -2
  282. package/dist/validation/role-editor.schema.d.ts +1 -1
  283. package/frontend-vue/build/backend-boundary.ts +49 -18
  284. package/frontend-vue/dms.frontend.ts +8 -12
  285. package/frontend-vue/layers/dms-auth/server/accounts.ts +44 -3
  286. package/frontend-vue/layers/dms-auth/server/actions.ts +17 -4
  287. package/frontend-vue/layers/dms-auth/server/oauth.ts +42 -4
  288. package/frontend-vue/layers/dms-core/app/types/watch.ts +1 -2
  289. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +57 -6
  290. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +7 -9
  291. package/frontend-vue/layers/dms-ui/app/entries/composables/form/registerFormFunctions.ts +1 -0
  292. package/frontend-vue/lazy-plugin.ts +18 -0
  293. package/frontend-vue/package.json +2 -2
  294. package/package.json +19 -13
  295. package/skills/dms-project/SKILL.md +4 -3
@@ -1,6 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { firstString, stringField } from "./json.mjs";
4
5
  import { saveRun } from "./run-state.mjs";
5
6
 
6
7
  const REQUEST_TIMEOUT_MS = 30_000;
@@ -9,11 +10,15 @@ const PASSWORD_BYTES = 9;
9
10
  const OK_MIN = 200;
10
11
  const OK_MAX = 299;
11
12
 
13
+ /**
14
+ * @typedef {{ status: number | null, body: unknown, error: string | null }} ApiAnswer
15
+ */
16
+
12
17
  /**
13
18
  * The credentials of the run's administrator, derived from the run id so a
14
19
  * journey can name them before onboarding.
15
20
  *
16
- * @param {{ id: string }} state The run
21
+ * @param {Pick<import("./run-state.mjs").RunState, "id" | "adminPassword">} state The run
17
22
  * @returns {{ email: string, password: string, firstName: string, lastName: string }} The administrator's credentials
18
23
  */
19
24
  export function adminCredentials(state) {
@@ -35,6 +40,10 @@ export function newPassword() {
35
40
  return `Vr1!${randomBytes(PASSWORD_BYTES).toString("base64url").replace(/[-_]/g, "x")}`;
36
41
  }
37
42
 
43
+ /**
44
+ * @param {string} text The text
45
+ * @returns {string} The text as a folder name
46
+ */
38
47
  function slug(text) {
39
48
  return text
40
49
  .toLowerCase()
@@ -63,9 +72,15 @@ export function nextFolder(state, group, label) {
63
72
  return folder;
64
73
  }
65
74
 
75
+ /**
76
+ * @param {string} text The response text
77
+ * @returns {unknown} The parsed JSON, or the text when it is not JSON
78
+ */
66
79
  function parseBody(text) {
67
80
  try {
68
- return JSON.parse(text);
81
+ /** @type {unknown} */
82
+ const parsed = JSON.parse(text);
83
+ return parsed;
69
84
  } catch {
70
85
  return text;
71
86
  }
@@ -76,8 +91,8 @@ function parseBody(text) {
76
91
  * response.json into `folder`. Secrets in the request are left out.
77
92
  *
78
93
  * @param {string} folder Where the evidence goes
79
- * @param {{ url: string, method: string, body?: any, token?: string }} request What to send
80
- * @returns {Promise<{ status: number | null, body: any, error: string | null }>} The answer
94
+ * @param {{ url: string, method: string, body?: unknown, token?: string }} request What to send
95
+ * @returns {Promise<ApiAnswer>} The answer
81
96
  */
82
97
  export async function recordedRequest(folder, request) {
83
98
  const headers = { accept: "application/json" };
@@ -127,9 +142,9 @@ export function isOk(answer) {
127
142
  * Signs an account in through `POST /api/auth/login` and keeps its tokens in
128
143
  * the run state.
129
144
  *
130
- * @param {Record<string, any>} state The run
145
+ * @param {import("./run-state.mjs").RunState} state The run
131
146
  * @param {string} account The account name, `admin` for the run's administrator
132
- * @returns {Promise<{ status: number | null, body: any }>} The login answer
147
+ * @returns {Promise<ApiAnswer>} The login answer
133
148
  */
134
149
  export async function signIn(state, account) {
135
150
  const credentials = state.accounts?.[account];
@@ -144,8 +159,9 @@ export async function signIn(state, account) {
144
159
  method: "POST",
145
160
  body: { email: credentials.email, password: credentials.password },
146
161
  });
147
- if (isOk(answer) && answer.body?.access_token) {
148
- state.tokens = { ...state.tokens, [account]: answer.body.access_token };
162
+ const token = stringField(answer.body, "access_token");
163
+ if (isOk(answer) && token) {
164
+ state.tokens = { ...state.tokens, [account]: token };
149
165
  saveRun(state);
150
166
  }
151
167
  return answer;
@@ -155,9 +171,9 @@ export async function signIn(state, account) {
155
171
  * Creates the run's administrator through `POST /api/onboarding/register`,
156
172
  * then signs it in.
157
173
  *
158
- * @param {Record<string, any>} state The run
174
+ * @param {import("./run-state.mjs").RunState} state The run
159
175
  * @param {{ platformName?: string }} options Wizard details
160
- * @returns {Promise<{ register: { status: number | null, body: any }, login: { status: number | null, body: any } | null }>} Both answers
176
+ * @returns {Promise<{ register: ApiAnswer, login: ApiAnswer | null }>} Both answers
161
177
  */
162
178
  export async function onboard(state, options) {
163
179
  const admin = adminCredentials(state);
@@ -179,7 +195,7 @@ export async function onboard(state, options) {
179
195
  admin: {
180
196
  email: admin.email,
181
197
  password: admin.password,
182
- userId: register.body?.[0] ?? null,
198
+ userId: firstString(register.body) ?? null,
183
199
  },
184
200
  };
185
201
  saveRun(state);
@@ -190,9 +206,9 @@ export async function onboard(state, options) {
190
206
  * An authenticated (or anonymous) request to the run's backend, signing the
191
207
  * account in again once when its token was refused.
192
208
  *
193
- * @param {Record<string, any>} state The run
194
- * @param {{ method: string, path: string, body?: any, as: string, label?: string, folder?: () => string }} request What to send and as whom; `as` is an account name or `anon`; `folder` makes each attempt's evidence folder
195
- * @returns {Promise<{ status: number | null, body: any, error: string | null, folder: string }>} The answer and its evidence folder
209
+ * @param {import("./run-state.mjs").RunState} state The run
210
+ * @param {{ method: string, path: string, body?: unknown, as: string, label?: string, folder?: () => string }} request What to send and as whom; `as` is an account name or `anon`; `folder` makes each attempt's evidence folder
211
+ * @returns {Promise<ApiAnswer & { folder: string }>} The answer and its evidence folder
196
212
  */
197
213
  export async function apiRequest(state, request) {
198
214
  const label = request.label ?? `${request.method} ${request.path}`;
@@ -11,7 +11,9 @@ import {
11
11
  import { createRequire } from "node:module";
12
12
  import { homedir } from "node:os";
13
13
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
14
+ import { describeFailure } from "./failure.mjs";
14
15
  import { checkoutOf } from "./git.mjs";
16
+ import { stringField } from "./json.mjs";
15
17
  import { dropDatabase, dropRunDatabases, startMongod } from "./mongo.mjs";
16
18
  import {
17
19
  freePort,
@@ -29,8 +31,14 @@ import {
29
31
  runsRoot,
30
32
  saveRun,
31
33
  } from "./run-state.mjs";
34
+ import { RENDERER_CLI, resolveRenderer } from "./renderer.mjs";
32
35
  import { newPassword } from "./session.mjs";
33
36
 
37
+ /**
38
+ * @typedef {import("./run-state.mjs").RunState} RunState
39
+ * @typedef {{ playground: string, renderer?: string, runs?: string, mongoUrl?: string, env?: string[], base?: string, skill?: string }} UpOptions Where the playground and the runs are
40
+ */
41
+
34
42
  const READY_TIMEOUT_MS = 180_000;
35
43
  const SECRET_BYTES = 32;
36
44
  const MAIL_PORT_ATTEMPTS = 3;
@@ -67,23 +75,30 @@ export function resolutionFolders(state) {
67
75
  }
68
76
 
69
77
  /**
70
- * The `ajs` and `ajs dms` entry points the playground installs.
78
+ * The `ajs` and `ajs dms` entry points the playground installs, `ajs dms`
79
+ * being the renderer checkout's when the run serves one.
71
80
  *
72
81
  * @param {string} playground The playground folder
82
+ * @param {string} [renderer] The renderer checkout the run serves
73
83
  * @returns {{ ajs: string, dms: string }} Their scripts
74
84
  */
75
- export function cliPaths(playground) {
85
+ export function cliPaths(playground, renderer) {
76
86
  const require = createRequire(join(playground, "package.json"));
77
87
  const core = dirname(require.resolve("@antelopejs/core/package.json"));
78
- const frontend = dirname(
79
- require.resolve("@antelopejs/dms-frontend/package.json"),
80
- );
88
+ const frontend =
89
+ renderer ??
90
+ dirname(require.resolve("@antelopejs/dms-frontend/package.json"));
81
91
  return {
82
92
  ajs: join(core, "dist", "core", "cli", "index.js"),
83
- dms: join(frontend, "dist", "index.js"),
93
+ dms: join(frontend, RENDERER_CLI),
84
94
  };
85
95
  }
86
96
 
97
+ /**
98
+ * @param {string} real
99
+ * @param {string} shadow
100
+ * @param {string} keep
101
+ */
87
102
  function shadowLevel(real, shadow, keep) {
88
103
  mkdirSync(shadow, { recursive: true });
89
104
  for (const entry of readdirSync(real)) {
@@ -92,19 +107,40 @@ function shadowLevel(real, shadow, keep) {
92
107
  }
93
108
  }
94
109
 
110
+ /**
111
+ * A `node_modules` that is `modules` with `@antelopejs/dms-frontend` replaced
112
+ * by the renderer checkout, every other entry linked to the original.
113
+ *
114
+ * @param {string} modules The playground's installed modules
115
+ * @param {string} shadow The run's `node_modules`
116
+ * @param {string} renderer The renderer checkout
117
+ */
118
+ export function linkRenderer(modules, shadow, renderer) {
119
+ const scope = "@antelopejs";
120
+ shadowLevel(modules, shadow, scope);
121
+ const scoped = join(modules, scope);
122
+ const shadowScope = join(shadow, scope);
123
+ if (existsSync(scoped)) shadowLevel(scoped, shadowScope, "dms-frontend");
124
+ else mkdirSync(shadowScope);
125
+ symlinkSync(renderer, join(shadowScope, "dms-frontend"));
126
+ }
127
+
95
128
  /**
96
129
  * Lays out the run's copy of the checkout: from the checkout root down to
97
130
  * the playground, every sibling is linked, so relative paths such as a
98
131
  * tsconfig `extends` resolve as in the checkout. The playground itself is
99
132
  * copied without its installed modules and build, so `.antelope/` and the
100
- * frontend build belong to the run.
133
+ * frontend build belong to the run. Its `node_modules` is the playground's,
134
+ * except that `@antelopejs/dms-frontend` is the `renderer` checkout when
135
+ * there is one.
101
136
  *
102
137
  * @param {string} playground The playground folder
103
138
  * @param {string} checkout The checkout root, or the module folder outside one
104
139
  * @param {string} root The run's copy of the checkout
140
+ * @param {string} [renderer] The renderer checkout the run serves
105
141
  * @returns {string} The run's playground folder
106
142
  */
107
- export function layOutCopy(playground, checkout, root) {
143
+ export function layOutCopy(playground, checkout, root, renderer) {
108
144
  const segments = relative(checkout, playground).split(sep);
109
145
  let real = checkout;
110
146
  let shadow = root;
@@ -117,7 +153,9 @@ export function layOutCopy(playground, checkout, root) {
117
153
  recursive: true,
118
154
  filter: (source) => !COPY_SKIPPED.has(basename(source)),
119
155
  });
120
- symlinkSync(join(playground, "node_modules"), join(shadow, "node_modules"));
156
+ const modules = join(playground, "node_modules");
157
+ if (renderer) linkRenderer(modules, join(shadow, "node_modules"), renderer);
158
+ else symlinkSync(modules, join(shadow, "node_modules"));
121
159
  return shadow;
122
160
  }
123
161
 
@@ -134,7 +172,7 @@ function inheritedEnv() {
134
172
  * database, origin and secrets, an npm cache and a pnpm store shared by
135
173
  * every run, no update checks and no colors. Extra `KEY=VALUE` pairs from `--env` come last.
136
174
  *
137
- * @param {Record<string, any>} state The run
175
+ * @param {import("./run-state.mjs").RunState} state The run
138
176
  * @returns {NodeJS.ProcessEnv} The environment
139
177
  */
140
178
  export function runEnv(state) {
@@ -163,6 +201,10 @@ export function runEnv(state) {
163
201
  };
164
202
  }
165
203
 
204
+ /**
205
+ * @param {string[]} pairs
206
+ * @returns {Record<string, string>}
207
+ */
166
208
  function parseExtraEnv(pairs) {
167
209
  return Object.fromEntries(
168
210
  pairs.map((pair) => {
@@ -173,6 +215,10 @@ function parseExtraEnv(pairs) {
173
215
  );
174
216
  }
175
217
 
218
+ /**
219
+ * @param {UpOptions} options
220
+ * @returns {Promise<RunState>}
221
+ */
176
222
  async function initialState(options) {
177
223
  const playground = resolve(options.playground);
178
224
  if (!existsSync(join(playground, "antelope.config.ts"))) {
@@ -184,7 +230,9 @@ async function initialState(options) {
184
230
  const webPort = await freePort();
185
231
  const smtpPort = await freePort();
186
232
  const checkout = checkoutOf(playground);
187
- return {
233
+ const renderer = options.renderer && resolveRenderer(options.renderer);
234
+ const verified = renderer ? checkoutOf(renderer) : checkout;
235
+ const state = {
188
236
  ...paths,
189
237
  id,
190
238
  createdAt: new Date().toISOString(),
@@ -192,7 +240,7 @@ async function initialState(options) {
192
240
  playground,
193
241
  moduleRoot: dirname(playground),
194
242
  repo: checkout.root,
195
- headSha: checkout.head,
243
+ headSha: verified.head,
196
244
  baseRef: options.base ?? "origin/main",
197
245
  skill: options.skill ?? null,
198
246
  apiPort,
@@ -218,13 +266,15 @@ async function initialState(options) {
218
266
  tokens: {},
219
267
  ready: false,
220
268
  };
269
+ if (renderer) state.renderer = renderer;
270
+ return state;
221
271
  }
222
272
 
223
273
  /**
224
274
  * How to run one of the playground's CLIs for the run: its own log, the
225
275
  * run's environment, from the run's playground copy or `cwd`.
226
276
  *
227
- * @param {Record<string, any>} state The run
277
+ * @param {import("./run-state.mjs").RunState} state The run
228
278
  * @param {string} name The log name
229
279
  * @param {string} script The CLI script
230
280
  * @param {string[]} args Its arguments
@@ -242,6 +292,10 @@ export function spec(state, name, script, args, cwd = state.runPlayground) {
242
292
  };
243
293
  }
244
294
 
295
+ /**
296
+ * @param {RunState} state
297
+ * @param {import("./run-state.mjs").ProcessRecord} processRecord
298
+ */
245
299
  function record(state, processRecord) {
246
300
  state.processes = [
247
301
  ...state.processes.filter((entry) => entry.name !== processRecord.name),
@@ -254,7 +308,7 @@ function record(state, processRecord) {
254
308
  * Starts the run's mail sink unless it already runs, and waits until it is
255
309
  * the process listening on the run's mail port.
256
310
  *
257
- * @param {Record<string, any>} state The run
311
+ * @param {import("./run-state.mjs").RunState} state The run
258
312
  * @returns {Promise<void>} Resolves once it listens
259
313
  */
260
314
  export async function ensureMail(state) {
@@ -272,6 +326,10 @@ export async function ensureMail(state) {
272
326
  await waitForOwnPort(state.smtpPort, mail, READY_TIMEOUT_MS);
273
327
  }
274
328
 
329
+ /**
330
+ * @param {RunState} state
331
+ * @returns {Promise<void>}
332
+ */
275
333
  async function startFirstMail(state) {
276
334
  for (let attempt = 1; ; attempt += 1) {
277
335
  try {
@@ -284,6 +342,11 @@ async function startFirstMail(state) {
284
342
  }
285
343
  }
286
344
 
345
+ /**
346
+ * @param {RunState} state
347
+ * @param {{ playground?: string, cwd?: string }} [build]
348
+ * @returns {Promise<void>}
349
+ */
287
350
  async function startBackend(state, build) {
288
351
  const { ajs } = cliPaths(build?.playground ?? state.playground);
289
352
  const backend = startDetached(
@@ -303,12 +366,17 @@ async function startBackend(state, build) {
303
366
  );
304
367
  }
305
368
 
369
+ /**
370
+ * @param {RunState} state
371
+ * @returns {Promise<void>}
372
+ */
306
373
  async function buildAndStart(state) {
307
- const clis = cliPaths(state.playground);
374
+ const clis = cliPaths(state.playground, state.renderer);
308
375
  state.runPlayground = layOutCopy(
309
376
  state.playground,
310
377
  state.repo ?? state.moduleRoot,
311
378
  state.root,
379
+ state.renderer,
312
380
  );
313
381
  if (state.ownsMongo) {
314
382
  const mongo = await startMongod(state, resolutionFolders(state));
@@ -346,12 +414,13 @@ async function buildAndStart(state) {
346
414
  /**
347
415
  * Starts an isolated DMS from a playground: a copy of it in a run folder,
348
416
  * free ports, the `docs` environment, a `verify_<run>` database on a mongod
349
- * the run owns (or `--mongo-url`), fresh secrets and a mail sink. Ready once
417
+ * the run owns (or `--mongo-url`), fresh secrets and a mail sink. With
418
+ * `renderer`, the dashboard is that built dms-frontend checkout. Ready once
350
419
  * `/api/onboarding/informations` and `/dms-preview` answer 200. On failure
351
420
  * it stops what it started and keeps the logs.
352
421
  *
353
- * @param {{ playground: string, runs?: string, mongoUrl?: string, env?: string[], base?: string, skill?: string }} options Where the playground and the runs are
354
- * @returns {Promise<Record<string, any>>} The ready run
422
+ * @param {UpOptions} options Where the playground and the runs are
423
+ * @returns {Promise<import("./run-state.mjs").RunState>} The ready run
355
424
  */
356
425
  export async function up(options) {
357
426
  const state = await initialState(options);
@@ -374,7 +443,7 @@ export async function up(options) {
374
443
  * a crash would, from the run's build or another one (`build`, the base
375
444
  * version's). The frontend keeps serving and proxies to the same port.
376
445
  *
377
- * @param {Record<string, any>} state The run
446
+ * @param {import("./run-state.mjs").RunState} state The run
378
447
  * @param {{ playground: string, cwd: string }} [build] Another build to start
379
448
  * @returns {Promise<void>} Resolves once the backend answers again
380
449
  */
@@ -390,7 +459,7 @@ export async function restartBackend(state, build) {
390
459
  * journey database is dropped, so a long run does not exhaust mongod's
391
460
  * file handles; its evidence stays.
392
461
  *
393
- * @param {Record<string, any>} state The run
462
+ * @param {import("./run-state.mjs").RunState} state The run
394
463
  * @param {string} suffix What tells the database apart, such as `j03`
395
464
  * @returns {Promise<string>} The database now in use
396
465
  */
@@ -412,7 +481,7 @@ export async function freshDatabase(state, suffix) {
412
481
  * run's databases, and removes everything but `evidence/`, `logs/` and
413
482
  * `run.json`, from which the secrets and passwords are removed.
414
483
  *
415
- * @param {Record<string, any>} state The run
484
+ * @param {import("./run-state.mjs").RunState} state The run
416
485
  * @returns {Promise<string[]>} One line per action
417
486
  */
418
487
  export async function down(state) {
@@ -429,8 +498,8 @@ export async function down(state) {
429
498
  state.mongoUrl,
430
499
  state.databasePrefix,
431
500
  resolutionFolders(state),
432
- ).catch((error) => [
433
- `could not drop the run's databases: ${error.message}`,
501
+ ).catch((/** @type {unknown} */ error) => [
502
+ `could not drop the run's databases: ${describeFailure(error)}`,
434
503
  ]);
435
504
  lines.push(...dropped);
436
505
  }
@@ -474,7 +543,9 @@ export async function down(state) {
474
543
  */
475
544
  export function packageVersion(file) {
476
545
  try {
477
- return JSON.parse(readFileSync(file, "utf8")).version ?? "missing";
546
+ /** @type {unknown} */
547
+ const manifest = JSON.parse(readFileSync(file, "utf8"));
548
+ return stringField(manifest, "version") ?? "missing";
478
549
  } catch {
479
550
  return "missing";
480
551
  }
@@ -5,10 +5,22 @@ import {
5
5
  signInThroughForm,
6
6
  } from "./browser.mjs";
7
7
 
8
+ /**
9
+ * @typedef {import("./browser.mjs").Action} Action
10
+ * @typedef {import("./browser.mjs").Outcome} Outcome
11
+ * @typedef {import("./browser.mjs").Target} Target
12
+ * @typedef {import("./journey.mjs").Check} Check
13
+ * @typedef {{ timeoutMs?: number }} Timeout
14
+ */
15
+
8
16
  const EXPECT_TIMEOUT_MS = 15_000;
9
17
  const POLL_MS = 250;
10
18
  const DETAIL_LENGTH = 300;
11
19
 
20
+ /**
21
+ * @param {string} text The text
22
+ * @returns {string} The text on one line, cut to the detail length
23
+ */
12
24
  function flatten(text) {
13
25
  const flat = text.replace(/\s+/g, " ").trim();
14
26
  return flat.length > DETAIL_LENGTH
@@ -25,7 +37,7 @@ function flatten(text) {
25
37
  export class Tab {
26
38
  /**
27
39
  * @param {import("./journey.mjs").JourneyContext} context The journey
28
- * @param {any} page The Playwright page
40
+ * @param {import("playwright-core").Page} page The Playwright page
29
41
  * @param {string} label The tab's name in evidence
30
42
  */
31
43
  constructor(context, page, label) {
@@ -37,9 +49,9 @@ export class Tab {
37
49
  /**
38
50
  * Runs one browser action and records it as a critical step.
39
51
  *
40
- * @param {Record<string, any>} action The action, as `perform` takes it
52
+ * @param {Omit<Action, "baseUrl">} action The action, as `perform` takes it
41
53
  * @param {string} label What the step does
42
- * @returns {Promise<{ ok: boolean, url: string, error: string | null, aria: string, folder: string }>} The outcome
54
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
43
55
  */
44
56
  async act(action, label) {
45
57
  const folder = this.context.step(`${this.label} ${label}`);
@@ -56,22 +68,44 @@ export class Tab {
56
68
  return { ...outcome, folder };
57
69
  }
58
70
 
59
- /** Opens a path of the frontend. */
71
+ /**
72
+ * Opens a path of the frontend. Callers pass link lookups that are
73
+ * `undefined` when no link matched; the navigation then targets the
74
+ * literal path `undefined`.
75
+ *
76
+ * @param {string | undefined} path The path
77
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
78
+ */
60
79
  goto(path) {
61
80
  return this.act({ do: "goto", path }, `open ${path}`);
62
81
  }
63
82
 
64
- /** Reloads the page. */
83
+ /**
84
+ * Reloads the page.
85
+ *
86
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
87
+ */
65
88
  reload() {
66
89
  return this.act({ do: "reload" }, "reload");
67
90
  }
68
91
 
69
- /** Clicks a target found by role and name. */
92
+ /**
93
+ * Clicks a target found by role and name.
94
+ *
95
+ * @param {Target} target What to click
96
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
97
+ */
70
98
  click(target) {
71
99
  return this.act({ do: "click", target }, `click ${describeTarget(target)}`);
72
100
  }
73
101
 
74
- /** Types into a target found by role and name, replacing its value. */
102
+ /**
103
+ * Types into a target found by role and name, replacing its value.
104
+ *
105
+ * @param {Target} target What to fill
106
+ * @param {string} value The new value
107
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
108
+ */
75
109
  fill(target, value) {
76
110
  return this.act(
77
111
  { do: "fill", target, value },
@@ -79,7 +113,13 @@ export class Tab {
79
113
  );
80
114
  }
81
115
 
82
- /** Chooses an option of a native select by its label. */
116
+ /**
117
+ * Chooses an option of a native select by its label.
118
+ *
119
+ * @param {Target} target The select
120
+ * @param {string} value The option label
121
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
122
+ */
83
123
  select(target, value) {
84
124
  return this.act(
85
125
  { do: "select", target, value },
@@ -87,23 +127,44 @@ export class Tab {
87
127
  );
88
128
  }
89
129
 
90
- /** Ticks a checkbox or switch. */
130
+ /**
131
+ * Ticks a checkbox or switch.
132
+ *
133
+ * @param {Target} target The checkbox or switch
134
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
135
+ */
91
136
  check(target) {
92
137
  return this.act({ do: "check", target }, `check ${describeTarget(target)}`);
93
138
  }
94
139
 
95
- /** Presses a key, on a target or on the page. */
140
+ /**
141
+ * Presses a key, on a target or on the page.
142
+ *
143
+ * @param {string} key The key
144
+ * @param {Target} [target] Where to press it
145
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
146
+ */
96
147
  press(key, target) {
97
148
  const where = target ? ` in ${describeTarget(target)}` : "";
98
149
  return this.act({ do: "press", key, target }, `press ${key}${where}`);
99
150
  }
100
151
 
101
- /** Records the page as it is now. */
152
+ /**
153
+ * Records the page as it is now.
154
+ *
155
+ * @param {string} label What the step records
156
+ * @returns {Promise<Outcome & { folder: string }>} The outcome
157
+ */
102
158
  snapshot(label) {
103
159
  return this.act({ do: "snapshot" }, label);
104
160
  }
105
161
 
106
- /** Signs an account of the journey in through the sign-in form. */
162
+ /**
163
+ * Signs an account of the journey in through the sign-in form.
164
+ *
165
+ * @param {string} account The account name
166
+ * @returns {Promise<Outcome | { ok: boolean, url: string, error: string }>} The outcome of the last sign-in step, or a failure without a snapshot when the journey has no such account
167
+ */
107
168
  async signIn(account) {
108
169
  const credentials = this.context.state.accounts?.[account];
109
170
  const outcome = credentials
@@ -127,7 +188,12 @@ export class Tab {
127
188
  return outcome;
128
189
  }
129
190
 
130
- /** The Playwright locator of a target. */
191
+ /**
192
+ * The Playwright locator of a target.
193
+ *
194
+ * @param {Target} target What to find
195
+ * @returns {import("playwright-core").Locator} The locator
196
+ */
131
197
  locator(target) {
132
198
  return locate(this.page, target);
133
199
  }
@@ -135,6 +201,10 @@ export class Tab {
135
201
  /**
136
202
  * A check that a target becomes visible, or with `{ hidden: true }`
137
203
  * hidden or absent, within the timeout.
204
+ *
205
+ * @param {Target} target What to wait for
206
+ * @param {Timeout & { hidden?: boolean }} [options] Whether to wait for it to be hidden, and how long
207
+ * @returns {Promise<Check>} The check
138
208
  */
139
209
  async visible(target, options = {}) {
140
210
  const state = options.hidden ? "hidden" : "visible";
@@ -152,7 +222,13 @@ export class Tab {
152
222
  };
153
223
  }
154
224
 
155
- /** A check that a target is enabled (or, with `{ disabled: true }`, disabled). */
225
+ /**
226
+ * A check that a target is enabled (or, with `{ disabled: true }`, disabled).
227
+ *
228
+ * @param {Target} target The control
229
+ * @param {Timeout & { disabled?: boolean }} [options] Whether to expect it disabled, and how long
230
+ * @returns {Promise<Check>} The check
231
+ */
156
232
  async enabled(target, options = {}) {
157
233
  const element = this.locator(target).first();
158
234
  const disabled = await element
@@ -166,7 +242,13 @@ export class Tab {
166
242
  };
167
243
  }
168
244
 
169
- /** A check that the page shows (or, with `{ not: true }`, never shows) text matching `pattern`. */
245
+ /**
246
+ * A check that the page shows (or, with `{ not: true }`, never shows) text matching `pattern`.
247
+ *
248
+ * @param {RegExp} pattern The text to match
249
+ * @param {Timeout & { not?: boolean }} [options] Whether the text must never show, and how long
250
+ * @returns {Promise<Check>} The check
251
+ */
170
252
  async shows(pattern, options = {}) {
171
253
  const deadline = Date.now() + (options.timeoutMs ?? EXPECT_TIMEOUT_MS);
172
254
  let text = "";
@@ -185,7 +267,13 @@ export class Tab {
185
267
  };
186
268
  }
187
269
 
188
- /** A check that the current path matches `pattern` within the timeout. */
270
+ /**
271
+ * A check that the current path matches `pattern` within the timeout.
272
+ *
273
+ * @param {RegExp} pattern The path to match
274
+ * @param {Timeout} [options] How long to wait
275
+ * @returns {Promise<Check>} The check
276
+ */
189
277
  async at(pattern, options = {}) {
190
278
  const reached = await this.page
191
279
  .waitForURL((url) => pattern.test(url.pathname), {
@@ -202,7 +290,13 @@ export class Tab {
202
290
  };
203
291
  }
204
292
 
205
- /** A check on how many targets match. */
293
+ /**
294
+ * A check on how many targets match.
295
+ *
296
+ * @param {Target} target What to count
297
+ * @param {number} expected How many should match
298
+ * @returns {Promise<Check>} The check
299
+ */
206
300
  async count(target, expected) {
207
301
  const actual = await this.locator(target).count();
208
302
  return {
@@ -212,7 +306,12 @@ export class Tab {
212
306
  };
213
307
  }
214
308
 
215
- /** The current value of a form field. */
309
+ /**
310
+ * The current value of a form field.
311
+ *
312
+ * @param {Target} target The form field
313
+ * @returns {Promise<string>} The value
314
+ */
216
315
  value(target) {
217
316
  return this.locator(target)
218
317
  .first()