@antelopejs/dms 0.15.0 → 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 (354) hide show
  1. package/CHANGELOG.md +47 -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 +163 -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 +31 -14
  27. package/bin/control/stack.mjs +96 -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/config/internal/state.d.ts +7 -0
  69. package/dist/config/internal/state.js +9 -0
  70. package/dist/config/internal/state.js.map +1 -1
  71. package/dist/config/startup-config.d.ts +1 -0
  72. package/dist/config/startup-config.js +11 -0
  73. package/dist/config/startup-config.js.map +1 -1
  74. package/dist/crons/cleanup-user-invites.js.map +1 -1
  75. package/dist/crons/sweep-expired-sessions.js.map +1 -1
  76. package/dist/crons/sweep-stale-exports.js.map +1 -1
  77. package/dist/db/guard-row.d.ts +2 -3
  78. package/dist/db/guard-row.js +1 -2
  79. package/dist/db/guard-row.js.map +1 -1
  80. package/dist/db/index.d.ts +1 -0
  81. package/dist/db/index.js +1 -0
  82. package/dist/db/index.js.map +1 -1
  83. package/dist/db/models/attachments.model.d.ts +7 -5
  84. package/dist/db/models/index.d.ts +1 -0
  85. package/dist/db/models/index.js +1 -0
  86. package/dist/db/models/index.js.map +1 -1
  87. package/dist/db/models/setupToken.model.d.ts +31 -0
  88. package/dist/db/models/setupToken.model.js +62 -0
  89. package/dist/db/models/setupToken.model.js.map +1 -0
  90. package/dist/db/models/signInAttempts.model.d.ts +7 -5
  91. package/dist/db/models/systemState.model.d.ts +7 -5
  92. package/dist/db/models/userEmailChanges.model.d.ts +7 -5
  93. package/dist/db/models/userKnownDevices.model.d.ts +7 -5
  94. package/dist/db/models/userNotificationPreferences.model.d.ts +7 -5
  95. package/dist/db/models/userNotifications.model.d.ts +9 -6
  96. package/dist/db/models/userNotifications.model.js.map +1 -1
  97. package/dist/db/setup-token.d.ts +2 -0
  98. package/dist/db/setup-token.js +28 -0
  99. package/dist/db/setup-token.js.map +1 -0
  100. package/dist/db/tables/index.d.ts +1 -0
  101. package/dist/db/tables/index.js +1 -0
  102. package/dist/db/tables/index.js.map +1 -1
  103. package/dist/db/tables/setupToken.table.d.ts +7 -0
  104. package/dist/db/tables/setupToken.table.js +34 -0
  105. package/dist/db/tables/setupToken.table.js.map +1 -0
  106. package/dist/dev/module-update-notifications.js.map +1 -1
  107. package/dist/hooks/internal/sign-in-monitor.js +6 -6
  108. package/dist/hooks/internal/sign-in-monitor.js.map +1 -1
  109. package/dist/implementations/dms/page/controller.d.ts +2 -2
  110. package/dist/implementations/dms/page/controller.js +3 -1
  111. package/dist/implementations/dms/page/controller.js.map +1 -1
  112. package/dist/implementations/dms/page/dynamic-menu.js +3 -2
  113. package/dist/implementations/dms/page/dynamic-menu.js.map +1 -1
  114. package/dist/implementations/dms/page/notifications.d.ts +6 -2
  115. package/dist/implementations/dms/page/notifications.js +1 -0
  116. package/dist/implementations/dms/page/notifications.js.map +1 -1
  117. package/dist/implementations/dms/page/payloads.js +6 -10
  118. package/dist/implementations/dms/page/payloads.js.map +1 -1
  119. package/dist/implementations/dms/page/preview-pages.js +6 -3
  120. package/dist/implementations/dms/page/preview-pages.js.map +1 -1
  121. package/dist/implementations/dms/page/public-layout.d.ts +2 -0
  122. package/dist/implementations/dms/page/public-layout.js +45 -0
  123. package/dist/implementations/dms/page/public-layout.js.map +1 -0
  124. package/dist/implementations/dms/page/registry.js +10 -4
  125. package/dist/implementations/dms/page/registry.js.map +1 -1
  126. package/dist/implementations/dms/permissions.js +12 -12
  127. package/dist/implementations/dms/permissions.js.map +1 -1
  128. package/dist/implementations/dms-base/bulk-selection.d.ts +3 -4
  129. package/dist/implementations/dms-base/bulk-selection.js +18 -6
  130. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  131. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  132. package/dist/implementations/dms-base/footer-summary.js +44 -27
  133. package/dist/implementations/dms-base/footer-summary.js.map +1 -1
  134. package/dist/implementations/dms-base/row-rule-operand.d.ts +6 -0
  135. package/dist/implementations/dms-base/row-rule-operand.js +41 -0
  136. package/dist/implementations/dms-base/row-rule-operand.js.map +1 -0
  137. package/dist/implementations/dms-base/search-route.d.ts +9 -13
  138. package/dist/implementations/dms-base/search-route.js +7 -6
  139. package/dist/implementations/dms-base/search-route.js.map +1 -1
  140. package/dist/implementations/dms-base/services.d.ts +1 -0
  141. package/dist/implementations/dms-base/services.js +1 -0
  142. package/dist/implementations/dms-base/services.js.map +1 -1
  143. package/dist/implementations/dms-base/table-view.d.ts +5 -6
  144. package/dist/implementations/dms-base/table-view.js +17 -6
  145. package/dist/implementations/dms-base/table-view.js.map +1 -1
  146. package/dist/implementations/dms-html-render/index.js +16 -6
  147. package/dist/implementations/dms-html-render/index.js.map +1 -1
  148. package/dist/implementations/dms-notifications/delivery-guard.d.ts +3 -2
  149. package/dist/implementations/dms-notifications/delivery-guard.js.map +1 -1
  150. package/dist/implementations/dms-notifications/index.js.map +1 -1
  151. package/dist/index.d.ts +0 -1
  152. package/dist/index.js +6 -2
  153. package/dist/index.js.map +1 -1
  154. package/dist/pages/public/preview/block-types.d.ts +1 -2
  155. package/dist/pages/public/preview/block-types.js.map +1 -1
  156. package/dist/pages/settings/overview/account-summary.js +4 -1
  157. package/dist/pages/settings/overview/account-summary.js.map +1 -1
  158. package/dist/pages/settings/users/account-activity-feed.js +3 -2
  159. package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
  160. package/dist/pages/settings/users/account-credentials.d.ts +1 -1
  161. package/dist/pages/settings/users/account-credentials.js +1 -2
  162. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  163. package/dist/pages/settings/users/account-data-store.d.ts +1 -1
  164. package/dist/pages/settings/users/account-data-store.js +1 -3
  165. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  166. package/dist/pages/settings/users/current-password.js +1 -1
  167. package/dist/pages/settings/users/current-password.js.map +1 -1
  168. package/dist/pages/settings/users/email-change.d.ts +2 -2
  169. package/dist/pages/settings/users/email-change.js +2 -3
  170. package/dist/pages/settings/users/email-change.js.map +1 -1
  171. package/dist/pages/settings/users/invite-extension-routes.d.ts +2 -2
  172. package/dist/pages/settings/users/invite-extension-routes.js +11 -4
  173. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  174. package/dist/pages/settings/users/invites.d.ts +2 -2
  175. package/dist/pages/settings/users/member-invite-batch.d.ts +2 -2
  176. package/dist/pages/settings/users/member-invite-batch.js +7 -3
  177. package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
  178. package/dist/pages/settings/users/member-roles-field.js +1 -4
  179. package/dist/pages/settings/users/member-roles-field.js.map +1 -1
  180. package/dist/pages/settings/users/members.d.ts +2 -3
  181. package/dist/pages/settings/users/members.js.map +1 -1
  182. package/dist/pages/settings/users/notifications.d.ts +2 -3
  183. package/dist/pages/settings/users/notifications.js +2 -1
  184. package/dist/pages/settings/users/notifications.js.map +1 -1
  185. package/dist/pages/settings/users/profile-helpers.d.ts +0 -1
  186. package/dist/pages/settings/users/profile-helpers.js +0 -17
  187. package/dist/pages/settings/users/profile-helpers.js.map +1 -1
  188. package/dist/pages/settings/users/profile.d.ts +3 -4
  189. package/dist/pages/settings/users/profile.js +12 -12
  190. package/dist/pages/settings/users/profile.js.map +1 -1
  191. package/dist/pages/settings/users/region.d.ts +3 -4
  192. package/dist/pages/settings/users/region.js.map +1 -1
  193. package/dist/pages/settings/users/regional-preferences.d.ts +8 -1
  194. package/dist/pages/settings/users/regional-preferences.js +10 -6
  195. package/dist/pages/settings/users/regional-preferences.js.map +1 -1
  196. package/dist/pages/settings/users/role-preview.js +3 -2
  197. package/dist/pages/settings/users/role-preview.js.map +1 -1
  198. package/dist/pages/settings/users/roles.d.ts +4 -5
  199. package/dist/pages/settings/users/roles.js.map +1 -1
  200. package/dist/pages/settings/users/security.d.ts +9 -12
  201. package/dist/pages/settings/users/security.js +11 -9
  202. package/dist/pages/settings/users/security.js.map +1 -1
  203. package/dist/pages/settings/users/two-factor-operations.d.ts +7 -7
  204. package/dist/pages/settings/users/two-factor-operations.js +5 -7
  205. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  206. package/dist/realtime/broker.js.map +1 -1
  207. package/dist/realtime/heartbeat.js.map +1 -1
  208. package/dist/realtime/redis-broker.js.map +1 -1
  209. package/dist/realtime/sessions.js.map +1 -1
  210. package/dist/realtime/sse.js +1 -2
  211. package/dist/realtime/sse.js.map +1 -1
  212. package/dist/routes/auth/forgot-password.d.ts +1 -1
  213. package/dist/routes/auth/forgot-password.js +1 -3
  214. package/dist/routes/auth/forgot-password.js.map +1 -1
  215. package/dist/routes/auth/login.d.ts +2 -2
  216. package/dist/routes/auth/login.js +8 -4
  217. package/dist/routes/auth/login.js.map +1 -1
  218. package/dist/routes/auth/logout.d.ts +1 -1
  219. package/dist/routes/auth/logout.js +1 -2
  220. package/dist/routes/auth/logout.js.map +1 -1
  221. package/dist/routes/auth/oauth/providers.js +30 -6
  222. package/dist/routes/auth/oauth/providers.js.map +1 -1
  223. package/dist/routes/auth/oauth/state.js +8 -5
  224. package/dist/routes/auth/oauth/state.js.map +1 -1
  225. package/dist/routes/auth/oauth/token-exchange.js +8 -1
  226. package/dist/routes/auth/oauth/token-exchange.js.map +1 -1
  227. package/dist/routes/auth/password-hash-upgrade.d.ts +2 -0
  228. package/dist/routes/auth/password-hash-upgrade.js +22 -0
  229. package/dist/routes/auth/password-hash-upgrade.js.map +1 -0
  230. package/dist/routes/auth/refresh.d.ts +1 -1
  231. package/dist/routes/auth/refresh.js +1 -2
  232. package/dist/routes/auth/refresh.js.map +1 -1
  233. package/dist/routes/auth/request-2fa-email.d.ts +1 -1
  234. package/dist/routes/auth/request-2fa-email.js +1 -2
  235. package/dist/routes/auth/request-2fa-email.js.map +1 -1
  236. package/dist/routes/auth/reset-password.d.ts +1 -1
  237. package/dist/routes/auth/reset-password.js +1 -3
  238. package/dist/routes/auth/reset-password.js.map +1 -1
  239. package/dist/routes/auth/session-handoff.d.ts +1 -1
  240. package/dist/routes/auth/session-handoff.js +1 -2
  241. package/dist/routes/auth/session-handoff.js.map +1 -1
  242. package/dist/routes/auth/signup.d.ts +1 -1
  243. package/dist/routes/auth/signup.js +1 -2
  244. package/dist/routes/auth/signup.js.map +1 -1
  245. package/dist/routes/auth/switch-tenant.d.ts +2 -3
  246. package/dist/routes/auth/switch-tenant.js +3 -2
  247. package/dist/routes/auth/switch-tenant.js.map +1 -1
  248. package/dist/routes/auth/validate-forgot-password-token.d.ts +1 -1
  249. package/dist/routes/auth/validate-forgot-password-token.js +1 -3
  250. package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
  251. package/dist/routes/auth/validate-invite-token.d.ts +1 -1
  252. package/dist/routes/auth/validate-invite-token.js +1 -3
  253. package/dist/routes/auth/validate-invite-token.js.map +1 -1
  254. package/dist/routes/auth/verify-2fa.d.ts +1 -1
  255. package/dist/routes/auth/verify-2fa.js +1 -2
  256. package/dist/routes/auth/verify-2fa.js.map +1 -1
  257. package/dist/routes/auth/verify-email.d.ts +1 -1
  258. package/dist/routes/auth/verify-email.js +1 -2
  259. package/dist/routes/auth/verify-email.js.map +1 -1
  260. package/dist/routes/auth.d.ts +14 -15
  261. package/dist/routes/auth.js +15 -13
  262. package/dist/routes/auth.js.map +1 -1
  263. package/dist/routes/authentication.d.ts +2 -0
  264. package/dist/routes/authentication.js +2 -0
  265. package/dist/routes/authentication.js.map +1 -1
  266. package/dist/routes/files.d.ts +1 -2
  267. package/dist/routes/files.js.map +1 -1
  268. package/dist/routes/onboarding.d.ts +4 -3
  269. package/dist/routes/onboarding.js +20 -3
  270. package/dist/routes/onboarding.js.map +1 -1
  271. package/dist/routes/realtime.js +5 -6
  272. package/dist/routes/realtime.js.map +1 -1
  273. package/dist/routes/store.d.ts +6 -7
  274. package/dist/routes/store.js +19 -13
  275. package/dist/routes/store.js.map +1 -1
  276. package/dist/store/catalog.d.ts +13 -5
  277. package/dist/store/catalog.js +19 -9
  278. package/dist/store/catalog.js.map +1 -1
  279. package/dist/store/compatibility.d.ts +1 -0
  280. package/dist/store/compatibility.js +24 -8
  281. package/dist/store/compatibility.js.map +1 -1
  282. package/dist/store/frontend-compatibility.js +15 -3
  283. package/dist/store/frontend-compatibility.js.map +1 -1
  284. package/dist/store/interfaces.js +35 -13
  285. package/dist/store/interfaces.js.map +1 -1
  286. package/dist/store/internal/frontend/frontend-modules.js +7 -3
  287. package/dist/store/internal/frontend/frontend-modules.js.map +1 -1
  288. package/dist/store/internal/frontend/frontend-ready.d.ts +2 -1
  289. package/dist/store/internal/frontend/frontend-ready.js +6 -4
  290. package/dist/store/internal/frontend/frontend-ready.js.map +1 -1
  291. package/dist/store/internal/frontend/manifest.js +6 -7
  292. package/dist/store/internal/frontend/manifest.js.map +1 -1
  293. package/dist/store/operations.js.map +1 -1
  294. package/dist/store/registry.js.map +1 -1
  295. package/dist/store/runtime.d.ts +13 -0
  296. package/dist/store/runtime.js +9 -1
  297. package/dist/store/runtime.js.map +1 -1
  298. package/dist/utils/admin-invite-email.js +3 -1
  299. package/dist/utils/admin-invite-email.js.map +1 -1
  300. package/dist/utils/email-subjects.js +3 -1
  301. package/dist/utils/email-subjects.js.map +1 -1
  302. package/dist/utils/ensure-default-tenant.d.ts +2 -1
  303. package/dist/utils/ensure-default-tenant.js +126 -8
  304. package/dist/utils/ensure-default-tenant.js.map +1 -1
  305. package/dist/utils/export-jobs-run.js.map +1 -1
  306. package/dist/utils/export-jobs.js +3 -9
  307. package/dist/utils/export-jobs.js.map +1 -1
  308. package/dist/utils/index.d.ts +1 -0
  309. package/dist/utils/index.js +1 -0
  310. package/dist/utils/index.js.map +1 -1
  311. package/dist/utils/own-value.d.ts +1 -0
  312. package/dist/utils/own-value.js +7 -0
  313. package/dist/utils/own-value.js.map +1 -0
  314. package/dist/utils/row-action-rule-evaluator.js +31 -35
  315. package/dist/utils/row-action-rule-evaluator.js.map +1 -1
  316. package/dist/utils/sign-in-country.js +4 -2
  317. package/dist/utils/sign-in-country.js.map +1 -1
  318. package/dist/utils/tenant-export-archive.js +9 -14
  319. package/dist/utils/tenant-export-archive.js.map +1 -1
  320. package/dist/utils/user-agent.js +2 -1
  321. package/dist/utils/user-agent.js.map +1 -1
  322. package/dist/validation/auth.schema.d.ts +10 -10
  323. package/dist/validation/onboarding/register-admin.schema.d.ts +5 -0
  324. package/dist/validation/onboarding/register-admin.schema.js +3 -0
  325. package/dist/validation/onboarding/register-admin.schema.js.map +1 -1
  326. package/dist/validation/regional-preferences.schema.d.ts +2 -2
  327. package/dist/validation/role-editor.schema.d.ts +1 -1
  328. package/frontend-vue/build/backend-boundary.ts +49 -18
  329. package/frontend-vue/dms.frontend.ts +8 -12
  330. package/frontend-vue/layers/dms-auth/server/accounts.ts +44 -3
  331. package/frontend-vue/layers/dms-auth/server/actions.ts +17 -4
  332. package/frontend-vue/layers/dms-auth/server/oauth.ts +42 -4
  333. package/frontend-vue/layers/dms-core/app/types/watch.ts +1 -2
  334. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +1 -0
  335. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +1 -0
  336. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +5 -0
  337. package/frontend-vue/layers/dms-layout/app/app.config.ts +1 -0
  338. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +9 -9
  339. package/frontend-vue/layers/dms-layout/app/components/layout/AppLogo.vue +1 -0
  340. package/frontend-vue/layers/dms-layout/app/composables/page/useInstalledModules.ts +78 -0
  341. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +5 -24
  342. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +2 -1
  343. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +2 -1
  344. package/frontend-vue/layers/dms-onboarding/app/components/onboarding/steps/register.vue +21 -1
  345. package/frontend-vue/layers/dms-onboarding/app/custom-pages/onboarding.vue +14 -0
  346. package/frontend-vue/layers/dms-onboarding/i18n/locales/onboarding-en-GB.json +2 -0
  347. package/frontend-vue/layers/dms-onboarding/i18n/locales/onboarding-fr-FR.json +2 -0
  348. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +57 -6
  349. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +7 -9
  350. package/frontend-vue/layers/dms-ui/app/entries/composables/form/registerFormFunctions.ts +1 -0
  351. package/frontend-vue/lazy-plugin.ts +18 -0
  352. package/frontend-vue/package.json +2 -2
  353. package/package.json +19 -13
  354. 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);
@@ -168,6 +184,7 @@ export async function onboard(state, options) {
168
184
  method: "POST",
169
185
  body: {
170
186
  ...admin,
187
+ setupToken: state.secrets.DMS_SETUP_TOKEN,
171
188
  platformName: options.platformName ?? `Verify ${state.id}`,
172
189
  },
173
190
  },
@@ -178,7 +195,7 @@ export async function onboard(state, options) {
178
195
  admin: {
179
196
  email: admin.email,
180
197
  password: admin.password,
181
- userId: register.body?.[0] ?? null,
198
+ userId: firstString(register.body) ?? null,
182
199
  },
183
200
  };
184
201
  saveRun(state);
@@ -189,9 +206,9 @@ export async function onboard(state, options) {
189
206
  * An authenticated (or anonymous) request to the run's backend, signing the
190
207
  * account in again once when its token was refused.
191
208
  *
192
- * @param {Record<string, any>} state The run
193
- * @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
194
- * @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
195
212
  */
196
213
  export async function apiRequest(state, request) {
197
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;
@@ -42,6 +50,7 @@ const SECRET_NAMES = [
42
50
  "DMS_BOOTSTRAP_SECRET",
43
51
  "DMS_HTML_RENDER_SECRET",
44
52
  "DMS_SESSION_SECRET",
53
+ "DMS_SETUP_TOKEN",
45
54
  ];
46
55
  const DROPPED_PREFIXES = ["ANTELOPEJS_", "npm_", "PNPM_", "DMS_", "MONGO_"];
47
56
 
@@ -66,23 +75,30 @@ export function resolutionFolders(state) {
66
75
  }
67
76
 
68
77
  /**
69
- * 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.
70
80
  *
71
81
  * @param {string} playground The playground folder
82
+ * @param {string} [renderer] The renderer checkout the run serves
72
83
  * @returns {{ ajs: string, dms: string }} Their scripts
73
84
  */
74
- export function cliPaths(playground) {
85
+ export function cliPaths(playground, renderer) {
75
86
  const require = createRequire(join(playground, "package.json"));
76
87
  const core = dirname(require.resolve("@antelopejs/core/package.json"));
77
- const frontend = dirname(
78
- require.resolve("@antelopejs/dms-frontend/package.json"),
79
- );
88
+ const frontend =
89
+ renderer ??
90
+ dirname(require.resolve("@antelopejs/dms-frontend/package.json"));
80
91
  return {
81
92
  ajs: join(core, "dist", "core", "cli", "index.js"),
82
- dms: join(frontend, "dist", "index.js"),
93
+ dms: join(frontend, RENDERER_CLI),
83
94
  };
84
95
  }
85
96
 
97
+ /**
98
+ * @param {string} real
99
+ * @param {string} shadow
100
+ * @param {string} keep
101
+ */
86
102
  function shadowLevel(real, shadow, keep) {
87
103
  mkdirSync(shadow, { recursive: true });
88
104
  for (const entry of readdirSync(real)) {
@@ -91,19 +107,40 @@ function shadowLevel(real, shadow, keep) {
91
107
  }
92
108
  }
93
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
+
94
128
  /**
95
129
  * Lays out the run's copy of the checkout: from the checkout root down to
96
130
  * the playground, every sibling is linked, so relative paths such as a
97
131
  * tsconfig `extends` resolve as in the checkout. The playground itself is
98
132
  * copied without its installed modules and build, so `.antelope/` and the
99
- * 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.
100
136
  *
101
137
  * @param {string} playground The playground folder
102
138
  * @param {string} checkout The checkout root, or the module folder outside one
103
139
  * @param {string} root The run's copy of the checkout
140
+ * @param {string} [renderer] The renderer checkout the run serves
104
141
  * @returns {string} The run's playground folder
105
142
  */
106
- export function layOutCopy(playground, checkout, root) {
143
+ export function layOutCopy(playground, checkout, root, renderer) {
107
144
  const segments = relative(checkout, playground).split(sep);
108
145
  let real = checkout;
109
146
  let shadow = root;
@@ -116,7 +153,9 @@ export function layOutCopy(playground, checkout, root) {
116
153
  recursive: true,
117
154
  filter: (source) => !COPY_SKIPPED.has(basename(source)),
118
155
  });
119
- 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"));
120
159
  return shadow;
121
160
  }
122
161
 
@@ -133,7 +172,7 @@ function inheritedEnv() {
133
172
  * database, origin and secrets, an npm cache and a pnpm store shared by
134
173
  * every run, no update checks and no colors. Extra `KEY=VALUE` pairs from `--env` come last.
135
174
  *
136
- * @param {Record<string, any>} state The run
175
+ * @param {import("./run-state.mjs").RunState} state The run
137
176
  * @returns {NodeJS.ProcessEnv} The environment
138
177
  */
139
178
  export function runEnv(state) {
@@ -162,6 +201,10 @@ export function runEnv(state) {
162
201
  };
163
202
  }
164
203
 
204
+ /**
205
+ * @param {string[]} pairs
206
+ * @returns {Record<string, string>}
207
+ */
165
208
  function parseExtraEnv(pairs) {
166
209
  return Object.fromEntries(
167
210
  pairs.map((pair) => {
@@ -172,6 +215,10 @@ function parseExtraEnv(pairs) {
172
215
  );
173
216
  }
174
217
 
218
+ /**
219
+ * @param {UpOptions} options
220
+ * @returns {Promise<RunState>}
221
+ */
175
222
  async function initialState(options) {
176
223
  const playground = resolve(options.playground);
177
224
  if (!existsSync(join(playground, "antelope.config.ts"))) {
@@ -183,7 +230,9 @@ async function initialState(options) {
183
230
  const webPort = await freePort();
184
231
  const smtpPort = await freePort();
185
232
  const checkout = checkoutOf(playground);
186
- return {
233
+ const renderer = options.renderer && resolveRenderer(options.renderer);
234
+ const verified = renderer ? checkoutOf(renderer) : checkout;
235
+ const state = {
187
236
  ...paths,
188
237
  id,
189
238
  createdAt: new Date().toISOString(),
@@ -191,7 +240,7 @@ async function initialState(options) {
191
240
  playground,
192
241
  moduleRoot: dirname(playground),
193
242
  repo: checkout.root,
194
- headSha: checkout.head,
243
+ headSha: verified.head,
195
244
  baseRef: options.base ?? "origin/main",
196
245
  skill: options.skill ?? null,
197
246
  apiPort,
@@ -217,13 +266,15 @@ async function initialState(options) {
217
266
  tokens: {},
218
267
  ready: false,
219
268
  };
269
+ if (renderer) state.renderer = renderer;
270
+ return state;
220
271
  }
221
272
 
222
273
  /**
223
274
  * How to run one of the playground's CLIs for the run: its own log, the
224
275
  * run's environment, from the run's playground copy or `cwd`.
225
276
  *
226
- * @param {Record<string, any>} state The run
277
+ * @param {import("./run-state.mjs").RunState} state The run
227
278
  * @param {string} name The log name
228
279
  * @param {string} script The CLI script
229
280
  * @param {string[]} args Its arguments
@@ -241,6 +292,10 @@ export function spec(state, name, script, args, cwd = state.runPlayground) {
241
292
  };
242
293
  }
243
294
 
295
+ /**
296
+ * @param {RunState} state
297
+ * @param {import("./run-state.mjs").ProcessRecord} processRecord
298
+ */
244
299
  function record(state, processRecord) {
245
300
  state.processes = [
246
301
  ...state.processes.filter((entry) => entry.name !== processRecord.name),
@@ -253,7 +308,7 @@ function record(state, processRecord) {
253
308
  * Starts the run's mail sink unless it already runs, and waits until it is
254
309
  * the process listening on the run's mail port.
255
310
  *
256
- * @param {Record<string, any>} state The run
311
+ * @param {import("./run-state.mjs").RunState} state The run
257
312
  * @returns {Promise<void>} Resolves once it listens
258
313
  */
259
314
  export async function ensureMail(state) {
@@ -271,6 +326,10 @@ export async function ensureMail(state) {
271
326
  await waitForOwnPort(state.smtpPort, mail, READY_TIMEOUT_MS);
272
327
  }
273
328
 
329
+ /**
330
+ * @param {RunState} state
331
+ * @returns {Promise<void>}
332
+ */
274
333
  async function startFirstMail(state) {
275
334
  for (let attempt = 1; ; attempt += 1) {
276
335
  try {
@@ -283,6 +342,11 @@ async function startFirstMail(state) {
283
342
  }
284
343
  }
285
344
 
345
+ /**
346
+ * @param {RunState} state
347
+ * @param {{ playground?: string, cwd?: string }} [build]
348
+ * @returns {Promise<void>}
349
+ */
286
350
  async function startBackend(state, build) {
287
351
  const { ajs } = cliPaths(build?.playground ?? state.playground);
288
352
  const backend = startDetached(
@@ -302,12 +366,17 @@ async function startBackend(state, build) {
302
366
  );
303
367
  }
304
368
 
369
+ /**
370
+ * @param {RunState} state
371
+ * @returns {Promise<void>}
372
+ */
305
373
  async function buildAndStart(state) {
306
- const clis = cliPaths(state.playground);
374
+ const clis = cliPaths(state.playground, state.renderer);
307
375
  state.runPlayground = layOutCopy(
308
376
  state.playground,
309
377
  state.repo ?? state.moduleRoot,
310
378
  state.root,
379
+ state.renderer,
311
380
  );
312
381
  if (state.ownsMongo) {
313
382
  const mongo = await startMongod(state, resolutionFolders(state));
@@ -345,12 +414,13 @@ async function buildAndStart(state) {
345
414
  /**
346
415
  * Starts an isolated DMS from a playground: a copy of it in a run folder,
347
416
  * free ports, the `docs` environment, a `verify_<run>` database on a mongod
348
- * 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
349
419
  * `/api/onboarding/informations` and `/dms-preview` answer 200. On failure
350
420
  * it stops what it started and keeps the logs.
351
421
  *
352
- * @param {{ playground: string, runs?: string, mongoUrl?: string, env?: string[], base?: string, skill?: string }} options Where the playground and the runs are
353
- * @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
354
424
  */
355
425
  export async function up(options) {
356
426
  const state = await initialState(options);
@@ -373,7 +443,7 @@ export async function up(options) {
373
443
  * a crash would, from the run's build or another one (`build`, the base
374
444
  * version's). The frontend keeps serving and proxies to the same port.
375
445
  *
376
- * @param {Record<string, any>} state The run
446
+ * @param {import("./run-state.mjs").RunState} state The run
377
447
  * @param {{ playground: string, cwd: string }} [build] Another build to start
378
448
  * @returns {Promise<void>} Resolves once the backend answers again
379
449
  */
@@ -389,7 +459,7 @@ export async function restartBackend(state, build) {
389
459
  * journey database is dropped, so a long run does not exhaust mongod's
390
460
  * file handles; its evidence stays.
391
461
  *
392
- * @param {Record<string, any>} state The run
462
+ * @param {import("./run-state.mjs").RunState} state The run
393
463
  * @param {string} suffix What tells the database apart, such as `j03`
394
464
  * @returns {Promise<string>} The database now in use
395
465
  */
@@ -411,7 +481,7 @@ export async function freshDatabase(state, suffix) {
411
481
  * run's databases, and removes everything but `evidence/`, `logs/` and
412
482
  * `run.json`, from which the secrets and passwords are removed.
413
483
  *
414
- * @param {Record<string, any>} state The run
484
+ * @param {import("./run-state.mjs").RunState} state The run
415
485
  * @returns {Promise<string[]>} One line per action
416
486
  */
417
487
  export async function down(state) {
@@ -428,8 +498,8 @@ export async function down(state) {
428
498
  state.mongoUrl,
429
499
  state.databasePrefix,
430
500
  resolutionFolders(state),
431
- ).catch((error) => [
432
- `could not drop the run's databases: ${error.message}`,
501
+ ).catch((/** @type {unknown} */ error) => [
502
+ `could not drop the run's databases: ${describeFailure(error)}`,
433
503
  ]);
434
504
  lines.push(...dropped);
435
505
  }
@@ -473,7 +543,9 @@ export async function down(state) {
473
543
  */
474
544
  export function packageVersion(file) {
475
545
  try {
476
- 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";
477
549
  } catch {
478
550
  return "missing";
479
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()