@antelopejs/dms 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -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 +163 -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 +31 -14
- package/bin/control/stack.mjs +96 -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/config/internal/state.d.ts +7 -0
- package/dist/config/internal/state.js +9 -0
- package/dist/config/internal/state.js.map +1 -1
- package/dist/config/startup-config.d.ts +1 -0
- package/dist/config/startup-config.js +11 -0
- package/dist/config/startup-config.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/index.d.ts +1 -0
- package/dist/db/index.js +1 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/models/attachments.model.d.ts +7 -5
- package/dist/db/models/index.d.ts +1 -0
- package/dist/db/models/index.js +1 -0
- package/dist/db/models/index.js.map +1 -1
- package/dist/db/models/setupToken.model.d.ts +31 -0
- package/dist/db/models/setupToken.model.js +62 -0
- package/dist/db/models/setupToken.model.js.map +1 -0
- 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/db/setup-token.d.ts +2 -0
- package/dist/db/setup-token.js +28 -0
- package/dist/db/setup-token.js.map +1 -0
- package/dist/db/tables/index.d.ts +1 -0
- package/dist/db/tables/index.js +1 -0
- package/dist/db/tables/index.js.map +1 -1
- package/dist/db/tables/setupToken.table.d.ts +7 -0
- package/dist/db/tables/setupToken.table.js +34 -0
- package/dist/db/tables/setupToken.table.js.map +1 -0
- 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/payloads.js +6 -10
- package/dist/implementations/dms/page/payloads.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/public-layout.d.ts +2 -0
- package/dist/implementations/dms/page/public-layout.js +45 -0
- package/dist/implementations/dms/page/public-layout.js.map +1 -0
- 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.d.ts +0 -1
- package/dist/index.js +6 -2
- 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/current-password.js +1 -1
- package/dist/pages/settings/users/current-password.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 +2 -2
- package/dist/routes/auth/login.js +8 -4
- 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/password-hash-upgrade.d.ts +2 -0
- package/dist/routes/auth/password-hash-upgrade.js +22 -0
- package/dist/routes/auth/password-hash-upgrade.js.map +1 -0
- 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/authentication.d.ts +2 -0
- package/dist/routes/authentication.js +2 -0
- package/dist/routes/authentication.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 +4 -3
- package/dist/routes/onboarding.js +20 -3
- 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/ensure-default-tenant.d.ts +2 -1
- package/dist/utils/ensure-default-tenant.js +126 -8
- package/dist/utils/ensure-default-tenant.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/onboarding/register-admin.schema.d.ts +5 -0
- package/dist/validation/onboarding/register-admin.schema.js +3 -0
- package/dist/validation/onboarding/register-admin.schema.js.map +1 -1
- 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-core/i18n/locales/core-en-GB.json +1 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +1 -0
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +5 -0
- package/frontend-vue/layers/dms-layout/app/app.config.ts +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +9 -9
- package/frontend-vue/layers/dms-layout/app/components/layout/AppLogo.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useInstalledModules.ts +78 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +5 -24
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +2 -1
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +2 -1
- package/frontend-vue/layers/dms-onboarding/app/components/onboarding/steps/register.vue +21 -1
- package/frontend-vue/layers/dms-onboarding/app/custom-pages/onboarding.vue +14 -0
- package/frontend-vue/layers/dms-onboarding/i18n/locales/onboarding-en-GB.json +2 -0
- package/frontend-vue/layers/dms-onboarding/i18n/locales/onboarding-fr-FR.json +2 -0
- 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,245 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { decodeCommitStatuses, readFeatureResults } from "./decode.mjs";
|
|
8
|
+
import { readFormalResult } from "./formal.mjs";
|
|
9
|
+
import { readJudgement } from "./judgement.mjs";
|
|
10
|
+
import { loadSkill } from "./skill.mjs";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {import("node:test").TestContext} t The test
|
|
14
|
+
* @returns {string} A scratch folder removed when the test ends
|
|
15
|
+
*/
|
|
16
|
+
function scratch(t) {
|
|
17
|
+
const dir = mkdtempSync(join(tmpdir(), "dms-control-decode-test-"));
|
|
18
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} file The file
|
|
24
|
+
* @param {unknown} value What it holds as JSON
|
|
25
|
+
*/
|
|
26
|
+
function writeJson(file, value) {
|
|
27
|
+
mkdirSync(join(file, ".."), { recursive: true });
|
|
28
|
+
writeFileSync(file, JSON.stringify(value));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const FORMAL = {
|
|
32
|
+
name: "formal-add",
|
|
33
|
+
exitCode: 0,
|
|
34
|
+
violated: [],
|
|
35
|
+
passing: ["AddWorks"],
|
|
36
|
+
folder: "formal/formal-add",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
await test("a formal result is read as recorded", (t) => {
|
|
40
|
+
const evidence = scratch(t);
|
|
41
|
+
writeJson(join(evidence, "formal", "formal-add", "result.json"), {
|
|
42
|
+
...FORMAL,
|
|
43
|
+
base: null,
|
|
44
|
+
fixed: [],
|
|
45
|
+
});
|
|
46
|
+
assert.deepEqual(readFormalResult({ evidence }, "formal-add"), {
|
|
47
|
+
...FORMAL,
|
|
48
|
+
base: null,
|
|
49
|
+
fixed: [],
|
|
50
|
+
});
|
|
51
|
+
assert.equal(readFormalResult({ evidence }, "formal-other"), undefined);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await test("a formal result whose rules are no list is refused", (t) => {
|
|
55
|
+
const evidence = scratch(t);
|
|
56
|
+
writeJson(join(evidence, "formal", "formal-add", "result.json"), {
|
|
57
|
+
...FORMAL,
|
|
58
|
+
violated: "AddWorks",
|
|
59
|
+
});
|
|
60
|
+
assert.throws(
|
|
61
|
+
() => readFormalResult({ evidence }, "formal-add"),
|
|
62
|
+
/result\.json is malformed: expected a formal check result/,
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const RESULT = {
|
|
67
|
+
feature: "smoke",
|
|
68
|
+
journey: "onboard",
|
|
69
|
+
title: "Onboard",
|
|
70
|
+
entryPoint: "/",
|
|
71
|
+
status: "PASS",
|
|
72
|
+
database: "dms_run",
|
|
73
|
+
steps: [
|
|
74
|
+
{ name: "open", checks: [{ name: "loads", pass: true, detail: "" }] },
|
|
75
|
+
],
|
|
76
|
+
error: null,
|
|
77
|
+
folder: "smoke/onboard",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
await test("journey results are read as recorded and none when absent", (t) => {
|
|
81
|
+
const dir = scratch(t);
|
|
82
|
+
assert.deepEqual(readFeatureResults(join(dir, "feature.json")), []);
|
|
83
|
+
writeJson(join(dir, "feature.json"), [RESULT]);
|
|
84
|
+
assert.deepEqual(readFeatureResults(join(dir, "feature.json")), [RESULT]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await test("a journey result with a status that is neither PASS nor FAIL is refused", (t) => {
|
|
88
|
+
const dir = scratch(t);
|
|
89
|
+
writeJson(join(dir, "feature.json"), [{ ...RESULT, status: "PASSED" }]);
|
|
90
|
+
assert.throws(
|
|
91
|
+
() => readFeatureResults(join(dir, "feature.json")),
|
|
92
|
+
/feature\.json is malformed: expected a list of journey results/,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await test("a journey result whose check does not say pass or fail is refused", (t) => {
|
|
97
|
+
const dir = scratch(t);
|
|
98
|
+
const steps = [
|
|
99
|
+
{ name: "open", checks: [{ name: "loads", pass: "yes", detail: "" }] },
|
|
100
|
+
];
|
|
101
|
+
writeJson(join(dir, "feature.json"), [{ ...RESULT, steps }]);
|
|
102
|
+
assert.throws(
|
|
103
|
+
() => readFeatureResults(join(dir, "feature.json")),
|
|
104
|
+
/malformed: expected a list of journey results/,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await test("a status list without a context and state is refused", () => {
|
|
109
|
+
const statuses = [{ context: "verify", state: "success", id: 1 }];
|
|
110
|
+
assert.deepEqual(decodeCommitStatuses(statuses, "gh"), statuses);
|
|
111
|
+
assert.throws(
|
|
112
|
+
() => decodeCommitStatuses({ message: "Not Found" }, "gh api statuses"),
|
|
113
|
+
/gh api statuses is malformed/,
|
|
114
|
+
);
|
|
115
|
+
assert.throws(
|
|
116
|
+
() => decodeCommitStatuses([{ context: "verify" }], "gh"),
|
|
117
|
+
/malformed: expected a list of commit statuses/,
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const CONFIG = `export default {
|
|
122
|
+
playground: "playground",
|
|
123
|
+
prepare: [["pnpm", "install"]],
|
|
124
|
+
userFacing: ["src/**"],
|
|
125
|
+
smoke: "smoke",
|
|
126
|
+
smokeJourney: "onboard",
|
|
127
|
+
doors: { data: { globs: ["src/**"], funnels: [], containments: [], why: "rows" }, format: null },
|
|
128
|
+
formal: [],
|
|
129
|
+
};
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {import("node:test").TestContext} t The test
|
|
134
|
+
* @param {string} config What `verify.config.ts` holds
|
|
135
|
+
* @returns {string} The skill folder, inside a git checkout
|
|
136
|
+
*/
|
|
137
|
+
function skillFolder(t, config) {
|
|
138
|
+
const repo = scratch(t);
|
|
139
|
+
execFileSync("git", ["init", "-q"], { cwd: repo });
|
|
140
|
+
execFileSync(
|
|
141
|
+
"git",
|
|
142
|
+
[
|
|
143
|
+
"-c",
|
|
144
|
+
"user.name=t",
|
|
145
|
+
"-c",
|
|
146
|
+
"user.email=t@example.com",
|
|
147
|
+
"-c",
|
|
148
|
+
"commit.gpgsign=false",
|
|
149
|
+
"commit",
|
|
150
|
+
"-q",
|
|
151
|
+
"--allow-empty",
|
|
152
|
+
"-m",
|
|
153
|
+
"init",
|
|
154
|
+
],
|
|
155
|
+
{ cwd: repo },
|
|
156
|
+
);
|
|
157
|
+
const dir = join(repo, ".agents", "skills", "verify-demo");
|
|
158
|
+
mkdirSync(dir, { recursive: true });
|
|
159
|
+
writeFileSync(join(dir, "verify.config.ts"), config);
|
|
160
|
+
return dir;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await test("a config without the optional list of non-user-facing paths loads with none", async (t) => {
|
|
164
|
+
const skill = await loadSkill(skillFolder(t, CONFIG), tmpdir());
|
|
165
|
+
assert.deepEqual(skill.config.notUserFacing, []);
|
|
166
|
+
assert.equal(skill.config.doors.format, null);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await test("a config missing a field is refused where the skill loads", async (t) => {
|
|
170
|
+
const dir = skillFolder(t, CONFIG.replace('smoke: "smoke",\n', ""));
|
|
171
|
+
await assert.rejects(
|
|
172
|
+
loadSkill(dir, tmpdir()),
|
|
173
|
+
/verify\.config\.ts is malformed: expected a verification skill config/,
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await test("a config whose formal check pattern is a string is refused", async (t) => {
|
|
178
|
+
const dir = skillFolder(
|
|
179
|
+
t,
|
|
180
|
+
CONFIG.replace(
|
|
181
|
+
"formal: []",
|
|
182
|
+
'formal: [{ name: "f", covers: [], command: ["node"], violation: "^x", passing: null }]',
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
await assert.rejects(
|
|
186
|
+
loadSkill(dir, tmpdir()),
|
|
187
|
+
/malformed: expected a verification skill config/,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await test("a config module without a default export is refused", async (t) => {
|
|
192
|
+
const dir = skillFolder(t, "export const config = {};\n");
|
|
193
|
+
await assert.rejects(
|
|
194
|
+
loadSkill(dir, tmpdir()),
|
|
195
|
+
/verify\.config\.ts is malformed/,
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const JUDGEMENT = {
|
|
200
|
+
verifier: "thread",
|
|
201
|
+
notVerified: ["ui"],
|
|
202
|
+
attention: [],
|
|
203
|
+
docs: [{ page: "docs/a.md", matches: true, note: "ok" }],
|
|
204
|
+
doors: { none: "no door" },
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
await test("a judgement is read into the verdict's shape", async (t) => {
|
|
208
|
+
const skill = await loadSkill(skillFolder(t, CONFIG), tmpdir());
|
|
209
|
+
const file = join(scratch(t), "judgement.json");
|
|
210
|
+
writeJson(file, JUDGEMENT);
|
|
211
|
+
assert.deepEqual(readJudgement(skill, file), {
|
|
212
|
+
notVerified: ["ui"],
|
|
213
|
+
attention: [],
|
|
214
|
+
docs: [{ page: "docs/a.md", matches: true, note: "ok" }],
|
|
215
|
+
doorsNone: "no door",
|
|
216
|
+
doors: [],
|
|
217
|
+
falsePositives: [],
|
|
218
|
+
verifier: "thread",
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await test("a judgement whose lists hold other than strings is refused", async (t) => {
|
|
223
|
+
const skill = await loadSkill(skillFolder(t, CONFIG), tmpdir());
|
|
224
|
+
const file = join(scratch(t), "judgement.json");
|
|
225
|
+
writeJson(file, { ...JUDGEMENT, notVerified: "ui" });
|
|
226
|
+
assert.throws(
|
|
227
|
+
() => readJudgement(skill, file),
|
|
228
|
+
/judgement\.json is malformed: expected a verifier's judgement/,
|
|
229
|
+
);
|
|
230
|
+
writeJson(file, {
|
|
231
|
+
...JUDGEMENT,
|
|
232
|
+
docs: [{ page: "docs/a.md", matches: "yes" }],
|
|
233
|
+
});
|
|
234
|
+
assert.throws(
|
|
235
|
+
() => readJudgement(skill, file),
|
|
236
|
+
/malformed: expected a verifier/,
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await test("a judgement that is no object is refused", async (t) => {
|
|
241
|
+
const skill = await loadSkill(skillFolder(t, CONFIG), tmpdir());
|
|
242
|
+
const file = join(scratch(t), "judgement.json");
|
|
243
|
+
writeJson(file, ["verifier"]);
|
|
244
|
+
assert.throws(() => readJudgement(skill, file), /is malformed/);
|
|
245
|
+
});
|
|
@@ -5,7 +5,7 @@ import { matchesGlob } from "node:path";
|
|
|
5
5
|
* The one-way door kinds whose path rules a set of changed files hits, with
|
|
6
6
|
* the files per kind. A declared funnel module is a path rule of its kind.
|
|
7
7
|
*
|
|
8
|
-
* @param {
|
|
8
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
9
9
|
* @param {string[]} files Repository paths
|
|
10
10
|
* @returns {{ kind: string, files: string[] }[]} The flagged kinds
|
|
11
11
|
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { flaggedDoors, patchFiles } from "./doors.mjs";
|
|
7
|
+
import { loadSkill } from "./skill.mjs";
|
|
8
|
+
|
|
9
|
+
const skill = await loadSkill(undefined, import.meta.dirname);
|
|
10
|
+
const kinds = Object.entries(skill.config.doors)
|
|
11
|
+
.filter(
|
|
12
|
+
([kind, rule]) =>
|
|
13
|
+
rule && existsSync(join(skill.seededDoorsDir, `${kind}.patch`)),
|
|
14
|
+
)
|
|
15
|
+
.map(([kind]) => kind);
|
|
16
|
+
|
|
17
|
+
for (const kind of kinds) {
|
|
18
|
+
const patch = join(skill.seededDoorsDir, `${kind}.patch`);
|
|
19
|
+
|
|
20
|
+
await test(`the ${kind} seeded door applies to the current tree`, () => {
|
|
21
|
+
assert.doesNotThrow(
|
|
22
|
+
() =>
|
|
23
|
+
execFileSync("git", ["apply", "--check", patch], {
|
|
24
|
+
cwd: skill.repo,
|
|
25
|
+
stdio: "pipe",
|
|
26
|
+
}),
|
|
27
|
+
`${kind}.patch no longer applies; refresh it against the current source`,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await test(`the ${kind} seeded door flags the ${kind} door`, () => {
|
|
32
|
+
const flagged = flaggedDoors(skill, patchFiles(patch)).map(
|
|
33
|
+
(door) => door.kind,
|
|
34
|
+
);
|
|
35
|
+
assert.ok(
|
|
36
|
+
flagged.includes(kind),
|
|
37
|
+
`${kind}.patch flags ${flagged.join(", ") || "nothing"}`,
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -3,6 +3,12 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { dirname, extname, join, posix } from "node:path";
|
|
4
4
|
import { PACKAGE_ROOT } from "../stack.mjs";
|
|
5
5
|
|
|
6
|
+
/** @typedef {typeof import("typescript")} TypeScript */
|
|
7
|
+
/** @typedef {import("typescript").Node} Node */
|
|
8
|
+
/** @typedef {{ modules: Set<string>, functions: Set<string> }} Covered The covered modules and function names */
|
|
9
|
+
/** @typedef {{ structure: string, text: string, hoisted: boolean }} Item */
|
|
10
|
+
/** @typedef {{ path: string, file: import("typescript").SourceFile }} Script */
|
|
11
|
+
|
|
6
12
|
const CODE_EXTENSIONS = new Set([
|
|
7
13
|
".ts",
|
|
8
14
|
".mts",
|
|
@@ -16,9 +22,14 @@ const CODE_EXTENSIONS = new Set([
|
|
|
16
22
|
const OUTPUT_LIMIT = 64 * 1024 * 1024;
|
|
17
23
|
const TEXT_LENGTH = 160;
|
|
18
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} repo
|
|
27
|
+
* @returns {TypeScript}
|
|
28
|
+
*/
|
|
19
29
|
function loadTypeScript(repo) {
|
|
20
30
|
for (const folder of [repo, join(repo, "packages", "dms"), PACKAGE_ROOT]) {
|
|
21
31
|
try {
|
|
32
|
+
// oxlint-disable-next-line typescript/no-unsafe-return -- createRequire loads TypeScript by a path known only at run time, from the checkout under test first and the dms package last, and Node types what require answers as any.
|
|
22
33
|
return createRequire(join(folder, "package.json"))("typescript");
|
|
23
34
|
} catch {
|
|
24
35
|
continue;
|
|
@@ -29,6 +40,12 @@ function loadTypeScript(repo) {
|
|
|
29
40
|
);
|
|
30
41
|
}
|
|
31
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} repo
|
|
45
|
+
* @param {string} sha
|
|
46
|
+
* @param {string} path
|
|
47
|
+
* @returns {string | null}
|
|
48
|
+
*/
|
|
32
49
|
function fileAt(repo, sha, path) {
|
|
33
50
|
const shown = spawnSync("git", ["show", `${sha}:${path}`], {
|
|
34
51
|
cwd: repo,
|
|
@@ -38,6 +55,12 @@ function fileAt(repo, sha, path) {
|
|
|
38
55
|
return shown.status === 0 ? shown.stdout : null;
|
|
39
56
|
}
|
|
40
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @param {TypeScript} ts
|
|
60
|
+
* @param {Node} node
|
|
61
|
+
* @param {Map<string, string>} bindings
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
41
64
|
function leafOf(ts, node, bindings) {
|
|
42
65
|
if (ts.isIdentifier(node))
|
|
43
66
|
return propertyName(ts, node)
|
|
@@ -59,15 +82,46 @@ function leafOf(ts, node, bindings) {
|
|
|
59
82
|
|
|
60
83
|
const MODULE_EXTENSION = /\.(?:[cm]?[jt]s|[jt]sx)$/;
|
|
61
84
|
|
|
85
|
+
/**
|
|
86
|
+
* The text of a module specifier: a string, template without substitutions,
|
|
87
|
+
* number or identifier, which is all the parser leaves a `text` on.
|
|
88
|
+
*
|
|
89
|
+
* @param {TypeScript} ts
|
|
90
|
+
* @param {import("typescript").Expression} specifier
|
|
91
|
+
* @returns {string}
|
|
92
|
+
* @throws {TypeError} For any other expression, such as a call
|
|
93
|
+
*/
|
|
94
|
+
function specifierText(ts, specifier) {
|
|
95
|
+
if (ts.isLiteralExpression(specifier) || ts.isIdentifier(specifier))
|
|
96
|
+
return specifier.text;
|
|
97
|
+
throw new TypeError(
|
|
98
|
+
`a module specifier must be a name, found ${ts.SyntaxKind[specifier.kind]}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {string} path
|
|
104
|
+
* @param {string} specifier The text of the module specifier
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
62
107
|
function moduleKey(path, specifier) {
|
|
63
108
|
if (!specifier.startsWith(".")) return specifier;
|
|
64
109
|
return posix.join(dirname(path), specifier).replace(MODULE_EXTENSION, "");
|
|
65
110
|
}
|
|
66
111
|
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} path
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
67
116
|
function stripExtension(path) {
|
|
68
117
|
return path.replace(MODULE_EXTENSION, "");
|
|
69
118
|
}
|
|
70
119
|
|
|
120
|
+
/**
|
|
121
|
+
* @param {TypeScript} ts
|
|
122
|
+
* @param {Node} node
|
|
123
|
+
* @returns {boolean}
|
|
124
|
+
*/
|
|
71
125
|
function propertyName(ts, node) {
|
|
72
126
|
const parent = node.parent;
|
|
73
127
|
return (
|
|
@@ -89,7 +143,7 @@ function propertyName(ts, node) {
|
|
|
89
143
|
* otherwise, so a variable or class of one file never reads as one of the same
|
|
90
144
|
* name in another.
|
|
91
145
|
*
|
|
92
|
-
* @param {
|
|
146
|
+
* @param {Covered} covered The covered modules and function names
|
|
93
147
|
* @param {string} module The resolved module
|
|
94
148
|
* @param {string} name The imported name
|
|
95
149
|
* @returns {string} The origin
|
|
@@ -100,8 +154,16 @@ function bindingOrigin(covered, module, name) {
|
|
|
100
154
|
: `${module}#${name}`;
|
|
101
155
|
}
|
|
102
156
|
|
|
157
|
+
/**
|
|
158
|
+
* @param {TypeScript} ts
|
|
159
|
+
* @param {import("typescript").ImportClause} clause
|
|
160
|
+
* @param {string} module
|
|
161
|
+
* @param {Covered} covered
|
|
162
|
+
* @returns {[string, string][]}
|
|
163
|
+
*/
|
|
103
164
|
function importedBindings(ts, clause, module, covered) {
|
|
104
165
|
const named = clause.namedBindings;
|
|
166
|
+
/** @type {[string, string][]} */
|
|
105
167
|
const bindings = [];
|
|
106
168
|
if (clause.name)
|
|
107
169
|
bindings.push([
|
|
@@ -123,6 +185,13 @@ function importedBindings(ts, clause, module, covered) {
|
|
|
123
185
|
return bindings;
|
|
124
186
|
}
|
|
125
187
|
|
|
188
|
+
/**
|
|
189
|
+
* @param {TypeScript} ts
|
|
190
|
+
* @param {import("typescript").ExportDeclaration} statement
|
|
191
|
+
* @param {string} module
|
|
192
|
+
* @param {Covered} covered
|
|
193
|
+
* @returns {string[]}
|
|
194
|
+
*/
|
|
126
195
|
function reexportTexts(ts, statement, module, covered) {
|
|
127
196
|
const exported = statement.exportClause;
|
|
128
197
|
const inside = covered.modules.has(module);
|
|
@@ -146,6 +215,11 @@ function reexportTexts(ts, statement, module, covered) {
|
|
|
146
215
|
* origin of what they export. An export under its own name of a local needs no
|
|
147
216
|
* entry, the same as an `export` keyword, so only a renamed local and any
|
|
148
217
|
* imported binding does.
|
|
218
|
+
* @param {TypeScript} ts
|
|
219
|
+
* @param {import("typescript").ExportDeclaration} statement
|
|
220
|
+
* @param {Map<string, string>} bindings
|
|
221
|
+
* @param {Map<string, string>} locals
|
|
222
|
+
* @returns {string[]}
|
|
149
223
|
*/
|
|
150
224
|
function localExportTexts(ts, statement, bindings, locals) {
|
|
151
225
|
const exported = statement.exportClause;
|
|
@@ -170,17 +244,21 @@ function localExportTexts(ts, statement, bindings, locals) {
|
|
|
170
244
|
* module. Side-effect imports keep their order in the file. Re-exports and
|
|
171
245
|
* renamed or imported exports become entries keyed by the origin they export.
|
|
172
246
|
*
|
|
173
|
-
* @param {
|
|
247
|
+
* @param {TypeScript} ts The TypeScript module
|
|
174
248
|
* @param {string} path The file
|
|
175
|
-
* @param {
|
|
176
|
-
* @param {
|
|
249
|
+
* @param {Covered} covered The covered modules and function names
|
|
250
|
+
* @param {import("typescript").SourceFile} file The emitted source file
|
|
177
251
|
* @param {Map<string, string>} locals The top-level variables and classes of the file, keyed by file and name
|
|
178
252
|
* @returns {{ bindings: Map<string, string>, entries: { structure: string, text: string, hoisted: boolean }[], ordered: { structure: string, text: string, hoisted: boolean }[] }} The origins; `entries` may move freely, `ordered` keep their order in the file
|
|
179
253
|
*/
|
|
180
254
|
function originsOf(ts, path, covered, file, locals) {
|
|
255
|
+
/** @type {Map<string, string>} */
|
|
181
256
|
const bindings = new Map();
|
|
257
|
+
/** @type {string[]} */
|
|
182
258
|
const texts = [];
|
|
259
|
+
/** @type {string[]} */
|
|
183
260
|
const ordered = [];
|
|
261
|
+
/** @type {import("typescript").ExportDeclaration[]} */
|
|
184
262
|
const exports = [];
|
|
185
263
|
for (const statement of file.statements) {
|
|
186
264
|
if (
|
|
@@ -191,7 +269,7 @@ function originsOf(ts, path, covered, file, locals) {
|
|
|
191
269
|
if (specifier === undefined && ts.isExportDeclaration(statement))
|
|
192
270
|
exports.push(statement);
|
|
193
271
|
if (specifier === undefined) continue;
|
|
194
|
-
const module = moduleKey(path, specifier
|
|
272
|
+
const module = moduleKey(path, specifierText(ts, specifier));
|
|
195
273
|
if (ts.isImportDeclaration(statement)) {
|
|
196
274
|
if (statement.importClause === undefined) {
|
|
197
275
|
ordered.push(`import ${module}`);
|
|
@@ -208,6 +286,11 @@ function originsOf(ts, path, covered, file, locals) {
|
|
|
208
286
|
}
|
|
209
287
|
for (const statement of exports)
|
|
210
288
|
texts.push(...localExportTexts(ts, statement, bindings, locals));
|
|
289
|
+
/**
|
|
290
|
+
* @param {string} text
|
|
291
|
+
* @param {boolean} hoisted
|
|
292
|
+
* @returns {Item}
|
|
293
|
+
*/
|
|
211
294
|
const item = (text, hoisted) => ({ structure: text, text, hoisted });
|
|
212
295
|
return {
|
|
213
296
|
bindings,
|
|
@@ -216,6 +299,11 @@ function originsOf(ts, path, covered, file, locals) {
|
|
|
216
299
|
};
|
|
217
300
|
}
|
|
218
301
|
|
|
302
|
+
/**
|
|
303
|
+
* @param {TypeScript} ts
|
|
304
|
+
* @param {Node} node
|
|
305
|
+
* @returns {boolean}
|
|
306
|
+
*/
|
|
219
307
|
function dropped(ts, node) {
|
|
220
308
|
return (
|
|
221
309
|
node.kind === ts.SyntaxKind.ExportKeyword ||
|
|
@@ -228,14 +316,15 @@ function dropped(ts, node) {
|
|
|
228
316
|
* and its children's structure, with parentheses, positions, trivia, quote
|
|
229
317
|
* style and line breaks left out, so formatting never changes it.
|
|
230
318
|
*
|
|
231
|
-
* @param {
|
|
232
|
-
* @param {
|
|
319
|
+
* @param {TypeScript} ts The TypeScript module
|
|
320
|
+
* @param {Node} node A node of the emitted JavaScript
|
|
233
321
|
* @param {Map<string, string>} bindings The origin of each imported binding
|
|
234
322
|
* @returns {string} The structure
|
|
235
323
|
*/
|
|
236
324
|
function structureOf(ts, node, bindings) {
|
|
237
325
|
if (ts.isParenthesizedExpression(node))
|
|
238
326
|
return structureOf(ts, node.expression, bindings);
|
|
327
|
+
/** @type {string[]} */
|
|
239
328
|
const children = [];
|
|
240
329
|
ts.forEachChild(node, (child) => {
|
|
241
330
|
if (!dropped(ts, child)) children.push(structureOf(ts, child, bindings));
|
|
@@ -243,6 +332,12 @@ function structureOf(ts, node, bindings) {
|
|
|
243
332
|
return `${ts.SyntaxKind[node.kind]}(${leafOf(ts, node, bindings)}|${children.join(",")})`;
|
|
244
333
|
}
|
|
245
334
|
|
|
335
|
+
/**
|
|
336
|
+
* @param {TypeScript} ts
|
|
337
|
+
* @param {string} path
|
|
338
|
+
* @param {string} source
|
|
339
|
+
* @returns {Script}
|
|
340
|
+
*/
|
|
246
341
|
function parseScript(ts, path, source) {
|
|
247
342
|
const { outputText } = ts.transpileModule(source, {
|
|
248
343
|
fileName: path,
|
|
@@ -265,11 +360,20 @@ function parseScript(ts, path, source) {
|
|
|
265
360
|
};
|
|
266
361
|
}
|
|
267
362
|
|
|
363
|
+
/**
|
|
364
|
+
* @param {string} text
|
|
365
|
+
* @returns {string}
|
|
366
|
+
*/
|
|
268
367
|
function readable(text) {
|
|
269
368
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
270
369
|
return flat.length > TEXT_LENGTH ? `${flat.slice(0, TEXT_LENGTH)}…` : flat;
|
|
271
370
|
}
|
|
272
371
|
|
|
372
|
+
/**
|
|
373
|
+
* @param {TypeScript} ts
|
|
374
|
+
* @param {import("typescript").BindingName} name
|
|
375
|
+
* @returns {string[]}
|
|
376
|
+
*/
|
|
273
377
|
function declaredNames(ts, name) {
|
|
274
378
|
return ts.isIdentifier(name)
|
|
275
379
|
? [name.text]
|
|
@@ -278,6 +382,11 @@ function declaredNames(ts, name) {
|
|
|
278
382
|
);
|
|
279
383
|
}
|
|
280
384
|
|
|
385
|
+
/**
|
|
386
|
+
* @param {TypeScript} ts
|
|
387
|
+
* @param {import("typescript").Statement} statement
|
|
388
|
+
* @returns {statement is import("typescript").FunctionDeclaration & { name: import("typescript").Identifier }}
|
|
389
|
+
*/
|
|
281
390
|
function isFunctionDeclaration(ts, statement) {
|
|
282
391
|
return (
|
|
283
392
|
ts.isFunctionDeclaration(statement) &&
|
|
@@ -286,7 +395,12 @@ function isFunctionDeclaration(ts, statement) {
|
|
|
286
395
|
);
|
|
287
396
|
}
|
|
288
397
|
|
|
289
|
-
/**
|
|
398
|
+
/**
|
|
399
|
+
* The top-level variables, classes and enums of a script, each keyed by its file and name: what a function moved into another file must not pick up by name.
|
|
400
|
+
* @param {TypeScript} ts
|
|
401
|
+
* @param {Script} script
|
|
402
|
+
* @returns {Map<string, string>}
|
|
403
|
+
*/
|
|
290
404
|
function localsOf(ts, script) {
|
|
291
405
|
const file = stripExtension(script.path);
|
|
292
406
|
const names = script.file.statements.flatMap((statement) => {
|
|
@@ -303,6 +417,12 @@ function localsOf(ts, script) {
|
|
|
303
417
|
return new Map(names.map((name) => [name, `${file}#${name}`]));
|
|
304
418
|
}
|
|
305
419
|
|
|
420
|
+
/**
|
|
421
|
+
* @param {TypeScript} ts
|
|
422
|
+
* @param {Script} script
|
|
423
|
+
* @param {Covered} covered
|
|
424
|
+
* @returns {Item[]}
|
|
425
|
+
*/
|
|
306
426
|
function scriptItems(ts, script, covered) {
|
|
307
427
|
const locals = localsOf(ts, script);
|
|
308
428
|
const origins = originsOf(ts, script.path, covered, script.file, locals);
|
|
@@ -329,7 +449,12 @@ function scriptItems(ts, script, covered) {
|
|
|
329
449
|
return [...origins.entries, ...origins.ordered, ...statements];
|
|
330
450
|
}
|
|
331
451
|
|
|
332
|
-
/**
|
|
452
|
+
/**
|
|
453
|
+
* A covered file as its scripts and the items that are not scripts: a Vue template, or the whole text of any other file.
|
|
454
|
+
* @param {TypeScript} ts
|
|
455
|
+
* @param {string} path
|
|
456
|
+
* @param {string} source
|
|
457
|
+
*/
|
|
333
458
|
function parsedFile(ts, path, source) {
|
|
334
459
|
if (path.endsWith(".vue")) {
|
|
335
460
|
const template = /<template\b[^>]*>([\s\S]*)<\/template>/.exec(source);
|
|
@@ -384,6 +509,7 @@ function parsedFile(ts, path, source) {
|
|
|
384
509
|
*/
|
|
385
510
|
export function emitFingerprint(repo, sha, files) {
|
|
386
511
|
const ts = loadTypeScript(repo);
|
|
512
|
+
/** @type {{ functions: Item[], files: Record<string, Item[]> }} */
|
|
387
513
|
const fingerprint = { functions: [], files: {} };
|
|
388
514
|
const parsed = files.flatMap((path) => {
|
|
389
515
|
const source = fileAt(repo, sha, path);
|