@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
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import {
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
import { withBaseBackend } from "./base.mjs";
|
|
14
|
+
import { targetOf } from "./commands-stack.mjs";
|
|
15
|
+
import { git } from "./git.mjs";
|
|
16
|
+
import { resolveRenderer } from "./renderer.mjs";
|
|
17
|
+
import { decodeSkillConfig } from "./verify/decode.mjs";
|
|
18
|
+
import { upArguments } from "./verify/seeded.mjs";
|
|
19
|
+
import { cliPaths, layOutCopy } from "./stack.mjs";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {import("node:test").TestContext} t The test
|
|
23
|
+
* @returns {string} A real scratch folder removed when the test ends
|
|
24
|
+
*/
|
|
25
|
+
function scratch(t) {
|
|
26
|
+
const dir = realpathSync(
|
|
27
|
+
mkdtempSync(join(tmpdir(), "dms-control-renderer-test-")),
|
|
28
|
+
);
|
|
29
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} folder The package folder
|
|
35
|
+
* @param {string} name Its name
|
|
36
|
+
* @returns {string} The folder
|
|
37
|
+
*/
|
|
38
|
+
function writePackage(folder, name) {
|
|
39
|
+
mkdirSync(folder, { recursive: true });
|
|
40
|
+
writeFileSync(join(folder, "package.json"), JSON.stringify({ name }));
|
|
41
|
+
return folder;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} folder The renderer checkout
|
|
46
|
+
* @returns {string} The folder
|
|
47
|
+
*/
|
|
48
|
+
function builtRenderer(folder) {
|
|
49
|
+
writePackage(folder, "@antelopejs/dms-frontend");
|
|
50
|
+
mkdirSync(join(folder, "dist"), { recursive: true });
|
|
51
|
+
writeFileSync(join(folder, "dist", "index.js"), "");
|
|
52
|
+
return folder;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A checkout whose playground has installed modules: core, a published
|
|
57
|
+
* dms-frontend and one other package.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} root The scratch folder
|
|
60
|
+
* @returns {{ checkout: string, playground: string, modules: string }} Its paths
|
|
61
|
+
*/
|
|
62
|
+
function checkoutWithPlayground(root) {
|
|
63
|
+
const checkout = join(root, "dms");
|
|
64
|
+
const playground = join(checkout, "playground");
|
|
65
|
+
const modules = join(playground, "node_modules");
|
|
66
|
+
mkdirSync(playground, { recursive: true });
|
|
67
|
+
writeFileSync(join(playground, "antelope.config.ts"), "");
|
|
68
|
+
writePackage(join(modules, "@antelopejs", "core"), "@antelopejs/core");
|
|
69
|
+
writePackage(
|
|
70
|
+
join(modules, "@antelopejs", "dms-frontend"),
|
|
71
|
+
"@antelopejs/dms-frontend",
|
|
72
|
+
);
|
|
73
|
+
writePackage(join(modules, "zod"), "zod");
|
|
74
|
+
return { checkout, playground, modules };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await test("--renderer takes a built dms-frontend checkout as an absolute folder", (t) => {
|
|
78
|
+
const folder = builtRenderer(join(scratch(t), "dms-frontend"));
|
|
79
|
+
assert.equal(resolveRenderer(folder), folder);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await test("--renderer refuses a folder without a package.json", (t) => {
|
|
83
|
+
assert.throws(() => resolveRenderer(scratch(t)), /has no package\.json/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await test("--renderer refuses a package that is not dms-frontend", (t) => {
|
|
87
|
+
const folder = writePackage(join(scratch(t), "other"), "@antelopejs/dms");
|
|
88
|
+
assert.throws(
|
|
89
|
+
() => resolveRenderer(folder),
|
|
90
|
+
/is @antelopejs\/dms, not @antelopejs\/dms-frontend/,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await test("--renderer refuses a checkout that is not built", (t) => {
|
|
95
|
+
const folder = writePackage(
|
|
96
|
+
join(scratch(t), "dms-frontend"),
|
|
97
|
+
"@antelopejs/dms-frontend",
|
|
98
|
+
);
|
|
99
|
+
assert.throws(() => resolveRenderer(folder), /has no dist.index\.js/);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await test("the run's playground resolves dms-frontend to the renderer and everything else to the playground's modules", (t) => {
|
|
103
|
+
const root = scratch(t);
|
|
104
|
+
const { checkout, playground, modules } = checkoutWithPlayground(root);
|
|
105
|
+
const renderer = builtRenderer(join(root, "dms-frontend"));
|
|
106
|
+
const copy = layOutCopy(playground, checkout, join(root, "run"), renderer);
|
|
107
|
+
const linked = join(copy, "node_modules");
|
|
108
|
+
assert.equal(
|
|
109
|
+
realpathSync(join(linked, "@antelopejs", "dms-frontend")),
|
|
110
|
+
renderer,
|
|
111
|
+
);
|
|
112
|
+
assert.equal(
|
|
113
|
+
realpathSync(join(linked, "@antelopejs", "core")),
|
|
114
|
+
realpathSync(join(modules, "@antelopejs", "core")),
|
|
115
|
+
);
|
|
116
|
+
assert.equal(
|
|
117
|
+
realpathSync(join(linked, "zod")),
|
|
118
|
+
realpathSync(join(modules, "zod")),
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await test("linking a renderer leaves the playground's own modules alone", (t) => {
|
|
123
|
+
const root = scratch(t);
|
|
124
|
+
const { checkout, playground, modules } = checkoutWithPlayground(root);
|
|
125
|
+
const published = realpathSync(join(modules, "@antelopejs", "dms-frontend"));
|
|
126
|
+
layOutCopy(
|
|
127
|
+
playground,
|
|
128
|
+
checkout,
|
|
129
|
+
join(root, "run"),
|
|
130
|
+
builtRenderer(join(root, "dms-frontend")),
|
|
131
|
+
);
|
|
132
|
+
assert.equal(
|
|
133
|
+
realpathSync(join(modules, "@antelopejs", "dms-frontend")),
|
|
134
|
+
published,
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await test("a playground that installs no @antelopejs scope still gets the renderer", (t) => {
|
|
139
|
+
const root = scratch(t);
|
|
140
|
+
const { checkout, playground, modules } = checkoutWithPlayground(root);
|
|
141
|
+
rmSync(join(modules, "@antelopejs"), { recursive: true });
|
|
142
|
+
const renderer = builtRenderer(join(root, "dms-frontend"));
|
|
143
|
+
const copy = layOutCopy(playground, checkout, join(root, "run"), renderer);
|
|
144
|
+
assert.equal(
|
|
145
|
+
realpathSync(join(copy, "node_modules", "@antelopejs", "dms-frontend")),
|
|
146
|
+
renderer,
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await test("without a renderer the run's node_modules is the playground's", (t) => {
|
|
151
|
+
const root = scratch(t);
|
|
152
|
+
const { checkout, playground, modules } = checkoutWithPlayground(root);
|
|
153
|
+
const copy = layOutCopy(playground, checkout, join(root, "run"));
|
|
154
|
+
const linked = join(copy, "node_modules");
|
|
155
|
+
assert.equal(lstatSync(linked).isSymbolicLink(), true);
|
|
156
|
+
assert.equal(realpathSync(linked), realpathSync(modules));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await test("the dashboard CLI is the renderer's build, the backend CLI stays the playground's core", (t) => {
|
|
160
|
+
const root = scratch(t);
|
|
161
|
+
const { playground } = checkoutWithPlayground(root);
|
|
162
|
+
const renderer = builtRenderer(join(root, "dms-frontend"));
|
|
163
|
+
const published = cliPaths(playground);
|
|
164
|
+
const served = cliPaths(playground, renderer);
|
|
165
|
+
assert.equal(served.dms, join(renderer, "dist", "index.js"));
|
|
166
|
+
assert.notEqual(published.dms, served.dms);
|
|
167
|
+
assert.equal(served.ajs, published.ajs);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {string} root The scratch folder
|
|
172
|
+
* @param {string} config The text after `export default` in `verify.config.ts`
|
|
173
|
+
* @returns {string} The checkout holding a verification skill with that config
|
|
174
|
+
*/
|
|
175
|
+
function checkoutWithSkill(root, config) {
|
|
176
|
+
const checkout = join(root, "dms-frontend");
|
|
177
|
+
const skill = join(checkout, ".agents", "skills", "verify-dms-frontend");
|
|
178
|
+
mkdirSync(skill, { recursive: true });
|
|
179
|
+
writeFileSync(join(skill, "verify.config.ts"), `export default ${config};\n`);
|
|
180
|
+
git(["init", "--quiet"], checkout);
|
|
181
|
+
git(["add", "."], checkout);
|
|
182
|
+
git(
|
|
183
|
+
[
|
|
184
|
+
"-c",
|
|
185
|
+
"user.name=t",
|
|
186
|
+
"-c",
|
|
187
|
+
"user.email=t@example.com",
|
|
188
|
+
"commit",
|
|
189
|
+
"--quiet",
|
|
190
|
+
"-m",
|
|
191
|
+
"skill",
|
|
192
|
+
],
|
|
193
|
+
checkout,
|
|
194
|
+
);
|
|
195
|
+
return checkout;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const EMPTY_CONFIG = `prepare: [], userFacing: [], smoke: "", smokeJourney: "", doors: {}, formal: []`;
|
|
199
|
+
|
|
200
|
+
await test("up takes the playground and the renderer from the skill's config, relative ones from its repository", async (t) => {
|
|
201
|
+
const root = scratch(t);
|
|
202
|
+
const checkout = checkoutWithSkill(
|
|
203
|
+
root,
|
|
204
|
+
`{ playground: "/dms/playground", renderer: ".", ${EMPTY_CONFIG} }`,
|
|
205
|
+
);
|
|
206
|
+
assert.deepEqual(await targetOf({}, checkout), {
|
|
207
|
+
playground: "/dms/playground",
|
|
208
|
+
renderer: checkout,
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await test("--renderer and --playground win over the skill's config", async (t) => {
|
|
213
|
+
const checkout = checkoutWithSkill(
|
|
214
|
+
scratch(t),
|
|
215
|
+
`{ playground: "/dms/playground", renderer: ".", ${EMPTY_CONFIG} }`,
|
|
216
|
+
);
|
|
217
|
+
assert.deepEqual(
|
|
218
|
+
await targetOf(
|
|
219
|
+
{ playground: "/other/playground", renderer: "/other/ui" },
|
|
220
|
+
checkout,
|
|
221
|
+
),
|
|
222
|
+
{ playground: "/other/playground", renderer: "/other/ui" },
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await test("a skill without a renderer serves the published one", async (t) => {
|
|
227
|
+
const checkout = checkoutWithSkill(
|
|
228
|
+
scratch(t),
|
|
229
|
+
`{ playground: "packages/dms/playground", ${EMPTY_CONFIG} }`,
|
|
230
|
+
);
|
|
231
|
+
assert.deepEqual(await targetOf({}, checkout), {
|
|
232
|
+
playground: resolve(checkout, "packages/dms/playground"),
|
|
233
|
+
renderer: undefined,
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const CONFIG = {
|
|
238
|
+
playground: "p",
|
|
239
|
+
prepare: [],
|
|
240
|
+
userFacing: [],
|
|
241
|
+
smoke: "",
|
|
242
|
+
smokeJourney: "",
|
|
243
|
+
doors: {},
|
|
244
|
+
formal: [],
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
await test("a skill config keeps its renderer, and refuses one that is not a path", () => {
|
|
248
|
+
assert.equal(
|
|
249
|
+
decodeSkillConfig({ ...CONFIG, renderer: "." }, "verify.config.ts")
|
|
250
|
+
.renderer,
|
|
251
|
+
".",
|
|
252
|
+
);
|
|
253
|
+
assert.equal(
|
|
254
|
+
decodeSkillConfig(CONFIG, "verify.config.ts").renderer,
|
|
255
|
+
undefined,
|
|
256
|
+
);
|
|
257
|
+
assert.throws(
|
|
258
|
+
() => decodeSkillConfig({ ...CONFIG, renderer: 3 }, "verify.config.ts"),
|
|
259
|
+
/verify\.config\.ts/,
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await test("a run with a renderer has no base backend", async () => {
|
|
264
|
+
await assert.rejects(
|
|
265
|
+
withBaseBackend({ renderer: "/ui" }, {}, async () => {}),
|
|
266
|
+
/no base backend/,
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @param {Partial<import("./verify/skill.mjs").SkillConfig>} config What differs from a skill with a playground in its repository
|
|
272
|
+
* @returns {import("./verify/skill.mjs").Skill} The skill
|
|
273
|
+
*/
|
|
274
|
+
function skillWith(config) {
|
|
275
|
+
return {
|
|
276
|
+
dir: "/repo/.agents/skills/verify-x",
|
|
277
|
+
name: "verify-x",
|
|
278
|
+
repo: "/repo",
|
|
279
|
+
config: { ...decodeSkillConfig(CONFIG, "verify.config.ts"), ...config },
|
|
280
|
+
featuresDir: "",
|
|
281
|
+
journeysDir: "",
|
|
282
|
+
seededBreaksDir: "",
|
|
283
|
+
seededDoorsDir: "",
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await test("seeded starts each run on the worktree's renderer and an external playground as it is", () => {
|
|
288
|
+
const prepared = {
|
|
289
|
+
worktree: "/work/seeded-auth-1",
|
|
290
|
+
skill: "/work/seeded-auth-1/skill",
|
|
291
|
+
};
|
|
292
|
+
assert.deepEqual(
|
|
293
|
+
upArguments(
|
|
294
|
+
skillWith({ playground: "/dms/playground", renderer: "." }),
|
|
295
|
+
prepared,
|
|
296
|
+
),
|
|
297
|
+
[
|
|
298
|
+
"up",
|
|
299
|
+
"--playground",
|
|
300
|
+
"/dms/playground",
|
|
301
|
+
"--renderer",
|
|
302
|
+
"/work/seeded-auth-1",
|
|
303
|
+
"--skill",
|
|
304
|
+
"/work/seeded-auth-1/skill",
|
|
305
|
+
],
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await test("seeded without a renderer starts the worktree's own playground", () => {
|
|
310
|
+
const prepared = {
|
|
311
|
+
worktree: "/work/seeded-auth-1",
|
|
312
|
+
skill: "/work/seeded-auth-1/skill",
|
|
313
|
+
};
|
|
314
|
+
assert.deepEqual(
|
|
315
|
+
upArguments(skillWith({ playground: "packages/dms/playground" }), prepared),
|
|
316
|
+
[
|
|
317
|
+
"up",
|
|
318
|
+
"--playground",
|
|
319
|
+
"/work/seeded-auth-1/packages/dms/playground",
|
|
320
|
+
"--skill",
|
|
321
|
+
"/work/seeded-auth-1/skill",
|
|
322
|
+
],
|
|
323
|
+
);
|
|
324
|
+
});
|
|
@@ -2,11 +2,29 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
arrayOf,
|
|
7
|
+
decode,
|
|
8
|
+
either,
|
|
9
|
+
isBoolean,
|
|
10
|
+
isNumber,
|
|
11
|
+
isString,
|
|
12
|
+
nullable,
|
|
13
|
+
objectOf,
|
|
14
|
+
recordOf,
|
|
15
|
+
} from "./json.mjs";
|
|
5
16
|
|
|
6
17
|
const RUN_FILE = "run.json";
|
|
7
18
|
const ID_BYTES = 3;
|
|
8
19
|
const TIMESTAMP_LENGTH = 15;
|
|
9
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{ name: string, pid: number, command: string, cwd: string, log: string, startedAt: string, port?: number }} ProcessRecord
|
|
23
|
+
* @typedef {ProcessRecord & { port: number }} ServerRecord
|
|
24
|
+
* @typedef {{ email: string, password: string, userId?: string | null }} Account
|
|
25
|
+
* @typedef {{ dir: string, root: string, home: string, logs: string, mongo: string, browser?: ServerRecord, evidence: string, cache: string, id: string, createdAt: string, environment: string, playground: string, moduleRoot: string, repo: string | null, headSha: string | null, renderer?: string, baseRef: string, skill: string | null, apiPort: number, webPort: number, smtpPort: number, mail: string, apiUrl: string, webUrl: string, databasePrefix: string, database: string, mongoUrl: string, ownsMongo: boolean, secrets: Record<string, string>, adminPassword: string, extraEnv: Record<string, string>, processes: ProcessRecord[], accounts: Record<string, Account>, tokens: Record<string, string>, ready: boolean, error?: string, runPlayground?: string, baseRoot?: string, resetCount?: number, journeyCount?: number }} RunState
|
|
26
|
+
*/
|
|
27
|
+
|
|
10
28
|
/**
|
|
11
29
|
* Where runs are created: `--runs`, else `DMS_CONTROL_RUNS`, else
|
|
12
30
|
* `<tmpdir>/dms-control`. A run holds a playground copy and its frontend
|
|
@@ -39,7 +57,7 @@ export function newRunId() {
|
|
|
39
57
|
* The folders of a run, all under its root.
|
|
40
58
|
*
|
|
41
59
|
* @param {string} dir The run root
|
|
42
|
-
* @returns {{ dir: string, root: string, home: string, logs: string, mongo: string,
|
|
60
|
+
* @returns {{ dir: string, root: string, home: string, logs: string, mongo: string, evidence: string, cache: string }} The run's folders
|
|
43
61
|
*/
|
|
44
62
|
export function runPaths(dir) {
|
|
45
63
|
return {
|
|
@@ -48,7 +66,6 @@ export function runPaths(dir) {
|
|
|
48
66
|
home: join(dir, "home"),
|
|
49
67
|
logs: join(dir, "logs"),
|
|
50
68
|
mongo: join(dir, "mongo"),
|
|
51
|
-
browser: join(dir, "browser"),
|
|
52
69
|
evidence: join(dir, "evidence"),
|
|
53
70
|
cache: join(dir, "cache"),
|
|
54
71
|
};
|
|
@@ -80,10 +97,93 @@ export function createRunFolders(dir) {
|
|
|
80
97
|
}
|
|
81
98
|
|
|
82
99
|
/**
|
|
83
|
-
*
|
|
100
|
+
* @typedef {Omit<RunState, "browser" | "accounts"> & { browser?: string | ServerRecord, accounts: Record<string, Omit<Account, "password"> & { password?: string }> }} StoredRunState The state as a file may hold it. A run saved by an earlier release holds the browser's folder in `browser` until the browser starts, and `down` leaves an account without its `password`.
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
const isStrings = recordOf(isString);
|
|
104
|
+
|
|
105
|
+
const isProcessRecord = objectOf(
|
|
106
|
+
{
|
|
107
|
+
name: isString,
|
|
108
|
+
pid: isNumber,
|
|
109
|
+
command: isString,
|
|
110
|
+
cwd: isString,
|
|
111
|
+
log: isString,
|
|
112
|
+
startedAt: isString,
|
|
113
|
+
},
|
|
114
|
+
{ port: isNumber },
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const isServerRecord = objectOf({
|
|
118
|
+
name: isString,
|
|
119
|
+
pid: isNumber,
|
|
120
|
+
command: isString,
|
|
121
|
+
cwd: isString,
|
|
122
|
+
log: isString,
|
|
123
|
+
startedAt: isString,
|
|
124
|
+
port: isNumber,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const isAccount = objectOf(
|
|
128
|
+
{ email: isString },
|
|
129
|
+
{ password: isString, userId: nullable(isString) },
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
/** @type {import("./json.mjs").Guard<StoredRunState>} The state as `run.json` holds it. Keys it does not check are kept, so saving a loaded run writes them back. */
|
|
133
|
+
const isStoredRun = objectOf(
|
|
134
|
+
{
|
|
135
|
+
dir: isString,
|
|
136
|
+
root: isString,
|
|
137
|
+
home: isString,
|
|
138
|
+
logs: isString,
|
|
139
|
+
mongo: isString,
|
|
140
|
+
evidence: isString,
|
|
141
|
+
cache: isString,
|
|
142
|
+
id: isString,
|
|
143
|
+
createdAt: isString,
|
|
144
|
+
environment: isString,
|
|
145
|
+
playground: isString,
|
|
146
|
+
moduleRoot: isString,
|
|
147
|
+
repo: nullable(isString),
|
|
148
|
+
headSha: nullable(isString),
|
|
149
|
+
baseRef: isString,
|
|
150
|
+
skill: nullable(isString),
|
|
151
|
+
apiPort: isNumber,
|
|
152
|
+
webPort: isNumber,
|
|
153
|
+
smtpPort: isNumber,
|
|
154
|
+
mail: isString,
|
|
155
|
+
apiUrl: isString,
|
|
156
|
+
webUrl: isString,
|
|
157
|
+
databasePrefix: isString,
|
|
158
|
+
database: isString,
|
|
159
|
+
mongoUrl: isString,
|
|
160
|
+
ownsMongo: isBoolean,
|
|
161
|
+
secrets: isStrings,
|
|
162
|
+
adminPassword: isString,
|
|
163
|
+
extraEnv: isStrings,
|
|
164
|
+
processes: arrayOf(isProcessRecord),
|
|
165
|
+
accounts: recordOf(isAccount),
|
|
166
|
+
tokens: isStrings,
|
|
167
|
+
ready: isBoolean,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
browser: either(isString, isServerRecord),
|
|
171
|
+
error: isString,
|
|
172
|
+
renderer: isString,
|
|
173
|
+
runPlayground: isString,
|
|
174
|
+
baseRoot: isString,
|
|
175
|
+
resetCount: isNumber,
|
|
176
|
+
journeyCount: isNumber,
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Reads the state `up` recorded for a run. A folder an earlier release stored
|
|
182
|
+
* in `browser` is dropped, since nothing creates or reads that folder. An
|
|
183
|
+
* account that `down` left without its `password` reads with an empty one.
|
|
84
184
|
*
|
|
85
185
|
* @param {string} dir The run root, as `up` printed it
|
|
86
|
-
* @returns {
|
|
186
|
+
* @returns {RunState} The run state
|
|
87
187
|
* @throws When the folder holds no run.json
|
|
88
188
|
*/
|
|
89
189
|
export function loadRun(dir) {
|
|
@@ -93,14 +193,27 @@ export function loadRun(dir) {
|
|
|
93
193
|
`No run at ${dir}: run.json is missing. Start one with dms-control up.`,
|
|
94
194
|
);
|
|
95
195
|
}
|
|
96
|
-
|
|
196
|
+
/** @type {unknown} */
|
|
197
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
198
|
+
const stored = decode(isStoredRun, parsed, file, "a run state");
|
|
199
|
+
const { browser, accounts, ...rest } = stored;
|
|
200
|
+
const run = {
|
|
201
|
+
...rest,
|
|
202
|
+
accounts: Object.fromEntries(
|
|
203
|
+
Object.entries(accounts).map(([name, account]) => [
|
|
204
|
+
name,
|
|
205
|
+
{ ...account, password: account.password ?? "" },
|
|
206
|
+
]),
|
|
207
|
+
),
|
|
208
|
+
};
|
|
209
|
+
return browser === undefined || isString(browser) ? run : { ...run, browser };
|
|
97
210
|
}
|
|
98
211
|
|
|
99
212
|
/**
|
|
100
213
|
* Writes the run state, readable by its owner only since it holds the run's
|
|
101
214
|
* secrets and the admin password.
|
|
102
215
|
*
|
|
103
|
-
* @param {
|
|
216
|
+
* @param {RunState} state The run state
|
|
104
217
|
*/
|
|
105
218
|
export function saveRun(state) {
|
|
106
219
|
writeFileSync(
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { createRunFolders, loadRun, saveRun } from "./run-state.mjs";
|
|
7
|
+
import { down } from "./stack.mjs";
|
|
8
|
+
|
|
9
|
+
const SERVER = {
|
|
10
|
+
name: "browser",
|
|
11
|
+
pid: 4242,
|
|
12
|
+
command: "node serve",
|
|
13
|
+
cwd: "/run",
|
|
14
|
+
log: "/run/logs/browser.log",
|
|
15
|
+
startedAt: "2026-10-31T00:00:00.000Z",
|
|
16
|
+
port: 40123,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const RUN = {
|
|
20
|
+
root: "/run/root",
|
|
21
|
+
home: "/run/home",
|
|
22
|
+
logs: "/run/logs",
|
|
23
|
+
mongo: "/run/mongo",
|
|
24
|
+
evidence: "/run/evidence",
|
|
25
|
+
cache: "/run/cache",
|
|
26
|
+
id: "20261031T000000-abc123",
|
|
27
|
+
createdAt: "2026-10-31T00:00:00.000Z",
|
|
28
|
+
environment: "verify",
|
|
29
|
+
playground: "/repo/playground",
|
|
30
|
+
moduleRoot: "/repo",
|
|
31
|
+
repo: "/repo",
|
|
32
|
+
headSha: null,
|
|
33
|
+
baseRef: "origin/main",
|
|
34
|
+
skill: null,
|
|
35
|
+
apiPort: 5010,
|
|
36
|
+
webPort: 5011,
|
|
37
|
+
smtpPort: 5012,
|
|
38
|
+
mail: "/run/evidence/mail",
|
|
39
|
+
apiUrl: "http://127.0.0.1:5010",
|
|
40
|
+
webUrl: "http://127.0.0.1:5011",
|
|
41
|
+
databasePrefix: "verify_x",
|
|
42
|
+
database: "verify_x",
|
|
43
|
+
mongoUrl: "",
|
|
44
|
+
ownsMongo: true,
|
|
45
|
+
secrets: { JWT: "s" },
|
|
46
|
+
adminPassword: "pw",
|
|
47
|
+
extraEnv: {},
|
|
48
|
+
processes: [],
|
|
49
|
+
accounts: { admin: { email: "a@example.com", password: "pw" } },
|
|
50
|
+
tokens: {},
|
|
51
|
+
ready: false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {import("node:test").TestContext} t The test
|
|
56
|
+
* @param {Record<string, unknown>} fields What the saved file holds besides `dir` and the fields every run has
|
|
57
|
+
* @returns {string} The run folder
|
|
58
|
+
*/
|
|
59
|
+
function savedRun(t, fields) {
|
|
60
|
+
const dir = mkdtempSync(join(tmpdir(), "dms-control-run-state-test-"));
|
|
61
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
62
|
+
mkdirSync(dir, { recursive: true });
|
|
63
|
+
writeFileSync(
|
|
64
|
+
join(dir, "run.json"),
|
|
65
|
+
JSON.stringify({ ...RUN, dir, ...fields }),
|
|
66
|
+
);
|
|
67
|
+
return dir;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await test("a run saved by an earlier release drops the browser folder it held in `browser`", (t) => {
|
|
71
|
+
const dir = savedRun(t, { browser: "/runs/old/browser" });
|
|
72
|
+
const state = loadRun(dir);
|
|
73
|
+
assert.equal("browser" in state, false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await test("a run saved by an earlier release keeps its server record once the browser started", (t) => {
|
|
77
|
+
const dir = savedRun(t, { browser: SERVER });
|
|
78
|
+
assert.deepEqual(loadRun(dir).browser, SERVER);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await test("a run without a browser reads back without one", (t) => {
|
|
82
|
+
const dir = savedRun(t, {});
|
|
83
|
+
assert.equal("browser" in loadRun(dir), false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await test("saving a loaded run again keeps its server record", (t) => {
|
|
87
|
+
const dir = savedRun(t, { browser: SERVER });
|
|
88
|
+
const state = loadRun(dir);
|
|
89
|
+
saveRun(state);
|
|
90
|
+
assert.deepEqual(loadRun(dir), state);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await test("a run.json that holds no run is refused", (t) => {
|
|
94
|
+
for (const text of ["null", "[]", "5", '"s"', "{}"]) {
|
|
95
|
+
const dir = mkdtempSync(join(tmpdir(), "dms-control-run-state-test-"));
|
|
96
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
97
|
+
writeFileSync(join(dir, "run.json"), text);
|
|
98
|
+
assert.throws(
|
|
99
|
+
() => loadRun(dir),
|
|
100
|
+
/run\.json is malformed: expected a run state/,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await test("a run.json whose field has the wrong type is refused", (t) => {
|
|
106
|
+
const dir = savedRun(t, { ready: "yes" });
|
|
107
|
+
assert.throws(() => loadRun(dir), /malformed: expected a run state/);
|
|
108
|
+
const processes = savedRun(t, { processes: [{ name: "api", pid: "7" }] });
|
|
109
|
+
assert.throws(() => loadRun(processes), /malformed: expected a run state/);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await test("keys of a run.json that the schema does not name are written back", (t) => {
|
|
113
|
+
const dir = savedRun(t, { addedByANewerRelease: 1 });
|
|
114
|
+
saveRun(loadRun(dir));
|
|
115
|
+
assert.equal(
|
|
116
|
+
Object.getOwnPropertyDescriptor(loadRun(dir), "addedByANewerRelease")
|
|
117
|
+
?.value,
|
|
118
|
+
1,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await test("a new run's folders hold no browser folder, and loading a fresh run has no server", (t) => {
|
|
123
|
+
const parent = mkdtempSync(join(tmpdir(), "dms-control-run-state-test-"));
|
|
124
|
+
t.after(() => rmSync(parent, { recursive: true, force: true }));
|
|
125
|
+
const dir = join(parent, "run");
|
|
126
|
+
const paths = createRunFolders(dir);
|
|
127
|
+
assert.equal("browser" in paths, false);
|
|
128
|
+
saveRun({ ...RUN, ...paths, id: "x" });
|
|
129
|
+
assert.equal("browser" in loadRun(dir), false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await test("a run that down stopped loads again, its accounts without a password", async (t) => {
|
|
133
|
+
const dir = savedRun(t, {
|
|
134
|
+
accounts: {
|
|
135
|
+
admin: { email: "a@example.com", password: "pw", userId: "u1" },
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
await down(loadRun(dir));
|
|
139
|
+
const stopped = loadRun(dir);
|
|
140
|
+
assert.deepEqual(stopped.accounts, {
|
|
141
|
+
admin: { email: "a@example.com", password: "" },
|
|
142
|
+
});
|
|
143
|
+
assert.equal(stopped.ready, false);
|
|
144
|
+
await down(stopped);
|
|
145
|
+
assert.deepEqual(loadRun(dir).accounts, {
|
|
146
|
+
admin: { email: "a@example.com", password: "" },
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await test("a run.json without its accounts, or with an account without an email, is refused", (t) => {
|
|
151
|
+
const withoutAccounts = savedRun(t, {});
|
|
152
|
+
writeFileSync(
|
|
153
|
+
join(withoutAccounts, "run.json"),
|
|
154
|
+
JSON.stringify({ ...RUN, dir: withoutAccounts, accounts: undefined }),
|
|
155
|
+
);
|
|
156
|
+
assert.throws(() => loadRun(withoutAccounts), /malformed/);
|
|
157
|
+
const noEmail = savedRun(t, { accounts: { admin: { password: "pw" } } });
|
|
158
|
+
assert.throws(() => loadRun(noEmail), /malformed/);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await test("a run keeps the renderer checkout it serves across a save and a load", (t) => {
|
|
162
|
+
const dir = savedRun(t, { renderer: "/work/dms-frontend" });
|
|
163
|
+
const state = loadRun(dir);
|
|
164
|
+
assert.equal(state.renderer, "/work/dms-frontend");
|
|
165
|
+
saveRun(state);
|
|
166
|
+
assert.equal(loadRun(dir).renderer, "/work/dms-frontend");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await test("a run whose renderer is not a path is refused", (t) => {
|
|
170
|
+
assert.throws(() => loadRun(savedRun(t, { renderer: 3 })), /malformed/);
|
|
171
|
+
});
|