@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,298 @@
|
|
|
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 { arrayOf, decode, isNumber, isString, objectOf } from "./json.mjs";
|
|
7
|
+
import { dropDatabase, dropRunDatabases, mongodBinary } from "./mongo.mjs";
|
|
8
|
+
|
|
9
|
+
const isDriverLog = objectOf({
|
|
10
|
+
label: isString,
|
|
11
|
+
built: isNumber,
|
|
12
|
+
closed: isNumber,
|
|
13
|
+
dropped: arrayOf(isString),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import("node:test").TestContext} t
|
|
18
|
+
* @param {Record<string, string>} packages Package name to the source of its `index.js`
|
|
19
|
+
* @returns {string} The folder whose `node_modules` holds them
|
|
20
|
+
*/
|
|
21
|
+
function project(t, packages) {
|
|
22
|
+
const folder = mkdtempSync(join(tmpdir(), "dms-control-mongo-test-"));
|
|
23
|
+
t.after(() => rmSync(folder, { recursive: true, force: true }));
|
|
24
|
+
writeFileSync(join(folder, "package.json"), "{}");
|
|
25
|
+
for (const [name, source] of Object.entries(packages)) {
|
|
26
|
+
const root = join(folder, "node_modules", name);
|
|
27
|
+
mkdirSync(root, { recursive: true });
|
|
28
|
+
writeFileSync(join(root, "package.json"), `{"name":"${name}"}`);
|
|
29
|
+
writeFileSync(join(root, "index.js"), source);
|
|
30
|
+
}
|
|
31
|
+
return folder;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A driver whose client lists databases and records what is done to it in `globalThis.fakeMongo`, labelled by `label`.
|
|
36
|
+
* @param {{ label?: string, names?: string, listing?: string, client?: string, exported?: string }} [shape] `exported` is the expression the module exports as `MongoClient`
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function fakeDriver({
|
|
40
|
+
label = "driver",
|
|
41
|
+
names = '["verify_run1_a", "verify_run1_b", "other", "verify_run2_a"]',
|
|
42
|
+
listing = `{ databases: ${names}.map((name) => ({ name })), ok: 1 }`,
|
|
43
|
+
client = "",
|
|
44
|
+
exported = "MongoClient",
|
|
45
|
+
} = {}) {
|
|
46
|
+
return `
|
|
47
|
+
const state = { label: "${label}", built: 0, closed: 0, dropped: [] };
|
|
48
|
+
Object.defineProperty(globalThis, "fakeMongo", { value: state, configurable: true });
|
|
49
|
+
class MongoClient {
|
|
50
|
+
constructor() { state.built += 1; }
|
|
51
|
+
async connect() {}
|
|
52
|
+
async close() { state.closed += 1; }
|
|
53
|
+
db(name) {
|
|
54
|
+
return {
|
|
55
|
+
admin: () => ({ listDatabases: async () => (${listing}) }),
|
|
56
|
+
dropDatabase: async () => { state.dropped.push(name); },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
${client}
|
|
61
|
+
module.exports = { MongoClient: ${exported} };
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @returns {{ label: string, built: number, closed: number, dropped: string[] }} What was done to the client built last
|
|
67
|
+
*/
|
|
68
|
+
function driverLog() {
|
|
69
|
+
/** @type {unknown} */
|
|
70
|
+
const recorded = Object.getOwnPropertyDescriptor(
|
|
71
|
+
globalThis,
|
|
72
|
+
"fakeMongo",
|
|
73
|
+
)?.value;
|
|
74
|
+
return decode(isDriverLog, recorded, "fakeMongo", "the driver log");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await test("the run's databases are dropped and every other one is kept", async (t) => {
|
|
78
|
+
const folder = project(t, { mongodb: fakeDriver() });
|
|
79
|
+
const lines = await dropRunDatabases("mongodb://x", "verify_run1", [folder]);
|
|
80
|
+
assert.deepEqual(lines, [
|
|
81
|
+
"dropped database verify_run1_a",
|
|
82
|
+
"dropped database verify_run1_b",
|
|
83
|
+
]);
|
|
84
|
+
assert.deepEqual(driverLog().dropped, ["verify_run1_a", "verify_run1_b"]);
|
|
85
|
+
assert.equal(driverLog().closed, 1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await test("one database is dropped by name", async (t) => {
|
|
89
|
+
const folder = project(t, { mongodb: fakeDriver() });
|
|
90
|
+
await dropDatabase("mongodb://x", "verify_run1_a", [folder]);
|
|
91
|
+
assert.deepEqual(driverLog().dropped, ["verify_run1_a"]);
|
|
92
|
+
assert.equal(driverLog().closed, 1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await test("the driver beside mongodb-memory-server-core wins over an earlier folder's own", async (t) => {
|
|
96
|
+
const first = project(t, { mongodb: fakeDriver({ label: "first" }) });
|
|
97
|
+
const second = project(t, {
|
|
98
|
+
mongodb: fakeDriver({ label: "second" }),
|
|
99
|
+
"mongodb-memory-server-core": "module.exports = {};",
|
|
100
|
+
});
|
|
101
|
+
await dropDatabase("mongodb://x", "verify_run1_a", [first, second]);
|
|
102
|
+
assert.equal(driverLog().label, "second");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await test("a list of databases with a nameless entry is refused and the client still closes", async (t) => {
|
|
106
|
+
const folder = project(t, {
|
|
107
|
+
mongodb: fakeDriver({ listing: "{ databases: [{ id: 1 }] }" }),
|
|
108
|
+
});
|
|
109
|
+
await assert.rejects(
|
|
110
|
+
dropRunDatabases("mongodb://x", "verify_run1", [folder]),
|
|
111
|
+
{
|
|
112
|
+
message: "listDatabases is malformed: expected a list of databases",
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
assert.equal(driverLog().closed, 1);
|
|
116
|
+
assert.deepEqual(driverLog().dropped, []);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await test("a driver without MongoClient is refused", async (t) => {
|
|
120
|
+
const folder = project(t, {
|
|
121
|
+
mongodb: "module.exports = { MongoClient: 1 };",
|
|
122
|
+
});
|
|
123
|
+
await assert.rejects(dropDatabase("mongodb://x", "a", [folder]), {
|
|
124
|
+
message: "The MongoDB driver exports no MongoClient",
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await test("an arrow function is no MongoClient and is never called", async (t) => {
|
|
129
|
+
const folder = project(t, {
|
|
130
|
+
mongodb: fakeDriver({ exported: "(url) => { state.built += 1; }" }),
|
|
131
|
+
});
|
|
132
|
+
await assert.rejects(dropDatabase("mongodb://x", "a", [folder]), {
|
|
133
|
+
message: "The MongoDB driver exports no MongoClient",
|
|
134
|
+
});
|
|
135
|
+
assert.equal(driverLog().built, 0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await test("a class is a MongoClient and is built once", async (t) => {
|
|
139
|
+
const folder = project(t, { mongodb: fakeDriver() });
|
|
140
|
+
await dropDatabase("mongodb://x", "a", [folder]);
|
|
141
|
+
assert.equal(driverLog().built, 1);
|
|
142
|
+
assert.deepEqual(driverLog().dropped, ["a"]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await test("a bound class is a MongoClient and is built once", async (t) => {
|
|
146
|
+
const folder = project(t, {
|
|
147
|
+
mongodb: fakeDriver({ exported: "MongoClient.bind(null)" }),
|
|
148
|
+
});
|
|
149
|
+
await dropDatabase("mongodb://x", "a", [folder]);
|
|
150
|
+
assert.equal(driverLog().built, 1);
|
|
151
|
+
assert.deepEqual(driverLog().dropped, ["a"]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
for (const member of ["connect", "close", "db"]) {
|
|
155
|
+
await test(`a client whose ${member} is no function is refused before it is used`, async (t) => {
|
|
156
|
+
const folder = project(t, {
|
|
157
|
+
mongodb: fakeDriver({ client: `MongoClient.prototype.${member} = 1;` }),
|
|
158
|
+
});
|
|
159
|
+
await assert.rejects(dropDatabase("mongodb://x", "a", [folder]), {
|
|
160
|
+
message: "MongoClient built something that is no client",
|
|
161
|
+
});
|
|
162
|
+
assert.deepEqual(driverLog().dropped, []);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await test("a client without close is refused before it is used", async (t) => {
|
|
167
|
+
const folder = project(t, {
|
|
168
|
+
mongodb: fakeDriver({ client: "delete MongoClient.prototype.close;" }),
|
|
169
|
+
});
|
|
170
|
+
await assert.rejects(dropDatabase("mongodb://x", "a", [folder]), {
|
|
171
|
+
message: "MongoClient built something that is no client",
|
|
172
|
+
});
|
|
173
|
+
assert.deepEqual(driverLog().dropped, []);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const NO_DATABASES = [
|
|
177
|
+
["nothing", "{}"],
|
|
178
|
+
["no admin", "{ dropDatabase: async () => {} }"],
|
|
179
|
+
["no dropDatabase", "{ admin: () => ({}) }"],
|
|
180
|
+
[
|
|
181
|
+
"an admin that is no function",
|
|
182
|
+
"{ admin: 1, dropDatabase: async () => {} }",
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
"a dropDatabase that is no function",
|
|
186
|
+
"{ admin: () => ({}), dropDatabase: 1 }",
|
|
187
|
+
],
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
for (const [kind, database] of NO_DATABASES) {
|
|
191
|
+
await test(`a driver whose db() answers ${kind} is refused`, async (t) => {
|
|
192
|
+
const folder = project(t, {
|
|
193
|
+
mongodb: fakeDriver({
|
|
194
|
+
client: `MongoClient.prototype.db = () => (${database});`,
|
|
195
|
+
}),
|
|
196
|
+
});
|
|
197
|
+
await assert.rejects(dropDatabase("mongodb://x", "a", [folder]), {
|
|
198
|
+
message: "MongoClient.db(a) returned no database",
|
|
199
|
+
});
|
|
200
|
+
assert.equal(driverLog().closed, 1);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const NO_LIST_DATABASES = [
|
|
205
|
+
["no listDatabases", "{}"],
|
|
206
|
+
["a listDatabases that is no function", "{ listDatabases: 1 }"],
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
for (const [kind, admin] of NO_LIST_DATABASES) {
|
|
210
|
+
await test(`an admin handle with ${kind} is refused and drops nothing`, async (t) => {
|
|
211
|
+
const folder = project(t, {
|
|
212
|
+
mongodb: fakeDriver({
|
|
213
|
+
client: `MongoClient.prototype.db = () => ({ admin: () => (${admin}), dropDatabase: async () => { state.dropped.push("any"); } });`,
|
|
214
|
+
}),
|
|
215
|
+
});
|
|
216
|
+
await assert.rejects(
|
|
217
|
+
dropRunDatabases("mongodb://x", "verify_run1", [folder]),
|
|
218
|
+
{
|
|
219
|
+
message: "The admin handle of the driver has no listDatabases",
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
assert.equal(driverLog().closed, 1);
|
|
223
|
+
assert.deepEqual(driverLog().dropped, []);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await test("a missing driver names the devDependency to add", async (t) => {
|
|
228
|
+
const folder = project(t, {});
|
|
229
|
+
await assert.rejects(dropDatabase("mongodb://x", "a", [folder]), {
|
|
230
|
+
message:
|
|
231
|
+
"dms-control needs mongodb: add it to the playground's devDependencies.",
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @param {import("node:test").TestContext} t
|
|
237
|
+
* @param {string} resolver The source of the `getPath` the package exports
|
|
238
|
+
* @returns {string}
|
|
239
|
+
*/
|
|
240
|
+
function memoryServer(t, resolver) {
|
|
241
|
+
return project(t, {
|
|
242
|
+
"mongodb-memory-server-core": `
|
|
243
|
+
module.exports = { MongoBinary: { getPath: async (options) => { Object.defineProperty(globalThis, "fakeBinaryRequest", { value: options, configurable: true }); return ${resolver}; } } };
|
|
244
|
+
`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @param {import("node:test").TestContext} t
|
|
250
|
+
*/
|
|
251
|
+
function withoutConfiguredMongod(t) {
|
|
252
|
+
const saved = { ...process.env };
|
|
253
|
+
t.after(() => {
|
|
254
|
+
process.env = saved;
|
|
255
|
+
});
|
|
256
|
+
delete process.env.DMS_CONTROL_MONGOD;
|
|
257
|
+
process.env.PATH = "";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
await test("the binary comes from the memory server package when none is configured", async (t) => {
|
|
261
|
+
withoutConfiguredMongod(t);
|
|
262
|
+
const folder = memoryServer(t, '"/cache/mongod"');
|
|
263
|
+
assert.equal(await mongodBinary([folder]), "/cache/mongod");
|
|
264
|
+
assert.deepEqual(
|
|
265
|
+
Object.getOwnPropertyDescriptor(globalThis, "fakeBinaryRequest")?.value,
|
|
266
|
+
{ version: "8.0.8" },
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
await test("a binary path that is no string is refused", async (t) => {
|
|
271
|
+
withoutConfiguredMongod(t);
|
|
272
|
+
const folder = memoryServer(t, "42");
|
|
273
|
+
await assert.rejects(mongodBinary([folder]), {
|
|
274
|
+
message:
|
|
275
|
+
"MongoBinary.getPath is malformed: expected the path of a mongod binary",
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await test("a MongoBinary whose getPath is no function is refused", async (t) => {
|
|
280
|
+
withoutConfiguredMongod(t);
|
|
281
|
+
const folder = project(t, {
|
|
282
|
+
"mongodb-memory-server-core":
|
|
283
|
+
"module.exports = { MongoBinary: { getPath: 42 } };",
|
|
284
|
+
});
|
|
285
|
+
await assert.rejects(mongodBinary([folder]), {
|
|
286
|
+
message: "mongodb-memory-server-core exports no MongoBinary",
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
await test("a memory server package without MongoBinary is refused", async (t) => {
|
|
291
|
+
withoutConfiguredMongod(t);
|
|
292
|
+
const folder = project(t, {
|
|
293
|
+
"mongodb-memory-server-core": "module.exports = {};",
|
|
294
|
+
});
|
|
295
|
+
await assert.rejects(mongodBinary([folder]), {
|
|
296
|
+
message: "mongodb-memory-server-core exports no MongoBinary",
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command line of dms-control: every option any command reads.
|
|
3
|
+
*/
|
|
4
|
+
export const OPTIONS = /** @type {const} */ ({
|
|
5
|
+
prepare: { type: "boolean" },
|
|
6
|
+
playground: { type: "string" },
|
|
7
|
+
renderer: { type: "string" },
|
|
8
|
+
runs: { type: "string" },
|
|
9
|
+
"mongo-url": { type: "string" },
|
|
10
|
+
env: { type: "string", multiple: true },
|
|
11
|
+
base: { type: "string", default: "origin/main" },
|
|
12
|
+
skill: { type: "string" },
|
|
13
|
+
verbose: { type: "boolean" },
|
|
14
|
+
"platform-name": { type: "string" },
|
|
15
|
+
as: { type: "string" },
|
|
16
|
+
tab: { type: "string" },
|
|
17
|
+
role: { type: "string" },
|
|
18
|
+
name: { type: "string" },
|
|
19
|
+
exact: { type: "boolean" },
|
|
20
|
+
nth: { type: "string" },
|
|
21
|
+
"within-role": { type: "string" },
|
|
22
|
+
"within-name": { type: "string" },
|
|
23
|
+
value: { type: "string" },
|
|
24
|
+
key: { type: "string" },
|
|
25
|
+
path: { type: "string" },
|
|
26
|
+
body: { type: "string" },
|
|
27
|
+
pr: { type: "string" },
|
|
28
|
+
all: { type: "boolean" },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {ReturnType<typeof import("node:util").parseArgs<{ options: typeof OPTIONS, allowPositionals: true }>>["values"]} Values The options of one command line
|
|
33
|
+
*/
|
|
@@ -17,6 +17,14 @@ const PROBE_TIMEOUT_MS = 3_000;
|
|
|
17
17
|
const LISTEN_STATE = "0A";
|
|
18
18
|
const HEX = 16;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {string | import("node:net").AddressInfo | null} address What a listening server reports
|
|
22
|
+
* @returns {address is import("node:net").AddressInfo} True for the address of a TCP listener
|
|
23
|
+
*/
|
|
24
|
+
function isTcpAddress(address) {
|
|
25
|
+
return typeof address === "object" && address !== null;
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
/**
|
|
21
29
|
* Asks the kernel for a free TCP port on the loopback interface.
|
|
22
30
|
*
|
|
@@ -27,8 +35,14 @@ export function freePort() {
|
|
|
27
35
|
const server = createServer();
|
|
28
36
|
server.once("error", reject);
|
|
29
37
|
server.listen(0, "127.0.0.1", () => {
|
|
30
|
-
const
|
|
31
|
-
|
|
38
|
+
const address = server.address();
|
|
39
|
+
if (!isTcpAddress(address)) {
|
|
40
|
+
server.close(() =>
|
|
41
|
+
reject(new Error("the probe server has no TCP port")),
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
server.close(() => resolve(address.port));
|
|
32
46
|
});
|
|
33
47
|
});
|
|
34
48
|
}
|
|
@@ -150,6 +164,7 @@ export async function waitForOk(url, record, timeoutMs) {
|
|
|
150
164
|
);
|
|
151
165
|
}
|
|
152
166
|
|
|
167
|
+
/** @param {number} port */
|
|
153
168
|
function accepts(port) {
|
|
154
169
|
return new Promise((resolve) => {
|
|
155
170
|
const socket = createConnection({ host: "127.0.0.1", port });
|
|
@@ -213,6 +228,7 @@ function procEntries() {
|
|
|
213
228
|
}
|
|
214
229
|
}
|
|
215
230
|
|
|
231
|
+
/** @param {string} pid */
|
|
216
232
|
function parentOf(pid) {
|
|
217
233
|
try {
|
|
218
234
|
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
@@ -247,6 +263,7 @@ export function descendantsOf(pids) {
|
|
|
247
263
|
return [...found].filter((pid) => !pids.includes(pid));
|
|
248
264
|
}
|
|
249
265
|
|
|
266
|
+
/** @param {number} port */
|
|
250
267
|
function listeningInodes(port) {
|
|
251
268
|
const inodes = new Set();
|
|
252
269
|
for (const table of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
@@ -266,6 +283,7 @@ function listeningInodes(port) {
|
|
|
266
283
|
return inodes;
|
|
267
284
|
}
|
|
268
285
|
|
|
286
|
+
/** @param {number} pid */
|
|
269
287
|
function socketInodes(pid) {
|
|
270
288
|
try {
|
|
271
289
|
return readdirSync(`/proc/${pid}/fd`).flatMap((fd) => {
|
|
@@ -293,6 +311,10 @@ export function listenersOf(port) {
|
|
|
293
311
|
.filter((pid) => socketInodes(pid).some((inode) => inodes.has(inode)));
|
|
294
312
|
}
|
|
295
313
|
|
|
314
|
+
/**
|
|
315
|
+
* @param {number} pid
|
|
316
|
+
* @param {NodeJS.Signals} signal
|
|
317
|
+
*/
|
|
296
318
|
function signalGroup(pid, signal) {
|
|
297
319
|
try {
|
|
298
320
|
process.kill(-pid, signal);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { stringField } from "./json.mjs";
|
|
4
|
+
|
|
5
|
+
/** The package a renderer checkout must be. */
|
|
6
|
+
export const RENDERER_PACKAGE = "@antelopejs/dms-frontend";
|
|
7
|
+
|
|
8
|
+
/** The built CLI of a renderer checkout, relative to it. */
|
|
9
|
+
export const RENDERER_CLI = join("dist", "index.js");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The renderer checkout a run serves: an absolute folder holding the
|
|
13
|
+
* `@antelopejs/dms-frontend` package with its build.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} folder The `--renderer` folder
|
|
16
|
+
* @returns {string} Its absolute path
|
|
17
|
+
* @throws When the folder is not that package, or is not built
|
|
18
|
+
*/
|
|
19
|
+
export function resolveRenderer(folder) {
|
|
20
|
+
const root = resolve(folder);
|
|
21
|
+
const manifest = join(root, "package.json");
|
|
22
|
+
if (!existsSync(manifest)) {
|
|
23
|
+
throw new Error(`--renderer ${root} has no package.json`);
|
|
24
|
+
}
|
|
25
|
+
/** @type {unknown} */
|
|
26
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
27
|
+
const name = stringField(parsed, "name");
|
|
28
|
+
if (name !== RENDERER_PACKAGE) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`--renderer ${root} is ${name ?? "unnamed"}, not ${RENDERER_PACKAGE}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
if (!existsSync(join(root, RENDERER_CLI))) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`--renderer ${root} has no ${RENDERER_CLI}: build it first (pnpm install, pnpm build)`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return root;
|
|
39
|
+
}
|