@antelopejs/dms 0.15.1 → 0.16.1
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.
- package/CHANGELOG.md +31 -0
- package/README.md +36 -26
- package/bin/control/base.mjs +9 -4
- package/bin/control/browser-client.mjs +24 -5
- package/bin/control/browser-wire.mjs +41 -0
- package/bin/control/browser-wire.test.mjs +96 -0
- package/bin/control/browser.mjs +78 -27
- package/bin/control/commands-stack.mjs +58 -8
- package/bin/control/commands-verify.mjs +47 -1
- package/bin/control/doctor.mjs +49 -4
- package/bin/control/failure.mjs +9 -0
- package/bin/control/journey.mjs +158 -30
- package/bin/control/journey.test.mjs +61 -0
- package/bin/control/json.mjs +229 -0
- package/bin/control/json.test.mjs +130 -0
- package/bin/control/mail.mjs +57 -7
- package/bin/control/mail.test.mjs +55 -0
- package/bin/control/mongo.mjs +205 -27
- package/bin/control/mongo.test.mjs +298 -0
- package/bin/control/options.mjs +33 -0
- package/bin/control/processes.mjs +24 -2
- package/bin/control/renderer.mjs +39 -0
- package/bin/control/renderer.test.mjs +324 -0
- package/bin/control/run-state.mjs +119 -6
- package/bin/control/run-state.test.mjs +171 -0
- package/bin/control/session.mjs +30 -14
- package/bin/control/stack.mjs +95 -24
- package/bin/control/tab.mjs +117 -18
- package/bin/control/verify/behaviour.mjs +44 -6
- package/bin/control/verify/behaviour.test.mjs +130 -22
- package/bin/control/verify/decode.mjs +220 -0
- package/bin/control/verify/decode.test.mjs +245 -0
- package/bin/control/verify/doors.mjs +1 -1
- package/bin/control/verify/doors.test.mjs +40 -0
- package/bin/control/verify/emit.mjs +135 -9
- package/bin/control/verify/formal.mjs +61 -10
- package/bin/control/verify/formal.test.mjs +19 -3
- package/bin/control/verify/judgement.mjs +22 -3
- package/bin/control/verify/map.mjs +25 -5
- package/bin/control/verify/publish.mjs +20 -14
- package/bin/control/verify/seeded.mjs +65 -16
- package/bin/control/verify/skill.mjs +27 -6
- package/bin/control/verify/types.mjs +29 -0
- package/bin/control/verify/verdict.mjs +72 -7
- package/bin/dms-control.mjs +8 -28
- package/checks/api-error-presentation.d.mts +61 -0
- package/dist/attachments/access.js +1 -1
- package/dist/attachments/access.js.map +1 -1
- package/dist/attachments/fields.d.ts +2 -2
- package/dist/attachments/fields.js +26 -13
- package/dist/attachments/fields.js.map +1 -1
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/attachments/index.js.map +1 -1
- package/dist/attachments/read.js.map +1 -1
- package/dist/attachments/save.d.ts +1 -1
- package/dist/attachments/save.js +49 -17
- package/dist/attachments/save.js.map +1 -1
- package/dist/attachments/table-view.d.ts +1 -2
- package/dist/attachments/table-view.js +23 -13
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/events.d.ts +10 -10
- package/dist/automation/index.js +3 -4
- package/dist/automation/index.js.map +1 -1
- package/dist/automation/send-notification.d.ts +2 -2
- package/dist/automation/send-notification.js +4 -6
- package/dist/automation/send-notification.js.map +1 -1
- package/dist/crons/cleanup-user-invites.js.map +1 -1
- package/dist/crons/sweep-expired-sessions.js.map +1 -1
- package/dist/crons/sweep-stale-exports.js.map +1 -1
- package/dist/db/guard-row.d.ts +2 -3
- package/dist/db/guard-row.js +1 -2
- package/dist/db/guard-row.js.map +1 -1
- package/dist/db/models/attachments.model.d.ts +7 -5
- package/dist/db/models/setupToken.model.d.ts +7 -5
- package/dist/db/models/signInAttempts.model.d.ts +7 -5
- package/dist/db/models/systemState.model.d.ts +7 -5
- package/dist/db/models/userEmailChanges.model.d.ts +7 -5
- package/dist/db/models/userKnownDevices.model.d.ts +7 -5
- package/dist/db/models/userNotificationPreferences.model.d.ts +7 -5
- package/dist/db/models/userNotifications.model.d.ts +9 -6
- package/dist/db/models/userNotifications.model.js.map +1 -1
- package/dist/dev/module-update-notifications.js.map +1 -1
- package/dist/hooks/internal/sign-in-monitor.js +6 -6
- package/dist/hooks/internal/sign-in-monitor.js.map +1 -1
- package/dist/implementations/dms/page/controller.d.ts +2 -2
- package/dist/implementations/dms/page/controller.js +3 -1
- package/dist/implementations/dms/page/controller.js.map +1 -1
- package/dist/implementations/dms/page/dynamic-menu.js +3 -2
- package/dist/implementations/dms/page/dynamic-menu.js.map +1 -1
- package/dist/implementations/dms/page/notifications.d.ts +6 -2
- package/dist/implementations/dms/page/notifications.js +1 -0
- package/dist/implementations/dms/page/notifications.js.map +1 -1
- package/dist/implementations/dms/page/preview-pages.js +6 -3
- package/dist/implementations/dms/page/preview-pages.js.map +1 -1
- package/dist/implementations/dms/page/registry.js +10 -4
- package/dist/implementations/dms/page/registry.js.map +1 -1
- package/dist/implementations/dms/permissions.js +12 -12
- package/dist/implementations/dms/permissions.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +3 -4
- package/dist/implementations/dms-base/bulk-selection.js +18 -6
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/footer-summary.js +44 -27
- package/dist/implementations/dms-base/footer-summary.js.map +1 -1
- package/dist/implementations/dms-base/row-rule-operand.d.ts +6 -0
- package/dist/implementations/dms-base/row-rule-operand.js +41 -0
- package/dist/implementations/dms-base/row-rule-operand.js.map +1 -0
- package/dist/implementations/dms-base/search-route.d.ts +9 -13
- package/dist/implementations/dms-base/search-route.js +7 -6
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/services.d.ts +1 -0
- package/dist/implementations/dms-base/services.js +1 -0
- package/dist/implementations/dms-base/services.js.map +1 -1
- package/dist/implementations/dms-base/table-view.d.ts +5 -6
- package/dist/implementations/dms-base/table-view.js +17 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-html-render/index.js +16 -6
- package/dist/implementations/dms-html-render/index.js.map +1 -1
- package/dist/implementations/dms-notifications/delivery-guard.d.ts +3 -2
- package/dist/implementations/dms-notifications/delivery-guard.js.map +1 -1
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pages/public/preview/block-types.d.ts +1 -2
- package/dist/pages/public/preview/block-types.js.map +1 -1
- package/dist/pages/settings/overview/account-summary.js +4 -1
- package/dist/pages/settings/overview/account-summary.js.map +1 -1
- package/dist/pages/settings/users/account-activity-feed.js +3 -2
- package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
- package/dist/pages/settings/users/account-credentials.d.ts +1 -1
- package/dist/pages/settings/users/account-credentials.js +1 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.d.ts +1 -1
- package/dist/pages/settings/users/account-data-store.js +1 -3
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/email-change.d.ts +2 -2
- package/dist/pages/settings/users/email-change.js +2 -3
- package/dist/pages/settings/users/email-change.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.d.ts +2 -2
- package/dist/pages/settings/users/invite-extension-routes.js +11 -4
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/invites.d.ts +2 -2
- package/dist/pages/settings/users/member-invite-batch.d.ts +2 -2
- package/dist/pages/settings/users/member-invite-batch.js +7 -3
- package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
- package/dist/pages/settings/users/member-roles-field.js +1 -4
- package/dist/pages/settings/users/member-roles-field.js.map +1 -1
- package/dist/pages/settings/users/members.d.ts +2 -3
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +2 -3
- package/dist/pages/settings/users/notifications.js +2 -1
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/profile-helpers.d.ts +0 -1
- package/dist/pages/settings/users/profile-helpers.js +0 -17
- package/dist/pages/settings/users/profile-helpers.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +3 -4
- package/dist/pages/settings/users/profile.js +12 -12
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/region.d.ts +3 -4
- package/dist/pages/settings/users/region.js.map +1 -1
- package/dist/pages/settings/users/regional-preferences.d.ts +8 -1
- package/dist/pages/settings/users/regional-preferences.js +10 -6
- package/dist/pages/settings/users/regional-preferences.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +3 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.d.ts +4 -5
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/security.d.ts +9 -12
- package/dist/pages/settings/users/security.js +11 -9
- package/dist/pages/settings/users/security.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.d.ts +7 -7
- package/dist/pages/settings/users/two-factor-operations.js +5 -7
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/heartbeat.js.map +1 -1
- package/dist/realtime/redis-broker.js.map +1 -1
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/sse.js +1 -2
- package/dist/realtime/sse.js.map +1 -1
- package/dist/routes/auth/forgot-password.d.ts +1 -1
- package/dist/routes/auth/forgot-password.js +1 -3
- package/dist/routes/auth/forgot-password.js.map +1 -1
- package/dist/routes/auth/login.d.ts +1 -1
- package/dist/routes/auth/login.js +1 -2
- package/dist/routes/auth/login.js.map +1 -1
- package/dist/routes/auth/logout.d.ts +1 -1
- package/dist/routes/auth/logout.js +1 -2
- package/dist/routes/auth/logout.js.map +1 -1
- package/dist/routes/auth/oauth/providers.js +30 -6
- package/dist/routes/auth/oauth/providers.js.map +1 -1
- package/dist/routes/auth/oauth/state.js +8 -5
- package/dist/routes/auth/oauth/state.js.map +1 -1
- package/dist/routes/auth/oauth/token-exchange.js +8 -1
- package/dist/routes/auth/oauth/token-exchange.js.map +1 -1
- package/dist/routes/auth/refresh.d.ts +1 -1
- package/dist/routes/auth/refresh.js +1 -2
- package/dist/routes/auth/refresh.js.map +1 -1
- package/dist/routes/auth/request-2fa-email.d.ts +1 -1
- package/dist/routes/auth/request-2fa-email.js +1 -2
- package/dist/routes/auth/request-2fa-email.js.map +1 -1
- package/dist/routes/auth/reset-password.d.ts +1 -1
- package/dist/routes/auth/reset-password.js +1 -3
- package/dist/routes/auth/reset-password.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +1 -1
- package/dist/routes/auth/session-handoff.js +1 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth/signup.d.ts +1 -1
- package/dist/routes/auth/signup.js +1 -2
- package/dist/routes/auth/signup.js.map +1 -1
- package/dist/routes/auth/switch-tenant.d.ts +2 -3
- package/dist/routes/auth/switch-tenant.js +3 -2
- package/dist/routes/auth/switch-tenant.js.map +1 -1
- package/dist/routes/auth/validate-forgot-password-token.d.ts +1 -1
- package/dist/routes/auth/validate-forgot-password-token.js +1 -3
- package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
- package/dist/routes/auth/validate-invite-token.d.ts +1 -1
- package/dist/routes/auth/validate-invite-token.js +1 -3
- package/dist/routes/auth/validate-invite-token.js.map +1 -1
- package/dist/routes/auth/verify-2fa.d.ts +1 -1
- package/dist/routes/auth/verify-2fa.js +1 -2
- package/dist/routes/auth/verify-2fa.js.map +1 -1
- package/dist/routes/auth/verify-email.d.ts +1 -1
- package/dist/routes/auth/verify-email.js +1 -2
- package/dist/routes/auth/verify-email.js.map +1 -1
- package/dist/routes/auth.d.ts +14 -15
- package/dist/routes/auth.js +15 -13
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/files.d.ts +1 -2
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/onboarding.d.ts +1 -2
- package/dist/routes/onboarding.js.map +1 -1
- package/dist/routes/realtime.js +5 -6
- package/dist/routes/realtime.js.map +1 -1
- package/dist/routes/store.d.ts +6 -7
- package/dist/routes/store.js +19 -13
- package/dist/routes/store.js.map +1 -1
- package/dist/store/catalog.d.ts +13 -5
- package/dist/store/catalog.js +19 -9
- package/dist/store/catalog.js.map +1 -1
- package/dist/store/compatibility.d.ts +1 -0
- package/dist/store/compatibility.js +24 -8
- package/dist/store/compatibility.js.map +1 -1
- package/dist/store/frontend-compatibility.js +15 -3
- package/dist/store/frontend-compatibility.js.map +1 -1
- package/dist/store/interfaces.js +35 -13
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/internal/frontend/frontend-modules.js +7 -3
- package/dist/store/internal/frontend/frontend-modules.js.map +1 -1
- package/dist/store/internal/frontend/frontend-ready.d.ts +2 -1
- package/dist/store/internal/frontend/frontend-ready.js +6 -4
- package/dist/store/internal/frontend/frontend-ready.js.map +1 -1
- package/dist/store/internal/frontend/manifest.js +6 -7
- package/dist/store/internal/frontend/manifest.js.map +1 -1
- package/dist/store/operations.js.map +1 -1
- package/dist/store/registry.js.map +1 -1
- package/dist/store/runtime.d.ts +13 -0
- package/dist/store/runtime.js +9 -1
- package/dist/store/runtime.js.map +1 -1
- package/dist/utils/admin-invite-email.js +3 -1
- package/dist/utils/admin-invite-email.js.map +1 -1
- package/dist/utils/email-subjects.js +3 -1
- package/dist/utils/email-subjects.js.map +1 -1
- package/dist/utils/export-jobs-run.js.map +1 -1
- package/dist/utils/export-jobs.js +3 -9
- package/dist/utils/export-jobs.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/own-value.d.ts +1 -0
- package/dist/utils/own-value.js +7 -0
- package/dist/utils/own-value.js.map +1 -0
- package/dist/utils/row-action-rule-evaluator.js +31 -35
- package/dist/utils/row-action-rule-evaluator.js.map +1 -1
- package/dist/utils/sign-in-country.js +4 -2
- package/dist/utils/sign-in-country.js.map +1 -1
- package/dist/utils/tenant-export-archive.js +9 -14
- package/dist/utils/tenant-export-archive.js.map +1 -1
- package/dist/utils/user-agent.js +2 -1
- package/dist/utils/user-agent.js.map +1 -1
- package/dist/validation/auth.schema.d.ts +10 -10
- package/dist/validation/regional-preferences.schema.d.ts +2 -2
- package/dist/validation/role-editor.schema.d.ts +1 -1
- package/frontend-vue/build/backend-boundary.ts +49 -18
- package/frontend-vue/dms.frontend.ts +8 -12
- package/frontend-vue/layers/dms-auth/server/accounts.ts +44 -3
- package/frontend-vue/layers/dms-auth/server/actions.ts +17 -4
- package/frontend-vue/layers/dms-auth/server/oauth.ts +42 -4
- package/frontend-vue/layers/dms-core/app/types/watch.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +57 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +7 -9
- package/frontend-vue/layers/dms-ui/app/entries/composables/form/registerFormFunctions.ts +1 -0
- package/frontend-vue/lazy-plugin.ts +18 -0
- package/frontend-vue/package.json +2 -2
- package/package.json +19 -13
- package/skills/dms-project/SKILL.md +4 -3
package/bin/control/session.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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?:
|
|
80
|
-
* @returns {Promise<
|
|
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 {
|
|
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<
|
|
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
|
-
|
|
148
|
-
|
|
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 {
|
|
174
|
+
* @param {import("./run-state.mjs").RunState} state The run
|
|
159
175
|
* @param {{ platformName?: string }} options Wizard details
|
|
160
|
-
* @returns {Promise<{ register:
|
|
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
|
|
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 {
|
|
194
|
-
* @param {{ method: string, path: string, body?:
|
|
195
|
-
* @returns {Promise<
|
|
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}`;
|
package/bin/control/stack.mjs
CHANGED
|
@@ -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 =
|
|
79
|
-
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
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 {
|
|
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 {
|
|
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.
|
|
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 {
|
|
354
|
-
* @returns {Promise<
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/bin/control/tab.mjs
CHANGED
|
@@ -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 {
|
|
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 {
|
|
52
|
+
* @param {Omit<Action, "baseUrl">} action The action, as `perform` takes it
|
|
41
53
|
* @param {string} label What the step does
|
|
42
|
-
* @returns {Promise<
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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()
|