tessera-learn 0.1.0 → 0.2.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/AGENTS.md +63 -13
- package/README.md +4 -4
- package/dist/{audit-CzKAXy3Y.js → audit-BNrvFHq_.js} +66 -26
- package/dist/audit-BNrvFHq_.js.map +1 -0
- package/dist/{build-commands-D101M_qb.js → build-commands-BWnATKat.js} +6 -6
- package/dist/build-commands-BWnATKat.js.map +1 -0
- package/dist/{inline-config-DYHT51G8.js → inline-config-Dudu5r8w.js} +8 -6
- package/dist/inline-config-Dudu5r8w.js.map +1 -0
- package/dist/plugin/cli.d.ts +6 -2
- package/dist/plugin/cli.d.ts.map +1 -1
- package/dist/plugin/cli.js +242 -26
- package/dist/plugin/cli.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2 -2
- package/dist/{plugin-y35ym9A3.js → plugin-diNZaDJK.js} +4 -17
- package/dist/plugin-diNZaDJK.js.map +1 -0
- package/package.json +10 -1
- package/src/plugin/a11y/audit.ts +83 -22
- package/src/plugin/a11y-cli.ts +6 -2
- package/src/plugin/build-commands.ts +10 -4
- package/src/plugin/cli.ts +63 -20
- package/src/plugin/course-root.ts +98 -0
- package/src/plugin/duplicate-cli.ts +74 -0
- package/src/plugin/inline-config.ts +13 -2
- package/src/plugin/new-cli.ts +51 -0
- package/src/plugin/project-name.ts +29 -0
- package/src/plugin/template-copy.ts +43 -0
- package/src/plugin/validate-cli.ts +1 -1
- package/src/runtime/App.svelte +20 -1
- package/src/virtual.d.ts +6 -0
- package/templates/course/course.config.js +11 -0
- package/templates/course/layout.svelte +116 -0
- package/templates/course/pages/01-getting-started/01-welcome/_meta.js +1 -0
- package/templates/course/pages/01-getting-started/01-welcome/welcome.svelte +19 -0
- package/templates/course/pages/01-getting-started/_meta.js +1 -0
- package/templates/course/styles/custom.css +5 -0
- package/dist/audit-CzKAXy3Y.js.map +0 -1
- package/dist/build-commands-D101M_qb.js.map +0 -1
- package/dist/inline-config-DYHT51G8.js.map +0 -1
- package/dist/plugin-y35ym9A3.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -6,23 +6,73 @@ Build a course with built-in components, your own (via the hooks), or any mix. T
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## Workspaces
|
|
10
|
+
|
|
11
|
+
A Tessera project is a **workspace**: one `package.json` and one `node_modules` shared by **many courses**, plus a `shared/` design system. Each course is a self-contained folder under `courses/`.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
my-courses/
|
|
15
|
+
├── package.json # the one package — owns tessera-learn, svelte, scripts
|
|
16
|
+
├── shared/ # design system shared across courses (imported as $shared)
|
|
17
|
+
│ ├── Button.svelte
|
|
18
|
+
│ └── tokens.css
|
|
19
|
+
├── courses/
|
|
20
|
+
│ ├── starter-course/ # a course = a content folder (course.config.js, pages/, …)
|
|
21
|
+
│ └── <next course>/
|
|
22
|
+
└── AGENTS.md / CLAUDE.md # pointers to this guide (workspace root only)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Everything else in this guide describes a single course** — i.e. the contents of one `courses/<name>/` folder (`course.config.js`, `layout.svelte`, `pages/`, `styles/`).
|
|
26
|
+
|
|
27
|
+
**Open the workspace folder** (not an individual course) so this guide stays in scope and `$shared` resolves.
|
|
28
|
+
|
|
29
|
+
### Working with courses
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pnpm tessera new <name> # scaffold courses/<name>/ (no install — deps already here)
|
|
33
|
+
pnpm tessera duplicate <source> <new> # copy an existing course to courses/<new>/
|
|
34
|
+
pnpm tessera dev <name> # run a command against a named course…
|
|
35
|
+
cd courses/<name> && pnpm exec tessera dev # …or cd into the course and run it without a name
|
|
36
|
+
pnpm tessera export <name> # each course exports independently to its own LMS package
|
|
37
|
+
cd courses/<name> && pnpm exec tessera export # …this works for every command, not just dev
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A **bare command at the workspace root errors** and lists the available courses — it never silently picks one, so its meaning can't change as you add courses. Name the course, or `cd` into its folder. (The scaffolded root scripts — `pnpm dev`, `pnpm export`, … — pass straight through, so `pnpm dev <course>` runs that course and a bare `pnpm dev` errors just the same.)
|
|
41
|
+
|
|
42
|
+
### Sharing across courses with `$shared`
|
|
43
|
+
|
|
44
|
+
`$shared` resolves to the workspace `shared/` directory, so any course can import the shared design system:
|
|
45
|
+
|
|
46
|
+
```svelte
|
|
47
|
+
<script>
|
|
48
|
+
import Button from '$shared/Button.svelte';
|
|
49
|
+
import '$shared/tokens.css';
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<Button>Continue</Button>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`$shared` is bundled into each course's export at build time, so it ships in every SCORM/cmi5/web package with no extra wiring.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
9
59
|
## Running the project
|
|
10
60
|
|
|
11
|
-
From the
|
|
61
|
+
From the workspace root (set up for `pnpm` — Node's corepack provisions it automatically). The `dev`/`export`/`validate`/`check` scripts take the course to run; a bare command lists the workspace's courses rather than picking one:
|
|
12
62
|
|
|
13
63
|
```bash
|
|
14
64
|
pnpm install # first time only
|
|
15
|
-
pnpm dev
|
|
16
|
-
pnpm export
|
|
17
|
-
pnpm validate
|
|
18
|
-
pnpm check
|
|
65
|
+
pnpm dev <course> # dev server at http://localhost:5173 (Ctrl+C to stop)
|
|
66
|
+
pnpm export <course> # build + package for the LMS standard configured in course.config.js
|
|
67
|
+
pnpm validate <course> # run project validation only — no server, no bundle
|
|
68
|
+
pnpm check <course> # validate, then the runtime accessibility audit (axe) over the built course
|
|
19
69
|
```
|
|
20
70
|
|
|
21
71
|
The dev server hot-reloads as you edit pages, layouts, components, and `course.config.js`. The `export` command produces a SCORM 1.2, SCORM 2004, cmi5, or static-web bundle depending on `course.config.js`.
|
|
22
72
|
|
|
23
|
-
`pnpm validate
|
|
73
|
+
`pnpm validate <course>` runs the same checks as `dev` and `export` (page syntax, manifest shape, `pageConfig`, question components, asset references, LMS data-contract bypass, and the static accessibility rules) and exits non-zero if any fail. Use it as a fast feedback loop after editing — it's the quickest way to confirm a change is structurally sound.
|
|
24
74
|
|
|
25
|
-
`pnpm check
|
|
75
|
+
`pnpm check <course>` runs `validate` and then the deeper, opt-in pass (`tessera a11y`): it builds the course, renders every page in a headless browser, and runs [axe-core](https://github.com/dequelabs/axe-core) to catch issues a static scan can't see (computed ARIA, real rendered contrast). The runtime audit drives Playwright, which needs a browser binary once per machine:
|
|
26
76
|
|
|
27
77
|
```bash
|
|
28
78
|
pnpm exec playwright install chromium
|
|
@@ -842,7 +892,7 @@ export default {
|
|
|
842
892
|
|
|
843
893
|
### Build output
|
|
844
894
|
|
|
845
|
-
`pnpm export
|
|
895
|
+
`pnpm export <course>` (which wraps `vite build`) writes:
|
|
846
896
|
|
|
847
897
|
| `export.standard` | What ships | Where |
|
|
848
898
|
| ----------------- | ------------------------------------- | ---------------------------------------- |
|
|
@@ -855,7 +905,7 @@ For LMS exports, upload the zip via your LMS's import flow. For web export, the
|
|
|
855
905
|
|
|
856
906
|
### Validation
|
|
857
907
|
|
|
858
|
-
The Vite plugin runs project validation on every dev start and build (page syntax, manifest shape, `pageConfig` parseability, question components, asset references, LMS data-contract bypass, etc.). Errors abort the build and print as `[tessera error] ...`; warnings print as `[tessera warning] ...` and don't block. Run `pnpm validate
|
|
908
|
+
The Vite plugin runs project validation on every dev start and build (page syntax, manifest shape, `pageConfig` parseability, question components, asset references, LMS data-contract bypass, etc.). Errors abort the build and print as `[tessera error] ...`; warnings print as `[tessera warning] ...` and don't block. Run `pnpm validate <course>` to check without building.
|
|
859
909
|
|
|
860
910
|
---
|
|
861
911
|
|
|
@@ -865,7 +915,7 @@ Tessera checks accessibility in two passes, plus components that are accessible
|
|
|
865
915
|
|
|
866
916
|
**Static checks** run inside `validate`, `dev`, and `export` — no extra setup. They cover what's visible in your source: `<Image>` alt-or-`decorative`, `<Video>`/`<Audio>` `title` + captions/transcript, empty question option/answer labels, skipped heading levels (e.g. `h2` → `h4`), `branding.primaryColor` contrast against white, and a well-formed `language` tag. They also route the Svelte compiler's own `a11y_*` warnings through the reporter. Each diagnostic carries a rule ID in brackets (e.g. `[tessera/image-alt]`, `[a11y_missing_attribute]`) — that ID is what `a11y.ignore` and `a11y.level` match.
|
|
867
917
|
|
|
868
|
-
**Runtime audit** is the opt-in deep pass: `
|
|
918
|
+
**Runtime audit** is the opt-in deep pass: `pnpm a11y <course>` (run it directly, or via `pnpm check <course>`, which runs `validate` first) builds the course, renders **every** page in a headless browser (including pages gated behind a quiz), runs [axe-core](https://github.com/dequelabs/axe-core), writes `a11y-report.json`, and exits non-zero on any violation at or above an impact threshold (default `serious`). It catches what a static scan can't — computed ARIA, focus order, real rendered contrast.
|
|
869
919
|
|
|
870
920
|
The runtime audit drives Playwright, which needs a browser binary once per machine:
|
|
871
921
|
|
|
@@ -874,9 +924,9 @@ pnpm exec playwright install chromium
|
|
|
874
924
|
```
|
|
875
925
|
|
|
876
926
|
```bash
|
|
877
|
-
tessera a11y
|
|
878
|
-
tessera a11y --threshold minor
|
|
879
|
-
tessera a11y --build
|
|
927
|
+
pnpm exec tessera a11y # audit (threshold: serious)
|
|
928
|
+
pnpm exec tessera a11y --threshold minor # stricter
|
|
929
|
+
pnpm exec tessera a11y --build # force a fresh build first
|
|
880
930
|
```
|
|
881
931
|
|
|
882
932
|
The audit renders the course with the web adapter, so it works regardless of your `export.standard` — you don't need an LMS to run it.
|
package/README.md
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
LMS tracking runtime for interactive learning content. One adapter layer (SCORM 1.2, SCORM 2004 4th Edition, cmi5, static Web), your choice of components.
|
|
4
4
|
|
|
5
|
-
Tessera is a toolkit for building interactive online courses, designed for AI-assisted authoring: pages are `.svelte` files, the runtime locks the LMS data contract (tracking, completion, scoring, persistence), and an AI agent working in the
|
|
5
|
+
Tessera is a toolkit for building interactive online courses, designed for AI-assisted authoring: pages are `.svelte` files, the runtime locks the LMS data contract (tracking, completion, scoring, persistence), and an AI agent working in the workspace follows the conventions in `AGENTS.md` to write pages and components. This package is the runtime; you typically don't depend on it directly — `create-tessera` scaffolds a workspace that pins it for you. A workspace is one package that holds many courses under `courses/<name>/` and a `shared/` design system imported as `$shared`.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
You probably don't want to install this package directly. Use the scaffolder:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
12
|
+
pnpm create tessera@latest my-course
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
That creates a
|
|
15
|
+
That creates a workspace with Tessera wired up, a seed course, and the authoring guide (`AGENTS.md`) at the workspace root. Add more courses with `pnpm tessera new <name>`.
|
|
16
16
|
|
|
17
17
|
## What's included
|
|
18
18
|
|
|
19
19
|
- **Hooks** (`tessera-learn`): `useQuestion`, `useQuiz`, `useNavigation`, `useProgress`, `usePersistence`, `useXAPI`.
|
|
20
|
-
- **Vite plugin** (`tessera-learn/plugin`): `tesseraPlugin()` — wires page/layout discovery, the LMS adapter, and the export pipeline. The `tessera` CLI (`dev`/`export`) runs Vite with this plugin for you, so scaffolded
|
|
20
|
+
- **Vite plugin** (`tessera-learn/plugin`): `tesseraPlugin()` — wires page/layout discovery, the LMS adapter, the `$shared` alias, and the export pipeline. The `tessera` CLI (`new`/`dev`/`export`/`validate`/`a11y`/`check`) runs Vite with this plugin for you, so scaffolded workspaces need no `vite.config.js`.
|
|
21
21
|
- **Built-in components** (`tessera-learn`): `Callout`, `Image`, `Audio`, `Video`, `Accordion` / `AccordionItem`, `Carousel` / `CarouselSlide`, `RevealModal`, `Quiz`, `MultipleChoice`, `FillInTheBlank`, `Matching`, `Sorting`, `DefaultLayout`.
|
|
22
22
|
- **LMS adapters**: SCORM 1.2, SCORM 2004 4th Edition, cmi5, static Web — selected via `course.config.js` `export.standard`.
|
|
23
23
|
- **Accessibility checks**: static rules (alt text, media titles/captions, heading order, contrast, `<html lang>`) run inside validation and the build with zero extra dependencies; an opt-in runtime audit (`tessera a11y`, with `playwright` + `@axe-core/playwright` as optional peers) renders every page and gates on axe-core violations.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { basename, extname, relative, resolve } from "node:path";
|
|
1
|
+
import { basename, dirname, extname, relative, resolve } from "node:path";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import JSON5 from "json5";
|
|
4
4
|
import { Parser } from "acorn";
|
|
@@ -1347,6 +1347,19 @@ function crossValidate(config, pageResults, errors, warnings) {
|
|
|
1347
1347
|
}
|
|
1348
1348
|
}
|
|
1349
1349
|
//#endregion
|
|
1350
|
+
//#region src/plugin/package-root.ts
|
|
1351
|
+
function resolvePackageRoot() {
|
|
1352
|
+
const dir = import.meta.dirname;
|
|
1353
|
+
for (let up = dir; up !== dirname(up); up = dirname(up)) {
|
|
1354
|
+
const pkgPath = resolve(up, "package.json");
|
|
1355
|
+
if (existsSync(pkgPath)) try {
|
|
1356
|
+
const { name } = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1357
|
+
if (name === "tessera-learn") return up;
|
|
1358
|
+
} catch {}
|
|
1359
|
+
}
|
|
1360
|
+
return resolve(dir, "..", "..");
|
|
1361
|
+
}
|
|
1362
|
+
//#endregion
|
|
1350
1363
|
//#region src/plugin/a11y/audit.ts
|
|
1351
1364
|
const IMPACT_RANK = {
|
|
1352
1365
|
minor: 1,
|
|
@@ -1419,7 +1432,7 @@ async function loadDeps() {
|
|
|
1419
1432
|
* reuses) dist/, serves it, drives Playwright + axe-core over each page, writes
|
|
1420
1433
|
* a11y-report.json, and returns a process exit code (0 pass, 1 fail/error).
|
|
1421
1434
|
*/
|
|
1422
|
-
async function runAudit(projectRoot, options = {}) {
|
|
1435
|
+
async function runAudit(projectRoot, workspaceRoot, options = {}) {
|
|
1423
1436
|
const threshold = options.threshold ?? "serious";
|
|
1424
1437
|
const deps = await loadDeps();
|
|
1425
1438
|
if (!deps.ok) {
|
|
@@ -1433,8 +1446,8 @@ async function runAudit(projectRoot, options = {}) {
|
|
|
1433
1446
|
const disableRules = axeIgnoreRules(settings.ignore);
|
|
1434
1447
|
const manifest = generateManifest(resolve(projectRoot, "pages"));
|
|
1435
1448
|
const vite = await import("vite");
|
|
1436
|
-
const { resolveTesseraConfig } = await import("./inline-config-
|
|
1437
|
-
const auditBaseConfig = await resolveTesseraConfig(projectRoot, {
|
|
1449
|
+
const { resolveTesseraConfig } = await import("./inline-config-Dudu5r8w.js");
|
|
1450
|
+
const auditBaseConfig = await resolveTesseraConfig(projectRoot, workspaceRoot, {
|
|
1438
1451
|
command: "build",
|
|
1439
1452
|
mode: "production"
|
|
1440
1453
|
});
|
|
@@ -1497,23 +1510,29 @@ async function runAudit(projectRoot, options = {}) {
|
|
|
1497
1510
|
nodes: v.nodes.length
|
|
1498
1511
|
}));
|
|
1499
1512
|
};
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
await page.waitForFunction((idx) => document.querySelectorAll("button.tessera-nav-page")[idx]?.getAttribute("aria-current") === "page", i, { timeout: 2e4 });
|
|
1511
|
-
await page.waitForLoadState("networkidle");
|
|
1512
|
-
pages.push({
|
|
1513
|
-
index: i,
|
|
1513
|
+
const recordPage = async (index, title) => {
|
|
1514
|
+
const loadFailed = await page.evaluate(() => document.getElementById("tessera-app")?.dataset.tesseraPageError === "true");
|
|
1515
|
+
if (loadFailed) return {
|
|
1516
|
+
index,
|
|
1517
|
+
title,
|
|
1518
|
+
violations: [],
|
|
1519
|
+
loadFailed
|
|
1520
|
+
};
|
|
1521
|
+
return {
|
|
1522
|
+
index,
|
|
1514
1523
|
title,
|
|
1515
1524
|
violations: await scan()
|
|
1516
|
-
}
|
|
1525
|
+
};
|
|
1526
|
+
};
|
|
1527
|
+
const totalPages = manifest.pages.length;
|
|
1528
|
+
if (!await page.evaluate(() => typeof window.__tesseraAudit?.goToIndex === "function")) {
|
|
1529
|
+
if (totalPages > 1) console.warn(`\x1b[33m[tessera a11y]\x1b[0m Could not enumerate pages; auditing the entry page only (1 of ${totalPages}). The report records the reduced scope.`);
|
|
1530
|
+
pages.push(await recordPage(0, manifest.pages[0]?.title ?? "(entry)"));
|
|
1531
|
+
} else for (let i = 0; i < totalPages; i++) {
|
|
1532
|
+
await page.evaluate((idx) => window.__tesseraAudit.goToIndex(idx), i);
|
|
1533
|
+
await page.waitForFunction((idx) => document.getElementById("tessera-app")?.dataset.tesseraPageIndex === String(idx), i, { timeout: 2e4 });
|
|
1534
|
+
await page.waitForLoadState("networkidle");
|
|
1535
|
+
pages.push(await recordPage(i, manifest.pages[i]?.title ?? `Page ${i + 1}`));
|
|
1517
1536
|
}
|
|
1518
1537
|
} finally {
|
|
1519
1538
|
await browser.close();
|
|
@@ -1521,17 +1540,24 @@ async function runAudit(projectRoot, options = {}) {
|
|
|
1521
1540
|
const thresholdRank = IMPACT_RANK[threshold];
|
|
1522
1541
|
let totalViolations = 0;
|
|
1523
1542
|
let failingViolations = 0;
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
if (
|
|
1543
|
+
let pagesFailedToLoad = 0;
|
|
1544
|
+
for (const p of pages) {
|
|
1545
|
+
if (p.loadFailed) pagesFailedToLoad++;
|
|
1546
|
+
for (const v of p.violations) {
|
|
1547
|
+
totalViolations++;
|
|
1548
|
+
if (isFailing(v, thresholdRank)) failingViolations++;
|
|
1549
|
+
}
|
|
1527
1550
|
}
|
|
1528
1551
|
const report = {
|
|
1529
1552
|
standard: settings.standard,
|
|
1530
1553
|
threshold,
|
|
1531
1554
|
pages,
|
|
1555
|
+
pagesAudited: pages.length,
|
|
1556
|
+
totalPages: manifest.pages.length,
|
|
1557
|
+
pagesFailedToLoad,
|
|
1532
1558
|
totalViolations,
|
|
1533
1559
|
failingViolations,
|
|
1534
|
-
passed: failingViolations === 0
|
|
1560
|
+
passed: failingViolations === 0 && pagesFailedToLoad === 0
|
|
1535
1561
|
};
|
|
1536
1562
|
const reportPath = resolve(projectRoot, "a11y-report.json");
|
|
1537
1563
|
writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
@@ -1549,6 +1575,10 @@ async function runAudit(projectRoot, options = {}) {
|
|
|
1549
1575
|
function printSummary(report, reportPath) {
|
|
1550
1576
|
const thresholdRank = IMPACT_RANK[report.threshold];
|
|
1551
1577
|
for (const p of report.pages) {
|
|
1578
|
+
if (p.loadFailed) {
|
|
1579
|
+
console.log(`\x1b[31m ✗\x1b[0m ${p.title} — failed to load`);
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1552
1582
|
if (p.violations.length === 0) {
|
|
1553
1583
|
console.log(`\x1b[32m ✓\x1b[0m ${p.title}`);
|
|
1554
1584
|
continue;
|
|
@@ -1558,10 +1588,20 @@ function printSummary(report, reportPath) {
|
|
|
1558
1588
|
for (const v of p.violations) console.log(` [${v.impact ?? "n/a"}] ${v.id} — ${v.help} (${v.nodes} node${v.nodes === 1 ? "" : "s"})`);
|
|
1559
1589
|
}
|
|
1560
1590
|
console.log(`\n[tessera a11y] Report written to ${reportPath}`);
|
|
1591
|
+
if (report.pagesAudited < report.totalPages) console.log(`\x1b[33m[tessera a11y] Covered ${report.pagesAudited} of ${report.totalPages} page(s)\x1b[0m — reduced scope, the rest were not audited.`);
|
|
1592
|
+
else if (report.pagesFailedToLoad > 0) {
|
|
1593
|
+
const scanned = report.pagesAudited - report.pagesFailedToLoad;
|
|
1594
|
+
console.log(`[tessera a11y] Reached all ${report.totalPages} page(s); scanned ${scanned}, ${report.pagesFailedToLoad} failed to load.`);
|
|
1595
|
+
} else console.log(`[tessera a11y] Covered all ${report.totalPages} page(s).`);
|
|
1561
1596
|
if (report.passed) console.log(`\x1b[32m[tessera a11y] Passed\x1b[0m — ${report.totalViolations} total finding(s), none at/above "${report.threshold}".`);
|
|
1562
|
-
else
|
|
1597
|
+
else {
|
|
1598
|
+
const reasons = [];
|
|
1599
|
+
if (report.failingViolations > 0) reasons.push(`${report.failingViolations} finding(s) at/above "${report.threshold}" (of ${report.totalViolations} total)`);
|
|
1600
|
+
if (report.pagesFailedToLoad > 0) reasons.push(`${report.pagesFailedToLoad} page(s) failed to load`);
|
|
1601
|
+
console.log(`\x1b[31m[tessera a11y] Failed\x1b[0m — ${reasons.join("; ")}.`);
|
|
1602
|
+
}
|
|
1563
1603
|
}
|
|
1564
1604
|
//#endregion
|
|
1565
|
-
export {
|
|
1605
|
+
export { isPlausibleLanguageTag as a, validateProject as c, isIgnored as i, generateManifest as l, runAudit as n, normalizeA11y as o, resolvePackageRoot as r, reportValidationIssues as s, AUDIT_ENV_FLAG as t, readCourseConfig as u };
|
|
1566
1606
|
|
|
1567
|
-
//# sourceMappingURL=audit-
|
|
1607
|
+
//# sourceMappingURL=audit-BNrvFHq_.js.map
|