tessera-learn 0.0.10 → 0.0.13
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/README.md +1 -0
- package/dist/audit-BBJpQGqb.js +204 -0
- package/dist/audit-BBJpQGqb.js.map +1 -0
- package/dist/plugin/a11y-cli.d.ts +1 -0
- package/dist/plugin/a11y-cli.js +36 -0
- package/dist/plugin/a11y-cli.js.map +1 -0
- package/dist/plugin/cli.js +6 -3
- package/dist/plugin/cli.js.map +1 -1
- package/dist/plugin/index.d.ts +16 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +171 -140
- package/dist/plugin/index.js.map +1 -1
- package/dist/{validation-BxWAMMnJ.js → validation-B-xTvM9B.js} +417 -81
- package/dist/validation-B-xTvM9B.js.map +1 -0
- package/package.json +17 -2
- package/src/components/Accordion.svelte +3 -1
- package/src/components/AccordionItem.svelte +1 -5
- package/src/components/Audio.svelte +22 -5
- package/src/components/Callout.svelte +5 -1
- package/src/components/Carousel.svelte +24 -8
- package/src/components/DefaultLayout.svelte +41 -12
- package/src/components/FillInTheBlank.svelte +75 -103
- package/src/components/Image.svelte +14 -10
- package/src/components/LockedBanner.svelte +5 -5
- package/src/components/Matching.svelte +48 -19
- package/src/components/MediaTracks.svelte +21 -0
- package/src/components/MultipleChoice.svelte +81 -102
- package/src/components/Quiz.svelte +63 -21
- package/src/components/ResultIcon.svelte +20 -4
- package/src/components/RevealModal.svelte +25 -22
- package/src/components/Sorting.svelte +61 -26
- package/src/components/Transcript.svelte +37 -0
- package/src/components/Video.svelte +25 -20
- package/src/components/util.ts +4 -1
- package/src/components/video-embed.ts +25 -0
- package/src/index.ts +2 -7
- package/src/plugin/a11y/audit.ts +299 -0
- package/src/plugin/a11y/contrast.ts +67 -0
- package/src/plugin/a11y-cli.ts +35 -0
- package/src/plugin/cli.ts +6 -8
- package/src/plugin/export.ts +60 -50
- package/src/plugin/index.ts +244 -101
- package/src/plugin/layout.ts +6 -51
- package/src/plugin/manifest.ts +90 -24
- package/src/plugin/override-plugin.ts +68 -0
- package/src/plugin/quiz.ts +9 -54
- package/src/plugin/validation.ts +768 -183
- package/src/runtime/App.svelte +128 -64
- package/src/runtime/LoadingBar.svelte +12 -3
- package/src/runtime/Sidebar.svelte +24 -8
- package/src/runtime/access.ts +15 -3
- package/src/runtime/adapters/cmi5.ts +68 -116
- package/src/runtime/adapters/format.ts +67 -0
- package/src/runtime/adapters/index.ts +45 -34
- package/src/runtime/adapters/retry.ts +25 -84
- package/src/runtime/adapters/scorm-base.ts +19 -15
- package/src/runtime/adapters/scorm12.ts +8 -9
- package/src/runtime/adapters/scorm2004.ts +22 -30
- package/src/runtime/adapters/web.ts +1 -1
- package/src/runtime/hooks.svelte.ts +152 -328
- package/src/runtime/interaction-format.ts +30 -12
- package/src/runtime/interaction.ts +44 -11
- package/src/runtime/navigation.svelte.ts +29 -40
- package/src/runtime/persistence.ts +2 -2
- package/src/runtime/progress.svelte.ts +22 -9
- package/src/runtime/quiz-engine.svelte.ts +361 -0
- package/src/runtime/quiz-policy.ts +28 -179
- package/src/runtime/types.ts +24 -2
- package/src/runtime/xapi/agent-rules.ts +11 -3
- package/src/runtime/xapi/client.ts +5 -5
- package/src/runtime/xapi/derive-actor.ts +2 -2
- package/src/runtime/xapi/publisher.ts +33 -40
- package/src/runtime/xapi/setup.ts +18 -15
- package/src/runtime/xapi/validation.ts +15 -6
- package/src/virtual.d.ts +4 -1
- package/styles/base.css +32 -11
- package/styles/layout.css +39 -18
- package/styles/theme.css +15 -3
- package/dist/validation-BxWAMMnJ.js.map +0 -1
package/dist/plugin/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { n as
|
|
1
|
+
import { a as validateProject, i as reportValidationIssues, n as isPlausibleLanguageTag, o as generateManifest, r as normalizeA11y, s as readCourseConfig, t as isIgnored } from "../validation-B-xTvM9B.js";
|
|
2
|
+
import { n as runAudit, t as AUDIT_ENV_FLAG } from "../audit-BBJpQGqb.js";
|
|
2
3
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { dirname, resolve } from "node:path";
|
|
5
|
-
import { cpSync, createWriteStream, existsSync, mkdirSync,
|
|
6
|
-
import JSON5 from "json5";
|
|
5
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
6
|
+
import { cpSync, createWriteStream, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { ZipArchive } from "archiver";
|
|
9
|
+
import { normalizePath } from "vite";
|
|
9
10
|
//#region src/runtime/slugify.ts
|
|
10
11
|
/**
|
|
11
12
|
* Slugify a string for use as a URL-safe / filename-safe identifier.
|
|
@@ -151,77 +152,78 @@ function cleanOldZips(projectRoot, slug) {
|
|
|
151
152
|
} catch {}
|
|
152
153
|
} catch {}
|
|
153
154
|
}
|
|
155
|
+
/** Packaged (zipped) export targets: which manifest file to write and how. */
|
|
156
|
+
const PACKAGED_EXPORTS = {
|
|
157
|
+
scorm12: {
|
|
158
|
+
manifestFile: "imsmanifest.xml",
|
|
159
|
+
label: "SCORM 1.2",
|
|
160
|
+
generate: generateSCORM12Manifest
|
|
161
|
+
},
|
|
162
|
+
scorm2004: {
|
|
163
|
+
manifestFile: "imsmanifest.xml",
|
|
164
|
+
label: "SCORM 2004",
|
|
165
|
+
generate: generateSCORM2004Manifest
|
|
166
|
+
},
|
|
167
|
+
cmi5: {
|
|
168
|
+
manifestFile: "cmi5.xml",
|
|
169
|
+
label: "CMI5",
|
|
170
|
+
generate: (config) => generateCMI5Xml(config)
|
|
171
|
+
}
|
|
172
|
+
};
|
|
154
173
|
async function runExport(projectRoot, config) {
|
|
155
174
|
const distDir = resolve(projectRoot, "dist");
|
|
156
175
|
const standard = config.export?.standard || "web";
|
|
157
176
|
const slug = slugify(config.title || "tessera-course") || "tessera-course";
|
|
158
177
|
const zipName = `${slug}-${config.version || "1.0.0"}.zip`;
|
|
159
178
|
const zipPath = resolve(projectRoot, zipName);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
case "scorm12": {
|
|
169
|
-
const manifest = generateSCORM12Manifest(config, distDir);
|
|
170
|
-
writeFileSync(resolve(distDir, "imsmanifest.xml"), manifest, "utf-8");
|
|
171
|
-
cleanOldZips(projectRoot, slug);
|
|
172
|
-
const zipSize = await createZip(distDir, zipPath);
|
|
173
|
-
console.log(`✓ SCORM 1.2 export: ${zipName} (${formatSize(zipSize)})`);
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
case "scorm2004": {
|
|
177
|
-
const manifest = generateSCORM2004Manifest(config, distDir);
|
|
178
|
-
writeFileSync(resolve(distDir, "imsmanifest.xml"), manifest, "utf-8");
|
|
179
|
-
cleanOldZips(projectRoot, slug);
|
|
180
|
-
const zipSize = await createZip(distDir, zipPath);
|
|
181
|
-
console.log(`✓ SCORM 2004 export: ${zipName} (${formatSize(zipSize)})`);
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
case "cmi5": {
|
|
185
|
-
const xml = generateCMI5Xml(config);
|
|
186
|
-
writeFileSync(resolve(distDir, "cmi5.xml"), xml, "utf-8");
|
|
187
|
-
cleanOldZips(projectRoot, slug);
|
|
188
|
-
const zipSize = await createZip(distDir, zipPath);
|
|
189
|
-
console.log(`✓ CMI5 export: ${zipName} (${formatSize(zipSize)})`);
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
179
|
+
if (standard === "web") {
|
|
180
|
+
const files = collectFiles(distDir);
|
|
181
|
+
let totalSize = 0;
|
|
182
|
+
for (const f of files) totalSize += statSync(resolve(distDir, f)).size;
|
|
183
|
+
console.log(`✓ Web export: dist/ (${formatSize(totalSize)})`);
|
|
184
|
+
return;
|
|
192
185
|
}
|
|
186
|
+
const spec = PACKAGED_EXPORTS[standard];
|
|
187
|
+
if (!spec) return;
|
|
188
|
+
writeFileSync(resolve(distDir, spec.manifestFile), spec.generate(config, distDir), "utf-8");
|
|
189
|
+
cleanOldZips(projectRoot, slug);
|
|
190
|
+
const zipSize = await createZip(distDir, zipPath);
|
|
191
|
+
console.log(`✓ ${spec.label} export: ${zipName} (${formatSize(zipSize)})`);
|
|
193
192
|
}
|
|
194
193
|
//#endregion
|
|
195
|
-
//#region src/plugin/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
//#region src/plugin/override-plugin.ts
|
|
195
|
+
/**
|
|
196
|
+
* A virtual module that resolves to a project-root override file when present,
|
|
197
|
+
* and to the built-in (or a null export) otherwise. Shared by the layout and
|
|
198
|
+
* quiz plugins — they differ only in the virtual id, file name, and built-in.
|
|
199
|
+
*/
|
|
200
|
+
function createOverridePlugin({ name, virtualId, projectFile, builtinFile }) {
|
|
201
|
+
const resolvedId = "\0" + virtualId;
|
|
202
|
+
const fallback = builtinFile ? `export { default } from '${normalizePath(builtinFile)}';` : "export default null;";
|
|
203
|
+
let filePath;
|
|
200
204
|
return {
|
|
201
|
-
name
|
|
205
|
+
name,
|
|
202
206
|
enforce: "pre",
|
|
203
207
|
configResolved(config) {
|
|
204
|
-
|
|
208
|
+
filePath = resolve(config.root, projectFile);
|
|
205
209
|
},
|
|
206
210
|
resolveId(id) {
|
|
207
|
-
if (id ===
|
|
211
|
+
if (id === virtualId) return resolvedId;
|
|
208
212
|
return null;
|
|
209
213
|
},
|
|
210
214
|
load(id) {
|
|
211
|
-
if (id !==
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return `export { default } from '${layoutPath.replace(/\\/g, "/")}';`;
|
|
215
|
+
if (id !== resolvedId) return null;
|
|
216
|
+
if (existsSync(filePath)) {
|
|
217
|
+
this.addWatchFile(filePath);
|
|
218
|
+
return `export { default } from '${normalizePath(filePath)}';`;
|
|
216
219
|
}
|
|
217
|
-
return
|
|
220
|
+
return fallback;
|
|
218
221
|
},
|
|
219
222
|
configureServer(server) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (filePath !== layoutPath) return;
|
|
223
|
+
server.watcher.on("all", (event, changed) => {
|
|
224
|
+
if (changed !== filePath) return;
|
|
223
225
|
if (event !== "add" && event !== "unlink") return;
|
|
224
|
-
const mod = server.moduleGraph.getModuleById(
|
|
226
|
+
const mod = server.moduleGraph.getModuleById(resolvedId);
|
|
225
227
|
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
226
228
|
server.ws.send({ type: "full-reload" });
|
|
227
229
|
});
|
|
@@ -229,51 +231,30 @@ function tesseraLayoutPlugin() {
|
|
|
229
231
|
};
|
|
230
232
|
}
|
|
231
233
|
//#endregion
|
|
234
|
+
//#region src/plugin/layout.ts
|
|
235
|
+
function tesseraLayoutPlugin() {
|
|
236
|
+
return createOverridePlugin({
|
|
237
|
+
name: "tessera:layout",
|
|
238
|
+
virtualId: "virtual:tessera-layout",
|
|
239
|
+
projectFile: "layout.svelte"
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
232
243
|
//#region src/plugin/quiz.ts
|
|
233
|
-
const VIRTUAL_QUIZ_ID = "virtual:tessera-quiz";
|
|
234
|
-
const RESOLVED_QUIZ_ID = "\0" + VIRTUAL_QUIZ_ID;
|
|
235
244
|
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
236
|
-
/**
|
|
237
|
-
* Resolve the project's quiz shell.
|
|
238
|
-
* `projectRoot/quiz.svelte` overrides the built-in `<Quiz>` if it exists,
|
|
239
|
-
* otherwise the built-in is used. Mirrors `tesseraLayoutPlugin` (Phase 3A).
|
|
240
|
-
*/
|
|
241
245
|
function tesseraQuizPlugin() {
|
|
242
|
-
|
|
243
|
-
const builtinQuiz = resolve(resolve(__dirname$1, "..", ".."), "src", "components", "Quiz.svelte");
|
|
244
|
-
return {
|
|
246
|
+
return createOverridePlugin({
|
|
245
247
|
name: "tessera:quiz",
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
resolveId(id) {
|
|
251
|
-
if (id === VIRTUAL_QUIZ_ID) return RESOLVED_QUIZ_ID;
|
|
252
|
-
return null;
|
|
253
|
-
},
|
|
254
|
-
load(id) {
|
|
255
|
-
if (id !== RESOLVED_QUIZ_ID) return null;
|
|
256
|
-
const userQuizPath = resolve(projectRoot, "quiz.svelte");
|
|
257
|
-
if (existsSync(userQuizPath)) {
|
|
258
|
-
this.addWatchFile(userQuizPath);
|
|
259
|
-
return `export { default } from '${userQuizPath.replace(/\\/g, "/")}';`;
|
|
260
|
-
}
|
|
261
|
-
return `export { default } from '${builtinQuiz.replace(/\\/g, "/")}';`;
|
|
262
|
-
},
|
|
263
|
-
configureServer(server) {
|
|
264
|
-
const userQuizPath = resolve(projectRoot, "quiz.svelte");
|
|
265
|
-
server.watcher.on("all", (event, filePath) => {
|
|
266
|
-
if (filePath !== userQuizPath) return;
|
|
267
|
-
if (event !== "add" && event !== "unlink") return;
|
|
268
|
-
const mod = server.moduleGraph.getModuleById(RESOLVED_QUIZ_ID);
|
|
269
|
-
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
270
|
-
server.ws.send({ type: "full-reload" });
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
};
|
|
248
|
+
virtualId: "virtual:tessera-quiz",
|
|
249
|
+
projectFile: "quiz.svelte",
|
|
250
|
+
builtinFile: resolve(resolve(__dirname$1, "..", ".."), "src", "components", "Quiz.svelte")
|
|
251
|
+
});
|
|
274
252
|
}
|
|
275
253
|
//#endregion
|
|
276
254
|
//#region src/plugin/index.ts
|
|
255
|
+
function isAuditBuild() {
|
|
256
|
+
return process.env[AUDIT_ENV_FLAG] === "1";
|
|
257
|
+
}
|
|
277
258
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
278
259
|
function resolveRuntimeDir() {
|
|
279
260
|
return resolve(resolve(__dirname, "..", ".."), "src", "runtime");
|
|
@@ -281,13 +262,44 @@ function resolveRuntimeDir() {
|
|
|
281
262
|
function resolveStylesDir() {
|
|
282
263
|
return resolve(resolve(__dirname, "..", ".."), "styles");
|
|
283
264
|
}
|
|
265
|
+
function projectFileRel(filename, projectRoot) {
|
|
266
|
+
if (!filename || !projectRoot) return null;
|
|
267
|
+
if (filename.startsWith("\0") || filename.includes("virtual:") || filename.includes("node_modules")) return null;
|
|
268
|
+
const rel = relative(projectRoot, isAbsolute(filename) ? filename : resolve(projectRoot, filename));
|
|
269
|
+
if (rel.startsWith("..") || isAbsolute(rel) || rel.includes("node_modules")) return null;
|
|
270
|
+
return rel;
|
|
271
|
+
}
|
|
284
272
|
function tesseraPlugin() {
|
|
285
273
|
const manifestRef = {
|
|
286
274
|
current: null,
|
|
287
275
|
root: ""
|
|
288
276
|
};
|
|
277
|
+
const a11y = {
|
|
278
|
+
warnings: [],
|
|
279
|
+
projectRoot: "",
|
|
280
|
+
isBuild: false,
|
|
281
|
+
settings: normalizeA11y(void 0)
|
|
282
|
+
};
|
|
289
283
|
return [
|
|
290
|
-
svelte({
|
|
284
|
+
svelte({
|
|
285
|
+
compilerOptions: { css: "external" },
|
|
286
|
+
onwarn(warning, defaultHandler) {
|
|
287
|
+
if (warning.code?.startsWith("a11y")) {
|
|
288
|
+
const rel = projectFileRel(warning.filename, a11y.projectRoot);
|
|
289
|
+
if (rel !== null) {
|
|
290
|
+
const msg = `[${warning.code}] ${rel}: ${warning.message}`;
|
|
291
|
+
if (a11y.isBuild) a11y.warnings.push(msg);
|
|
292
|
+
else if (!a11y.settings.ignore.includes(warning.code)) reportValidationIssues({
|
|
293
|
+
errors: [],
|
|
294
|
+
warnings: [msg]
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
defaultHandler?.(warning);
|
|
300
|
+
}
|
|
301
|
+
}),
|
|
302
|
+
tesseraA11yCompilerPlugin(a11y),
|
|
291
303
|
tesseraValidationPlugin(),
|
|
292
304
|
tesseraEntryPlugin(),
|
|
293
305
|
tesseraConfigPlugin(),
|
|
@@ -310,16 +322,18 @@ function tesseraEntryPlugin() {
|
|
|
310
322
|
const stylesDir = resolveStylesDir();
|
|
311
323
|
const appSveltePath = resolve(runtimeDir, "App.svelte");
|
|
312
324
|
let projectRoot;
|
|
325
|
+
let outDir;
|
|
313
326
|
let isBuild = false;
|
|
314
327
|
return {
|
|
315
328
|
name: "tessera:entry",
|
|
316
329
|
enforce: "pre",
|
|
317
330
|
configResolved(config) {
|
|
318
331
|
projectRoot = config.root;
|
|
332
|
+
outDir = resolve(config.root, config.build.outDir);
|
|
319
333
|
isBuild = config.command === "build";
|
|
320
334
|
},
|
|
321
335
|
buildStart() {
|
|
322
|
-
if (isBuild) writeFileSync(resolve(projectRoot, "index.html"), generateIndexHtml(), "utf-8");
|
|
336
|
+
if (isBuild) writeFileSync(resolve(projectRoot, "index.html"), generateIndexHtml(readLanguage(projectRoot)), "utf-8");
|
|
323
337
|
},
|
|
324
338
|
closeBundle() {
|
|
325
339
|
if (isBuild) {
|
|
@@ -328,7 +342,7 @@ function tesseraEntryPlugin() {
|
|
|
328
342
|
unlinkSync(htmlPath);
|
|
329
343
|
} catch {}
|
|
330
344
|
const assetsDir = resolve(projectRoot, "assets");
|
|
331
|
-
const distAssetsDir = resolve(
|
|
345
|
+
const distAssetsDir = resolve(outDir, "assets");
|
|
332
346
|
if (existsSync(assetsDir)) {
|
|
333
347
|
mkdirSync(distAssetsDir, { recursive: true });
|
|
334
348
|
cpSync(assetsDir, distAssetsDir, { recursive: true });
|
|
@@ -339,7 +353,7 @@ function tesseraEntryPlugin() {
|
|
|
339
353
|
return () => {
|
|
340
354
|
server.middlewares.use(async (req, res, next) => {
|
|
341
355
|
if (req.url === "/" || req.url === "/index.html") {
|
|
342
|
-
const html = generateIndexHtml();
|
|
356
|
+
const html = generateIndexHtml(readLanguage(projectRoot));
|
|
343
357
|
const transformed = await server.transformIndexHtml(req.url, html);
|
|
344
358
|
res.setHeader("Content-Type", "text/html");
|
|
345
359
|
res.statusCode = 200;
|
|
@@ -361,9 +375,14 @@ function tesseraEntryPlugin() {
|
|
|
361
375
|
}
|
|
362
376
|
};
|
|
363
377
|
}
|
|
364
|
-
function
|
|
378
|
+
function readLanguage(projectRoot) {
|
|
379
|
+
const read = readCourseConfig(projectRoot);
|
|
380
|
+
const lang = read.ok ? read.config.language : void 0;
|
|
381
|
+
return isPlausibleLanguageTag(lang) ? lang : "en";
|
|
382
|
+
}
|
|
383
|
+
function generateIndexHtml(lang) {
|
|
365
384
|
return `<!DOCTYPE html>
|
|
366
|
-
<html lang="
|
|
385
|
+
<html lang="${lang}">
|
|
367
386
|
<head>
|
|
368
387
|
<meta charset="UTF-8" />
|
|
369
388
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
@@ -421,7 +440,8 @@ function tesseraConfigPlugin() {
|
|
|
421
440
|
config(config) {
|
|
422
441
|
return {
|
|
423
442
|
base: "./",
|
|
424
|
-
|
|
443
|
+
build: { assetsDir: "tessera" },
|
|
444
|
+
resolve: { alias: { $assets: resolve(config.root || process.cwd(), "assets") } },
|
|
425
445
|
optimizeDeps: { exclude: ["tessera-learn"] }
|
|
426
446
|
};
|
|
427
447
|
},
|
|
@@ -435,14 +455,9 @@ function tesseraConfigPlugin() {
|
|
|
435
455
|
load(id) {
|
|
436
456
|
if (id === RESOLVED_CONFIG_ID) {
|
|
437
457
|
const configPath = resolve(projectRoot, "course.config.js");
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const objectStr = extractDefaultExportObjectLiteral(readFileSync(configPath, "utf-8"));
|
|
442
|
-
if (objectStr) try {
|
|
443
|
-
userConfig = JSON5.parse(objectStr);
|
|
444
|
-
} catch {}
|
|
445
|
-
}
|
|
458
|
+
if (existsSync(configPath)) this.addWatchFile(configPath);
|
|
459
|
+
const read = readCourseConfig(projectRoot);
|
|
460
|
+
const userConfig = read.ok ? read.config : {};
|
|
446
461
|
const { completion, passingScore } = completionDefaults(userConfig.completion?.mode);
|
|
447
462
|
const merged = {
|
|
448
463
|
title: userConfig.title || "Untitled Course",
|
|
@@ -516,13 +531,40 @@ function tesseraValidationPlugin() {
|
|
|
516
531
|
}
|
|
517
532
|
};
|
|
518
533
|
}
|
|
534
|
+
function tesseraA11yCompilerPlugin(a11y) {
|
|
535
|
+
return {
|
|
536
|
+
name: "tessera:a11y-compiler",
|
|
537
|
+
enforce: "pre",
|
|
538
|
+
configResolved(config) {
|
|
539
|
+
a11y.projectRoot = config.root;
|
|
540
|
+
a11y.isBuild = config.command === "build";
|
|
541
|
+
const read = readCourseConfig(config.root);
|
|
542
|
+
a11y.settings = normalizeA11y(read.ok ? read.config.a11y : void 0);
|
|
543
|
+
},
|
|
544
|
+
buildEnd() {
|
|
545
|
+
if (!a11y.isBuild || a11y.warnings.length === 0) return;
|
|
546
|
+
const ignored = new Set(a11y.settings.ignore);
|
|
547
|
+
const warnings = a11y.warnings.filter((msg) => !isIgnored(msg, ignored));
|
|
548
|
+
a11y.warnings = [];
|
|
549
|
+
if (warnings.length === 0) return;
|
|
550
|
+
if (a11y.settings.level === "error") {
|
|
551
|
+
reportValidationIssues({
|
|
552
|
+
errors: warnings,
|
|
553
|
+
warnings: []
|
|
554
|
+
});
|
|
555
|
+
throw new Error(`Tessera: ${warnings.length} a11y issue(s) with a11y.level: 'error'. Fix the errors above to continue.`);
|
|
556
|
+
}
|
|
557
|
+
reportValidationIssues({
|
|
558
|
+
errors: [],
|
|
559
|
+
warnings
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
519
564
|
function runValidation(projectRoot) {
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
if (errors.length > 0) {
|
|
523
|
-
for (const error of errors) console.error(`\x1b[31m[tessera error]\x1b[0m ${error}`);
|
|
524
|
-
throw new Error(`Tessera validation failed with ${errors.length} error(s). Fix the errors above to continue.`);
|
|
525
|
-
}
|
|
565
|
+
const result = validateProject(projectRoot);
|
|
566
|
+
reportValidationIssues(result);
|
|
567
|
+
if (result.errors.length > 0) throw new Error(`Tessera validation failed with ${result.errors.length} error(s). Fix the errors above to continue.`);
|
|
526
568
|
}
|
|
527
569
|
function tesseraExportPlugin() {
|
|
528
570
|
let projectRoot;
|
|
@@ -536,17 +578,14 @@ function tesseraExportPlugin() {
|
|
|
536
578
|
},
|
|
537
579
|
async closeBundle() {
|
|
538
580
|
if (!isBuild) return;
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
config = JSON5.parse(objectStr);
|
|
546
|
-
} catch (err) {
|
|
547
|
-
throw new Error(`[tessera:export] course.config.js: failed to parse export-default object literal — ${err.message}`);
|
|
581
|
+
if (isAuditBuild()) return;
|
|
582
|
+
const read = readCourseConfig(projectRoot);
|
|
583
|
+
if (!read.ok) {
|
|
584
|
+
if (read.reason === "missing") throw new Error("[tessera:export] course.config.js not found at closeBundle. The file must exist for the export step to run.");
|
|
585
|
+
if (read.reason === "no-export") throw new Error("[tessera:export] course.config.js: could not locate `export default { ... }`. Cannot determine export.standard.");
|
|
586
|
+
throw new Error(`[tessera:export] course.config.js: failed to parse export-default object literal — ${read.error.message}`);
|
|
548
587
|
}
|
|
549
|
-
await runExport(projectRoot, config);
|
|
588
|
+
await runExport(projectRoot, read.config);
|
|
550
589
|
}
|
|
551
590
|
};
|
|
552
591
|
}
|
|
@@ -620,14 +659,9 @@ function tesseraAdapterPlugin() {
|
|
|
620
659
|
if (id !== RESOLVED_ADAPTER_ID) return null;
|
|
621
660
|
if (!isBuild) return `export { createAdapter } from 'tessera-learn/runtime/adapters/index.js';`;
|
|
622
661
|
let standard = "web";
|
|
623
|
-
const
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
if (objectStr) try {
|
|
627
|
-
const parsed = JSON5.parse(objectStr);
|
|
628
|
-
if (typeof parsed?.export?.standard === "string") standard = parsed.export.standard;
|
|
629
|
-
} catch {}
|
|
630
|
-
}
|
|
662
|
+
const read = readCourseConfig(projectRoot);
|
|
663
|
+
if (read.ok && typeof read.config.export?.standard === "string") standard = read.config.export.standard;
|
|
664
|
+
if (isAuditBuild()) standard = "web";
|
|
631
665
|
switch (standard) {
|
|
632
666
|
case "scorm12": return `
|
|
633
667
|
import { SCORM12Adapter } from 'tessera-learn/runtime/adapters/scorm12.js';
|
|
@@ -687,16 +721,13 @@ function tesseraXAPISetupPlugin() {
|
|
|
687
721
|
load(id) {
|
|
688
722
|
if (id !== RESOLVED_XAPI_SETUP_ID) return null;
|
|
689
723
|
if (!isBuild) return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;
|
|
724
|
+
if (isAuditBuild()) return `export async function buildXAPIClient() { return null; }`;
|
|
690
725
|
let standard = "web";
|
|
691
726
|
let hasXapi = false;
|
|
692
|
-
const
|
|
693
|
-
if (
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
const parsed = JSON5.parse(objectStr);
|
|
697
|
-
if (typeof parsed?.export?.standard === "string") standard = parsed.export.standard;
|
|
698
|
-
hasXapi = parsed?.xapi != null;
|
|
699
|
-
} catch {}
|
|
727
|
+
const read = readCourseConfig(projectRoot);
|
|
728
|
+
if (read.ok) {
|
|
729
|
+
if (typeof read.config.export?.standard === "string") standard = read.config.export.standard;
|
|
730
|
+
hasXapi = read.config.xapi != null;
|
|
700
731
|
}
|
|
701
732
|
if (hasXapi || standard === "cmi5") return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;
|
|
702
733
|
return `export async function buildXAPIClient() { return null; }`;
|
|
@@ -728,6 +759,6 @@ function tesseraFirstPagePreloadPlugin(manifestRef) {
|
|
|
728
759
|
};
|
|
729
760
|
}
|
|
730
761
|
//#endregion
|
|
731
|
-
export { tesseraPlugin };
|
|
762
|
+
export { runAudit, tesseraPlugin };
|
|
732
763
|
|
|
733
764
|
//# sourceMappingURL=index.js.map
|