tessera-learn 0.0.13 → 0.1.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/AGENTS.md +1744 -0
- package/README.md +2 -2
- package/dist/{validation-B-xTvM9B.js → audit-CzKAXy3Y.js} +591 -268
- package/dist/audit-CzKAXy3Y.js.map +1 -0
- package/dist/build-commands-D101M_qb.js +27 -0
- package/dist/build-commands-D101M_qb.js.map +1 -0
- package/dist/inline-config-DYHT51G8.js +29 -0
- package/dist/inline-config-DYHT51G8.js.map +1 -0
- package/dist/plugin/cli.d.ts +5 -1
- package/dist/plugin/cli.d.ts.map +1 -0
- package/dist/plugin/cli.js +108 -15
- package/dist/plugin/cli.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2 -763
- package/dist/plugin-y35ym9A3.js +744 -0
- package/dist/plugin-y35ym9A3.js.map +1 -0
- package/package.json +12 -9
- package/src/components/FillInTheBlank.svelte +2 -2
- package/src/components/Matching.svelte +2 -2
- package/src/components/MultipleChoice.svelte +2 -2
- package/src/components/RevealModal.svelte +48 -103
- package/src/components/Sorting.svelte +2 -2
- package/src/components/util.ts +9 -0
- package/src/plugin/a11y/audit.ts +35 -8
- package/src/plugin/a11y-cli.ts +35 -22
- package/src/plugin/ast.ts +276 -0
- package/src/plugin/build-commands.ts +25 -0
- package/src/plugin/cli.ts +53 -21
- package/src/plugin/index.ts +87 -122
- package/src/plugin/inline-config.ts +43 -0
- package/src/plugin/manifest.ts +103 -136
- package/src/plugin/package-root.ts +24 -0
- package/src/plugin/quiz.ts +8 -9
- package/src/plugin/validate-cli.ts +30 -0
- package/src/plugin/validation.ts +152 -244
- package/src/runtime/App.svelte +11 -97
- package/src/runtime/Sidebar.svelte +3 -1
- package/src/runtime/adapters/cmi5.ts +6 -10
- package/src/runtime/adapters/format.ts +6 -0
- package/src/runtime/adapters/retry.ts +1 -1
- package/src/runtime/adapters/scorm2004.ts +2 -4
- package/src/runtime/branding.ts +90 -0
- package/src/runtime/defaults.ts +3 -0
- package/src/runtime/hooks.svelte.ts +16 -53
- package/src/runtime/interaction-format.ts +3 -8
- package/src/runtime/progress.svelte.ts +47 -83
- package/src/runtime/xapi/derive-actor.ts +41 -48
- package/src/runtime/xapi/publisher.ts +14 -14
- package/src/runtime/xapi/setup.ts +39 -46
- package/dist/audit-BBJpQGqb.js +0 -204
- package/dist/audit-BBJpQGqb.js.map +0 -1
- package/dist/plugin/a11y-cli.d.ts +0 -1
- package/dist/plugin/a11y-cli.js +0 -36
- package/dist/plugin/a11y-cli.js.map +0 -1
- package/dist/plugin/index.js.map +0 -1
- package/dist/validation-B-xTvM9B.js.map +0 -1
|
@@ -3,54 +3,48 @@ import type { SCORM12API } from '../adapters/scorm12.js';
|
|
|
3
3
|
import type { SCORM2004API } from '../adapters/scorm2004.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* override should treat it as a config error (the build-time validator
|
|
10
|
-
* already enforces this; this is a runtime fallback for completeness).
|
|
6
|
+
* Origin of an http(s) URL, else null. Shared with the config validator, which
|
|
7
|
+
* predicts this result to know when `actorAccountHomePage` becomes required —
|
|
8
|
+
* one helper keeps the two in lockstep.
|
|
11
9
|
*/
|
|
12
|
-
export function
|
|
10
|
+
export function httpOrigin(url: string): string | null {
|
|
13
11
|
try {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return null;
|
|
12
|
+
const parsed = new URL(url);
|
|
13
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:'
|
|
14
|
+
? parsed.origin
|
|
15
|
+
: null;
|
|
19
16
|
} catch {
|
|
20
17
|
return null;
|
|
21
18
|
}
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
/**
|
|
25
|
-
* Synthesize an Identified Agent
|
|
22
|
+
* Synthesize an Identified Agent from the SCORM learner fields.
|
|
26
23
|
*
|
|
27
|
-
* { account: { homePage, name:
|
|
28
|
-
* name: cmi.core.student_name,
|
|
29
|
-
* objectType: 'Agent' }
|
|
24
|
+
* { account: { homePage, name: <id> }, name: <name>, objectType: 'Agent' }
|
|
30
25
|
*
|
|
31
26
|
* The `account` IFI satisfies xAPI's Identified Agent rule. `homePage`
|
|
32
|
-
* defaults to the activityId origin so analytics keyed on actor identity
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* Returns null if `student_id` is missing — caller should not construct
|
|
37
|
-
* a publisher in that case (the LRS would 400 on every send anyway).
|
|
27
|
+
* defaults to the activityId origin so analytics keyed on actor identity stay
|
|
28
|
+
* stable across LMS hosts; the author's `actorAccountHomePage` overrides when
|
|
29
|
+
* the authority namespace is elsewhere. Returns null if the id is missing —
|
|
30
|
+
* the caller should not construct a publisher (the LRS would 400 every send).
|
|
38
31
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
function synthesizeActor(
|
|
33
|
+
readId: () => string,
|
|
34
|
+
readName: () => string,
|
|
41
35
|
activityId: string,
|
|
42
36
|
actorAccountHomePage?: string,
|
|
43
37
|
): XAPIAgent | null {
|
|
44
38
|
let id = '';
|
|
45
39
|
let name = '';
|
|
46
40
|
try {
|
|
47
|
-
id =
|
|
41
|
+
id = readId() || '';
|
|
48
42
|
} catch {}
|
|
49
43
|
try {
|
|
50
|
-
name =
|
|
44
|
+
name = readName() || '';
|
|
51
45
|
} catch {}
|
|
52
46
|
if (!id) return null;
|
|
53
|
-
const homePage = actorAccountHomePage ??
|
|
47
|
+
const homePage = actorAccountHomePage ?? httpOrigin(activityId);
|
|
54
48
|
if (!homePage) return null;
|
|
55
49
|
const agent: XAPIAgent = {
|
|
56
50
|
account: { homePage, name: id },
|
|
@@ -60,31 +54,30 @@ export function synthesizeSCORM12Actor(
|
|
|
60
54
|
return agent;
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
/** SCORM 1.2 actor from `cmi.core.student_id` / `cmi.core.student_name`. */
|
|
58
|
+
export function synthesizeSCORM12Actor(
|
|
59
|
+
api: SCORM12API,
|
|
60
|
+
activityId: string,
|
|
61
|
+
actorAccountHomePage?: string,
|
|
62
|
+
): XAPIAgent | null {
|
|
63
|
+
return synthesizeActor(
|
|
64
|
+
() => api.LMSGetValue('cmi.core.student_id'),
|
|
65
|
+
() => api.LMSGetValue('cmi.core.student_name'),
|
|
66
|
+
activityId,
|
|
67
|
+
actorAccountHomePage,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** SCORM 2004 actor from `cmi.learner_id` / `cmi.learner_name` (renamed 2004 fields). */
|
|
68
72
|
export function synthesizeSCORM2004Actor(
|
|
69
73
|
api: SCORM2004API,
|
|
70
74
|
activityId: string,
|
|
71
75
|
actorAccountHomePage?: string,
|
|
72
76
|
): XAPIAgent | null {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
name = api.GetValue('cmi.learner_name') || '';
|
|
80
|
-
} catch {}
|
|
81
|
-
if (!id) return null;
|
|
82
|
-
const homePage = actorAccountHomePage ?? defaultAccountHomePage(activityId);
|
|
83
|
-
if (!homePage) return null;
|
|
84
|
-
const agent: XAPIAgent = {
|
|
85
|
-
account: { homePage, name: id },
|
|
86
|
-
objectType: 'Agent',
|
|
87
|
-
};
|
|
88
|
-
if (name) agent.name = name;
|
|
89
|
-
return agent;
|
|
77
|
+
return synthesizeActor(
|
|
78
|
+
() => api.GetValue('cmi.learner_id'),
|
|
79
|
+
() => api.GetValue('cmi.learner_name'),
|
|
80
|
+
activityId,
|
|
81
|
+
actorAccountHomePage,
|
|
82
|
+
);
|
|
90
83
|
}
|
|
@@ -482,18 +482,22 @@ export class XAPIPublisher {
|
|
|
482
482
|
}));
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
+
#buildHeaders(token: string): Headers {
|
|
486
|
+
const headers = new Headers();
|
|
487
|
+
if (token) headers.set('Authorization', `Basic ${token}`);
|
|
488
|
+
headers.set('X-Experience-API-Version', X_API_VERSION);
|
|
489
|
+
headers.set('Content-Type', 'application/json');
|
|
490
|
+
return headers;
|
|
491
|
+
}
|
|
492
|
+
|
|
485
493
|
#fetchWithToken(
|
|
486
494
|
token: string,
|
|
487
495
|
body: string,
|
|
488
496
|
keepalive: boolean,
|
|
489
497
|
): Promise<SendOutcome> {
|
|
490
|
-
const headers = new Headers();
|
|
491
|
-
if (token) headers.set('Authorization', `Basic ${token}`);
|
|
492
|
-
headers.set('X-Experience-API-Version', X_API_VERSION);
|
|
493
|
-
headers.set('Content-Type', 'application/json');
|
|
494
498
|
return fetch(this.#statementsUrl, {
|
|
495
499
|
method: 'POST',
|
|
496
|
-
headers,
|
|
500
|
+
headers: this.#buildHeaders(token),
|
|
497
501
|
body,
|
|
498
502
|
keepalive,
|
|
499
503
|
})
|
|
@@ -521,18 +525,14 @@ export class XAPIPublisher {
|
|
|
521
525
|
) {
|
|
522
526
|
this.#cachedAuth = null;
|
|
523
527
|
return this.#resolveAuth(true)
|
|
524
|
-
.then((newToken) =>
|
|
525
|
-
|
|
526
|
-
if (newToken) retryHeaders.set('Authorization', `Basic ${newToken}`);
|
|
527
|
-
retryHeaders.set('X-Experience-API-Version', X_API_VERSION);
|
|
528
|
-
retryHeaders.set('Content-Type', 'application/json');
|
|
529
|
-
return fetch(this.#statementsUrl, {
|
|
528
|
+
.then((newToken) =>
|
|
529
|
+
fetch(this.#statementsUrl, {
|
|
530
530
|
method: 'POST',
|
|
531
|
-
headers:
|
|
531
|
+
headers: this.#buildHeaders(newToken),
|
|
532
532
|
body,
|
|
533
533
|
keepalive,
|
|
534
|
-
})
|
|
535
|
-
|
|
534
|
+
}),
|
|
535
|
+
)
|
|
536
536
|
.then((retryResp): SendOutcome => {
|
|
537
537
|
if (retryResp.ok || retryResp.status === 409) {
|
|
538
538
|
return { ok: true, status: retryResp.status };
|
|
@@ -90,6 +90,10 @@ function makeSCORMDevFallbackPublisher(
|
|
|
90
90
|
* adapter present in non-cmi5 export modes — the validator should have
|
|
91
91
|
* caught this at build time).
|
|
92
92
|
*/
|
|
93
|
+
type ActorResolution =
|
|
94
|
+
| { kind: 'actor'; value: XAPIAgent | (() => XAPIAgent | Promise<XAPIAgent>) }
|
|
95
|
+
| { kind: 'scorm-fallback'; standard: 'scorm12' | 'scorm2004' };
|
|
96
|
+
|
|
93
97
|
function resolveDestination(
|
|
94
98
|
entry: XAPIConfig,
|
|
95
99
|
config: CourseConfig,
|
|
@@ -114,24 +118,18 @@ function resolveDestination(
|
|
|
114
118
|
|
|
115
119
|
// Explicit endpoint.
|
|
116
120
|
const explicit = entry as XAPIExplicitConfig;
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
const std = (
|
|
125
|
-
actorOrResolver as { __scormDevFallback: 'scorm12' | 'scorm2004' }
|
|
126
|
-
).__scormDevFallback;
|
|
127
|
-
return { kind: 'explicit', publisher: makeSCORMDevFallbackPublisher(std) };
|
|
121
|
+
const resolution = resolveExplicitActor(explicit, config, adapter);
|
|
122
|
+
if (resolution === null) return null;
|
|
123
|
+
if (resolution.kind === 'scorm-fallback') {
|
|
124
|
+
return {
|
|
125
|
+
kind: 'explicit',
|
|
126
|
+
publisher: makeSCORMDevFallbackPublisher(resolution.standard),
|
|
127
|
+
};
|
|
128
128
|
}
|
|
129
129
|
const publisher = new XAPIPublisher({
|
|
130
130
|
endpoint: explicit.endpoint,
|
|
131
131
|
auth: explicit.auth,
|
|
132
|
-
actor:
|
|
133
|
-
| XAPIAgent
|
|
134
|
-
| (() => XAPIAgent | Promise<XAPIAgent>),
|
|
132
|
+
actor: resolution.value,
|
|
135
133
|
activityId: explicit.activityId,
|
|
136
134
|
registration: explicit.registration,
|
|
137
135
|
});
|
|
@@ -149,50 +147,45 @@ function resolveExplicitActor(
|
|
|
149
147
|
explicit: XAPIExplicitConfig,
|
|
150
148
|
config: CourseConfig,
|
|
151
149
|
adapter: PersistenceAdapter | null,
|
|
152
|
-
):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
| null {
|
|
157
|
-
if (explicit.actor !== undefined) return explicit.actor;
|
|
158
|
-
// No author-supplied actor — try mode-specific derivation.
|
|
150
|
+
): ActorResolution | null {
|
|
151
|
+
if (explicit.actor !== undefined) {
|
|
152
|
+
return { kind: 'actor', value: explicit.actor };
|
|
153
|
+
}
|
|
159
154
|
if (config.export?.standard === 'cmi5' && adapter instanceof CMI5Adapter) {
|
|
160
155
|
const inner = adapter.getPublisher();
|
|
161
|
-
if (inner)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
156
|
+
if (!inner) return null;
|
|
157
|
+
try {
|
|
158
|
+
return { kind: 'actor', value: inner.getActor() };
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
168
161
|
}
|
|
169
|
-
return null;
|
|
170
162
|
}
|
|
171
163
|
if (config.export?.standard === 'scorm12') {
|
|
172
164
|
if (adapter instanceof SCORM12Adapter) {
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
165
|
+
return {
|
|
166
|
+
kind: 'actor',
|
|
167
|
+
value: synthesizeSCORM12Actor(
|
|
168
|
+
adapter.getAPI(),
|
|
169
|
+
explicit.activityId,
|
|
170
|
+
explicit.actorAccountHomePage,
|
|
171
|
+
) as XAPIAgent,
|
|
172
|
+
};
|
|
178
173
|
}
|
|
179
|
-
|
|
180
|
-
// dev-fallback path: install a stub publisher that surfaces an
|
|
181
|
-
// explicit error rather than silently no-oping. Authors get the
|
|
182
|
-
// same dev/prod parity in SCORM that they get in cmi5.
|
|
183
|
-
return { __scormDevFallback: 'scorm12' };
|
|
174
|
+
return { kind: 'scorm-fallback', standard: 'scorm12' };
|
|
184
175
|
}
|
|
185
176
|
if (config.export?.standard === 'scorm2004') {
|
|
186
177
|
if (adapter instanceof SCORM2004Adapter) {
|
|
187
|
-
return
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
178
|
+
return {
|
|
179
|
+
kind: 'actor',
|
|
180
|
+
value: synthesizeSCORM2004Actor(
|
|
181
|
+
adapter.getAPI(),
|
|
182
|
+
explicit.activityId,
|
|
183
|
+
explicit.actorAccountHomePage,
|
|
184
|
+
) as XAPIAgent,
|
|
185
|
+
};
|
|
192
186
|
}
|
|
193
|
-
return {
|
|
187
|
+
return { kind: 'scorm-fallback', standard: 'scorm2004' };
|
|
194
188
|
}
|
|
195
|
-
// Web export with no actor — build-time validator should have errored.
|
|
196
189
|
console.warn(
|
|
197
190
|
'Tessera xAPI: explicit destination has no actor and no derivation source — skipping.',
|
|
198
191
|
);
|
package/dist/audit-BBJpQGqb.js
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { o as generateManifest, r as normalizeA11y, s as readCourseConfig } from "./validation-B-xTvM9B.js";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { existsSync, writeFileSync } from "node:fs";
|
|
4
|
-
//#region src/plugin/a11y/audit.ts
|
|
5
|
-
const IMPACT_RANK = {
|
|
6
|
-
minor: 1,
|
|
7
|
-
moderate: 2,
|
|
8
|
-
serious: 3,
|
|
9
|
-
critical: 4
|
|
10
|
-
};
|
|
11
|
-
const AUDIT_ENV_FLAG = "TESSERA_A11Y_AUDIT";
|
|
12
|
-
/** Map the `a11y.standard` enum to axe's cumulative `runOnly` tag list. */
|
|
13
|
-
function axeTags(standard) {
|
|
14
|
-
switch (standard) {
|
|
15
|
-
case "wcag2a": return ["wcag2a"];
|
|
16
|
-
case "wcag21aa": return [
|
|
17
|
-
"wcag2a",
|
|
18
|
-
"wcag2aa",
|
|
19
|
-
"wcag21a",
|
|
20
|
-
"wcag21aa"
|
|
21
|
-
];
|
|
22
|
-
default: return ["wcag2a", "wcag2aa"];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
/** axe-applicable ignore entries: drop the Tier-1a/1b namespaces. */
|
|
26
|
-
function axeIgnoreRules(ignore) {
|
|
27
|
-
return ignore.filter((id) => !id.startsWith("tessera/") && !id.startsWith("a11y_"));
|
|
28
|
-
}
|
|
29
|
-
function isFailing(v, thresholdRank) {
|
|
30
|
-
return !v.impact || IMPACT_RANK[v.impact] >= thresholdRank;
|
|
31
|
-
}
|
|
32
|
-
async function tryImport(specifier) {
|
|
33
|
-
return import(specifier);
|
|
34
|
-
}
|
|
35
|
-
async function loadDeps() {
|
|
36
|
-
let chromium;
|
|
37
|
-
for (const spec of ["playwright", "@playwright/test"]) try {
|
|
38
|
-
const mod = await tryImport(spec);
|
|
39
|
-
if (mod.chromium) {
|
|
40
|
-
chromium = mod.chromium;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
} catch {}
|
|
44
|
-
if (!chromium) return {
|
|
45
|
-
ok: false,
|
|
46
|
-
missing: "playwright"
|
|
47
|
-
};
|
|
48
|
-
try {
|
|
49
|
-
const mod = await tryImport("@axe-core/playwright");
|
|
50
|
-
if (!mod.default) return {
|
|
51
|
-
ok: false,
|
|
52
|
-
missing: "@axe-core/playwright"
|
|
53
|
-
};
|
|
54
|
-
return {
|
|
55
|
-
ok: true,
|
|
56
|
-
deps: {
|
|
57
|
-
chromium,
|
|
58
|
-
AxeBuilder: mod.default
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
} catch {
|
|
62
|
-
return {
|
|
63
|
-
ok: false,
|
|
64
|
-
missing: "@axe-core/playwright"
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Run the Tier-2 runtime accessibility audit against a built course. Builds (or
|
|
70
|
-
* reuses) dist/, serves it, drives Playwright + axe-core over each page, writes
|
|
71
|
-
* a11y-report.json, and returns a process exit code (0 pass, 1 fail/error).
|
|
72
|
-
*/
|
|
73
|
-
async function runAudit(projectRoot, options = {}) {
|
|
74
|
-
const threshold = options.threshold ?? "serious";
|
|
75
|
-
const deps = await loadDeps();
|
|
76
|
-
if (!deps.ok) {
|
|
77
|
-
console.error("\x1B[31m[tessera a11y]\x1B[0m Tier 2 needs Playwright + axe-core, which aren't installed.\n Install them to run the runtime audit:\n npm i -D playwright @axe-core/playwright\n npx playwright install chromium");
|
|
78
|
-
return 1;
|
|
79
|
-
}
|
|
80
|
-
const { chromium, AxeBuilder } = deps.deps;
|
|
81
|
-
const read = readCourseConfig(projectRoot);
|
|
82
|
-
const settings = normalizeA11y(read.ok ? read.config.a11y : void 0);
|
|
83
|
-
const tags = axeTags(settings.standard);
|
|
84
|
-
const disableRules = axeIgnoreRules(settings.ignore);
|
|
85
|
-
const manifest = generateManifest(resolve(projectRoot, "pages"));
|
|
86
|
-
const vite = await import("vite");
|
|
87
|
-
const auditDist = resolve(projectRoot, "node_modules", ".tessera-a11y");
|
|
88
|
-
const distHtml = resolve(auditDist, "index.html");
|
|
89
|
-
const prevEnv = process.env[AUDIT_ENV_FLAG];
|
|
90
|
-
process.env[AUDIT_ENV_FLAG] = "1";
|
|
91
|
-
let server;
|
|
92
|
-
try {
|
|
93
|
-
if (options.rebuild || !existsSync(distHtml)) {
|
|
94
|
-
console.log("[tessera a11y] Building course…");
|
|
95
|
-
await vite.build({
|
|
96
|
-
root: projectRoot,
|
|
97
|
-
build: {
|
|
98
|
-
outDir: auditDist,
|
|
99
|
-
emptyOutDir: true
|
|
100
|
-
},
|
|
101
|
-
logLevel: "warn"
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
server = await vite.preview({
|
|
105
|
-
root: projectRoot,
|
|
106
|
-
build: { outDir: auditDist },
|
|
107
|
-
preview: {
|
|
108
|
-
port: 0,
|
|
109
|
-
host: "127.0.0.1"
|
|
110
|
-
},
|
|
111
|
-
logLevel: "warn"
|
|
112
|
-
});
|
|
113
|
-
const baseUrl = server.resolvedUrls?.local?.[0];
|
|
114
|
-
if (!baseUrl) {
|
|
115
|
-
console.error("[tessera a11y] Could not determine preview server URL.");
|
|
116
|
-
return 1;
|
|
117
|
-
}
|
|
118
|
-
const browser = await chromium.launch();
|
|
119
|
-
const pages = [];
|
|
120
|
-
try {
|
|
121
|
-
const page = await (await browser.newContext()).newPage();
|
|
122
|
-
const auditUrl = new URL(baseUrl);
|
|
123
|
-
auditUrl.searchParams.set("__tessera_audit", "1");
|
|
124
|
-
await page.goto(auditUrl.href, { waitUntil: "networkidle" });
|
|
125
|
-
await page.waitForSelector("#tessera-app", { timeout: 2e4 });
|
|
126
|
-
const scan = async () => {
|
|
127
|
-
const builder = new AxeBuilder({ page }).withTags(tags);
|
|
128
|
-
if (disableRules.length > 0) builder.disableRules(disableRules);
|
|
129
|
-
return (await builder.analyze()).violations.map((v) => ({
|
|
130
|
-
id: v.id,
|
|
131
|
-
impact: v.impact ?? null,
|
|
132
|
-
help: v.help,
|
|
133
|
-
helpUrl: v.helpUrl,
|
|
134
|
-
nodes: v.nodes.length
|
|
135
|
-
}));
|
|
136
|
-
};
|
|
137
|
-
const navCount = await page.locator("button.tessera-nav-page").count();
|
|
138
|
-
if (navCount === 0) pages.push({
|
|
139
|
-
index: 0,
|
|
140
|
-
title: manifest.pages[0]?.title ?? "(entry)",
|
|
141
|
-
violations: await scan()
|
|
142
|
-
});
|
|
143
|
-
else for (let i = 0; i < navCount; i++) {
|
|
144
|
-
const btn = page.locator("button.tessera-nav-page").nth(i);
|
|
145
|
-
const title = (await btn.textContent())?.trim() || `Page ${i + 1}`;
|
|
146
|
-
await btn.click();
|
|
147
|
-
await page.waitForFunction((idx) => document.querySelectorAll("button.tessera-nav-page")[idx]?.getAttribute("aria-current") === "page", i, { timeout: 2e4 });
|
|
148
|
-
await page.waitForLoadState("networkidle");
|
|
149
|
-
pages.push({
|
|
150
|
-
index: i,
|
|
151
|
-
title,
|
|
152
|
-
violations: await scan()
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
} finally {
|
|
156
|
-
await browser.close();
|
|
157
|
-
}
|
|
158
|
-
const thresholdRank = IMPACT_RANK[threshold];
|
|
159
|
-
let totalViolations = 0;
|
|
160
|
-
let failingViolations = 0;
|
|
161
|
-
for (const p of pages) for (const v of p.violations) {
|
|
162
|
-
totalViolations++;
|
|
163
|
-
if (isFailing(v, thresholdRank)) failingViolations++;
|
|
164
|
-
}
|
|
165
|
-
const report = {
|
|
166
|
-
standard: settings.standard,
|
|
167
|
-
threshold,
|
|
168
|
-
pages,
|
|
169
|
-
totalViolations,
|
|
170
|
-
failingViolations,
|
|
171
|
-
passed: failingViolations === 0
|
|
172
|
-
};
|
|
173
|
-
const reportPath = resolve(projectRoot, "a11y-report.json");
|
|
174
|
-
writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
175
|
-
printSummary(report, reportPath);
|
|
176
|
-
return report.passed ? 0 : 1;
|
|
177
|
-
} catch (err) {
|
|
178
|
-
console.error(`\x1b[31m[tessera a11y]\x1b[0m Audit could not complete: ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
-
return 1;
|
|
180
|
-
} finally {
|
|
181
|
-
server?.httpServer?.close?.();
|
|
182
|
-
if (prevEnv === void 0) delete process.env[AUDIT_ENV_FLAG];
|
|
183
|
-
else process.env[AUDIT_ENV_FLAG] = prevEnv;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function printSummary(report, reportPath) {
|
|
187
|
-
const thresholdRank = IMPACT_RANK[report.threshold];
|
|
188
|
-
for (const p of report.pages) {
|
|
189
|
-
if (p.violations.length === 0) {
|
|
190
|
-
console.log(`\x1b[32m ✓\x1b[0m ${p.title}`);
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
const mark = p.violations.some((v) => isFailing(v, thresholdRank)) ? "\x1B[31m ✗\x1B[0m" : "\x1B[33m ⚠\x1B[0m";
|
|
194
|
-
console.log(`${mark} ${p.title}`);
|
|
195
|
-
for (const v of p.violations) console.log(` [${v.impact ?? "n/a"}] ${v.id} — ${v.help} (${v.nodes} node${v.nodes === 1 ? "" : "s"})`);
|
|
196
|
-
}
|
|
197
|
-
console.log(`\n[tessera a11y] Report written to ${reportPath}`);
|
|
198
|
-
if (report.passed) console.log(`\x1b[32m[tessera a11y] Passed\x1b[0m — ${report.totalViolations} total finding(s), none at/above "${report.threshold}".`);
|
|
199
|
-
else console.log(`\x1b[31m[tessera a11y] Failed\x1b[0m — ${report.failingViolations} finding(s) at/above "${report.threshold}" (of ${report.totalViolations} total).`);
|
|
200
|
-
}
|
|
201
|
-
//#endregion
|
|
202
|
-
export { runAudit as n, AUDIT_ENV_FLAG as t };
|
|
203
|
-
|
|
204
|
-
//# sourceMappingURL=audit-BBJpQGqb.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"audit-BBJpQGqb.js","names":[],"sources":["../src/plugin/a11y/audit.ts"],"sourcesContent":["import { existsSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { generateManifest, readCourseConfig } from '../manifest.js';\nimport { normalizeA11y, type A11ySettings } from '../validation.js';\n\nexport interface AuditOptions {\n /** Minimum violation impact that fails the run (CI gate). Default 'serious'. */\n threshold?: ImpactLevel;\n /** Force a fresh `vite build` even if dist/ exists. */\n rebuild?: boolean;\n}\n\nexport type ImpactLevel = 'minor' | 'moderate' | 'serious' | 'critical';\n\nconst IMPACT_RANK: Record<ImpactLevel, number> = {\n minor: 1,\n moderate: 2,\n serious: 3,\n critical: 4,\n};\n\n// Set by runAudit during its build/preview; the plugin forces the WebAdapter,\n// skips export packaging, and stubs xAPI while it's set. See plugin/index.ts.\nexport const AUDIT_ENV_FLAG = 'TESSERA_A11Y_AUDIT';\n\ninterface AxeViolation {\n id: string;\n impact: ImpactLevel | null;\n help: string;\n helpUrl: string;\n nodes: number;\n}\n\ninterface PageAuditResult {\n index: number;\n title: string;\n violations: AxeViolation[];\n}\n\ninterface AuditReport {\n standard: A11ySettings['standard'];\n threshold: ImpactLevel;\n pages: PageAuditResult[];\n totalViolations: number;\n failingViolations: number;\n passed: boolean;\n}\n\n/** Map the `a11y.standard` enum to axe's cumulative `runOnly` tag list. */\nexport function axeTags(standard: A11ySettings['standard']): string[] {\n switch (standard) {\n case 'wcag2a':\n return ['wcag2a'];\n case 'wcag21aa':\n return ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'];\n case 'wcag2aa':\n default:\n return ['wcag2a', 'wcag2aa'];\n }\n}\n\n/** axe-applicable ignore entries: drop the Tier-1a/1b namespaces. */\nexport function axeIgnoreRules(ignore: string[]): string[] {\n return ignore.filter(\n (id) => !id.startsWith('tessera/') && !id.startsWith('a11y_'),\n );\n}\n\n// A violation with no impact is treated as failing rather than slipping the\n// gate at every threshold.\nfunction isFailing(v: AxeViolation, thresholdRank: number): boolean {\n return !v.impact || IMPACT_RANK[v.impact] >= thresholdRank;\n}\n\n// Optional deps loaded by variable specifier so tsc doesn't require them to be\n// installed — Tier 2 is opt-in and the absence is handled with a clear message.\nasync function tryImport(specifier: string): Promise<unknown> {\n return import(specifier);\n}\n\ninterface LoadedDeps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n chromium: any;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n AxeBuilder: any;\n}\n\nasync function loadDeps(): Promise<\n { ok: true; deps: LoadedDeps } | { ok: false; missing: string }\n> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let chromium: any;\n for (const spec of ['playwright', '@playwright/test']) {\n try {\n const mod = (await tryImport(spec)) as { chromium?: unknown };\n if (mod.chromium) {\n chromium = mod.chromium;\n break;\n }\n } catch {\n // try the next specifier\n }\n }\n if (!chromium) return { ok: false, missing: 'playwright' };\n\n try {\n const mod = (await tryImport('@axe-core/playwright')) as {\n default?: unknown;\n };\n if (!mod.default) return { ok: false, missing: '@axe-core/playwright' };\n return { ok: true, deps: { chromium, AxeBuilder: mod.default } };\n } catch {\n return { ok: false, missing: '@axe-core/playwright' };\n }\n}\n\n/**\n * Run the Tier-2 runtime accessibility audit against a built course. Builds (or\n * reuses) dist/, serves it, drives Playwright + axe-core over each page, writes\n * a11y-report.json, and returns a process exit code (0 pass, 1 fail/error).\n */\nexport async function runAudit(\n projectRoot: string,\n options: AuditOptions = {},\n): Promise<number> {\n const threshold: ImpactLevel = options.threshold ?? 'serious';\n\n const deps = await loadDeps();\n if (!deps.ok) {\n console.error(\n `\\x1b[31m[tessera a11y]\\x1b[0m Tier 2 needs Playwright + axe-core, which aren't installed.\\n` +\n ` Install them to run the runtime audit:\\n` +\n ` npm i -D playwright @axe-core/playwright\\n` +\n ` npx playwright install chromium`,\n );\n return 1;\n }\n const { chromium, AxeBuilder } = deps.deps;\n\n const read = readCourseConfig(projectRoot);\n const settings = normalizeA11y(read.ok ? read.config.a11y : undefined);\n const tags = axeTags(settings.standard);\n const disableRules = axeIgnoreRules(settings.ignore);\n\n const manifest = generateManifest(resolve(projectRoot, 'pages'));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const vite = (await import('vite')) as any;\n\n // A throwaway web build, kept out of dist/ so a real LMS export is untouched.\n const auditDist = resolve(projectRoot, 'node_modules', '.tessera-a11y');\n const distHtml = resolve(auditDist, 'index.html');\n\n const prevEnv = process.env[AUDIT_ENV_FLAG];\n process.env[AUDIT_ENV_FLAG] = '1';\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let server: any;\n try {\n if (options.rebuild || !existsSync(distHtml)) {\n console.log('[tessera a11y] Building course…');\n await vite.build({\n root: projectRoot,\n build: { outDir: auditDist, emptyOutDir: true },\n logLevel: 'warn',\n });\n }\n\n server = await vite.preview({\n root: projectRoot,\n build: { outDir: auditDist },\n preview: { port: 0, host: '127.0.0.1' },\n logLevel: 'warn',\n });\n const baseUrl: string | undefined = server.resolvedUrls?.local?.[0];\n if (!baseUrl) {\n console.error('[tessera a11y] Could not determine preview server URL.');\n return 1;\n }\n\n const browser = await chromium.launch();\n const pages: PageAuditResult[] = [];\n try {\n // axe-core/playwright requires a page from an explicit context.\n const context = await browser.newContext();\n const page = await context.newPage();\n // ?__tessera_audit unlocks navigation so quiz-gated pages can be scanned.\n const auditUrl = new URL(baseUrl);\n auditUrl.searchParams.set('__tessera_audit', '1');\n await page.goto(auditUrl.href, { waitUntil: 'networkidle' });\n await page.waitForSelector('#tessera-app', { timeout: 20_000 });\n\n const scan = async (): Promise<AxeViolation[]> => {\n const builder = new AxeBuilder({ page }).withTags(tags);\n if (disableRules.length > 0) builder.disableRules(disableRules);\n const out = await builder.analyze();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return out.violations.map((v: any) => ({\n id: v.id,\n impact: v.impact ?? null,\n help: v.help,\n helpUrl: v.helpUrl,\n nodes: v.nodes.length,\n }));\n };\n\n const navCount = await page.locator('button.tessera-nav-page').count();\n if (navCount === 0) {\n // No sidebar (custom chrome) — audit whatever is rendered at the entry.\n pages.push({\n index: 0,\n title: manifest.pages[0]?.title ?? '(entry)',\n violations: await scan(),\n });\n } else {\n for (let i = 0; i < navCount; i++) {\n const btn = page.locator('button.tessera-nav-page').nth(i);\n const title = (await btn.textContent())?.trim() || `Page ${i + 1}`;\n await btn.click();\n await page.waitForFunction(\n (idx: number) =>\n document\n .querySelectorAll('button.tessera-nav-page')\n [idx]?.getAttribute('aria-current') === 'page',\n i,\n { timeout: 20_000 },\n );\n await page.waitForLoadState('networkidle');\n pages.push({ index: i, title, violations: await scan() });\n }\n }\n } finally {\n await browser.close();\n }\n\n const thresholdRank = IMPACT_RANK[threshold];\n let totalViolations = 0;\n let failingViolations = 0;\n for (const p of pages) {\n for (const v of p.violations) {\n totalViolations++;\n if (isFailing(v, thresholdRank)) failingViolations++;\n }\n }\n\n const report: AuditReport = {\n standard: settings.standard,\n threshold,\n pages,\n totalViolations,\n failingViolations,\n passed: failingViolations === 0,\n };\n const reportPath = resolve(projectRoot, 'a11y-report.json');\n writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');\n\n printSummary(report, reportPath);\n return report.passed ? 0 : 1;\n } catch (err) {\n console.error(\n `\\x1b[31m[tessera a11y]\\x1b[0m Audit could not complete: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return 1;\n } finally {\n server?.httpServer?.close?.();\n if (prevEnv === undefined) delete process.env[AUDIT_ENV_FLAG];\n else process.env[AUDIT_ENV_FLAG] = prevEnv;\n }\n}\n\nfunction printSummary(report: AuditReport, reportPath: string): void {\n const thresholdRank = IMPACT_RANK[report.threshold];\n for (const p of report.pages) {\n if (p.violations.length === 0) {\n console.log(`\\x1b[32m ✓\\x1b[0m ${p.title}`);\n continue;\n }\n const failing = p.violations.some((v) => isFailing(v, thresholdRank));\n const mark = failing ? '\\x1b[31m ✗\\x1b[0m' : '\\x1b[33m ⚠\\x1b[0m';\n console.log(`${mark} ${p.title}`);\n for (const v of p.violations) {\n console.log(\n ` [${v.impact ?? 'n/a'}] ${v.id} — ${v.help} (${v.nodes} node${v.nodes === 1 ? '' : 's'})`,\n );\n }\n }\n console.log(`\\n[tessera a11y] Report written to ${reportPath}`);\n if (report.passed) {\n console.log(\n `\\x1b[32m[tessera a11y] Passed\\x1b[0m — ${report.totalViolations} total finding(s), none at/above \"${report.threshold}\".`,\n );\n } else {\n console.log(\n `\\x1b[31m[tessera a11y] Failed\\x1b[0m — ${report.failingViolations} finding(s) at/above \"${report.threshold}\" (of ${report.totalViolations} total).`,\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|
package/dist/plugin/a11y-cli.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { n as runAudit } from "../audit-BBJpQGqb.js";
|
|
3
|
-
//#region src/plugin/a11y-cli.ts
|
|
4
|
-
const VALID_THRESHOLDS = [
|
|
5
|
-
"minor",
|
|
6
|
-
"moderate",
|
|
7
|
-
"serious",
|
|
8
|
-
"critical"
|
|
9
|
-
];
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
let threshold;
|
|
12
|
-
let rebuild = false;
|
|
13
|
-
for (let i = 0; i < args.length; i++) {
|
|
14
|
-
const arg = args[i];
|
|
15
|
-
if (arg === "--threshold") {
|
|
16
|
-
const value = args[++i];
|
|
17
|
-
if (!VALID_THRESHOLDS.includes(value)) {
|
|
18
|
-
console.error(`[tessera a11y] --threshold must be one of: ${VALID_THRESHOLDS.join(", ")}`);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
threshold = value;
|
|
22
|
-
} else if (arg === "--build") rebuild = true;
|
|
23
|
-
else {
|
|
24
|
-
console.error(`[tessera a11y] Unknown argument: ${arg}`);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const code = await runAudit(process.cwd(), {
|
|
29
|
-
threshold,
|
|
30
|
-
rebuild
|
|
31
|
-
});
|
|
32
|
-
process.exit(code);
|
|
33
|
-
//#endregion
|
|
34
|
-
export {};
|
|
35
|
-
|
|
36
|
-
//# sourceMappingURL=a11y-cli.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"a11y-cli.js","names":[],"sources":["../../src/plugin/a11y-cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runAudit, type ImpactLevel } from './a11y/audit.js';\n\nconst VALID_THRESHOLDS: ImpactLevel[] = [\n 'minor',\n 'moderate',\n 'serious',\n 'critical',\n];\n\nconst args = process.argv.slice(2);\nlet threshold: ImpactLevel | undefined;\nlet rebuild = false;\n\nfor (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--threshold') {\n const value = args[++i] as ImpactLevel;\n if (!VALID_THRESHOLDS.includes(value)) {\n console.error(\n `[tessera a11y] --threshold must be one of: ${VALID_THRESHOLDS.join(', ')}`,\n );\n process.exit(1);\n }\n threshold = value;\n } else if (arg === '--build') {\n rebuild = true;\n } else {\n console.error(`[tessera a11y] Unknown argument: ${arg}`);\n process.exit(1);\n }\n}\n\nconst code = await runAudit(process.cwd(), { threshold, rebuild });\nprocess.exit(code);\n"],"mappings":";;;AAGA,MAAM,mBAAkC;CACtC;CACA;CACA;CACA;CACD;AAED,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAClC,IAAI;AACJ,IAAI,UAAU;AAEd,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;CACpC,MAAM,MAAM,KAAK;CACjB,IAAI,QAAQ,eAAe;EACzB,MAAM,QAAQ,KAAK,EAAE;EACrB,IAAI,CAAC,iBAAiB,SAAS,MAAM,EAAE;GACrC,QAAQ,MACN,8CAA8C,iBAAiB,KAAK,KAAK,GAC1E;GACD,QAAQ,KAAK,EAAE;;EAEjB,YAAY;QACP,IAAI,QAAQ,WACjB,UAAU;MACL;EACL,QAAQ,MAAM,oCAAoC,MAAM;EACxD,QAAQ,KAAK,EAAE;;;AAInB,MAAM,OAAO,MAAM,SAAS,QAAQ,KAAK,EAAE;CAAE;CAAW;CAAS,CAAC;AAClE,QAAQ,KAAK,KAAK"}
|