tessera-learn 0.3.0 → 0.4.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.
Files changed (40) hide show
  1. package/AGENTS.md +17 -12
  2. package/README.md +1 -1
  3. package/dist/{audit-DkXqQTqn.js → audit-DsYqXbqm.js} +211 -183
  4. package/dist/audit-DsYqXbqm.js.map +1 -0
  5. package/dist/{build-commands-CyzuCDXg.js → build-commands-BFuiAxaR.js} +4 -4
  6. package/dist/build-commands-BFuiAxaR.js.map +1 -0
  7. package/dist/{inline-config-BEXyRqsJ.js → inline-config-DVvOCKht.js} +6 -6
  8. package/dist/inline-config-DVvOCKht.js.map +1 -0
  9. package/dist/plugin/cli.d.ts +5 -1
  10. package/dist/plugin/cli.d.ts.map +1 -1
  11. package/dist/plugin/cli.js +38 -7
  12. package/dist/plugin/cli.js.map +1 -1
  13. package/dist/plugin/index.d.ts +7 -1
  14. package/dist/plugin/index.d.ts.map +1 -1
  15. package/dist/plugin/index.js +2 -2
  16. package/dist/{plugin-CFUFgwHB.js → plugin-BuMiDTmU.js} +29 -38
  17. package/dist/plugin-BuMiDTmU.js.map +1 -0
  18. package/package.json +1 -1
  19. package/src/components/MultipleChoice.svelte +1 -2
  20. package/src/plugin/build-commands.ts +7 -4
  21. package/src/plugin/cli.ts +54 -3
  22. package/src/plugin/index.ts +31 -42
  23. package/src/plugin/inline-config.ts +4 -2
  24. package/src/plugin/manifest.ts +21 -0
  25. package/src/plugin/validate-cli.ts +5 -2
  26. package/src/plugin/validation.ts +214 -233
  27. package/src/runtime/App.svelte +4 -1
  28. package/src/runtime/adapters/scorm-base.ts +15 -14
  29. package/src/runtime/adapters/scorm12.ts +6 -25
  30. package/src/runtime/adapters/scorm2004.ts +12 -55
  31. package/src/runtime/adapters/web.ts +5 -13
  32. package/src/runtime/fingerprint.ts +28 -0
  33. package/src/runtime/interaction-format.ts +0 -1
  34. package/src/runtime/persistence.ts +4 -0
  35. package/src/runtime/types.ts +3 -0
  36. package/src/runtime/xapi/publisher.ts +11 -14
  37. package/dist/audit-DkXqQTqn.js.map +0 -1
  38. package/dist/build-commands-CyzuCDXg.js.map +0 -1
  39. package/dist/inline-config-BEXyRqsJ.js.map +0 -1
  40. package/dist/plugin-CFUFgwHB.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../../src/plugin/validate-cli.ts","../../src/plugin/a11y-cli.ts","../../src/plugin/project-name.ts","../../src/plugin/course-root.ts","../../src/plugin/template-copy.ts","../../src/plugin/new-cli.ts","../../src/plugin/duplicate-cli.ts","../../src/plugin/cli.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { validateProject, reportValidationIssues } from './validation.js';\n\nexport function runValidate(\n projectRoot: string,\n { showA11yTip = true }: { showA11yTip?: boolean } = {},\n): number {\n const { errors, warnings } = validateProject(projectRoot);\n\n reportValidationIssues({ errors, warnings });\n\n if (errors.length > 0) {\n const summary =\n `Validation failed with ${errors.length} error(s)` +\n (warnings.length > 0 ? ` and ${warnings.length} warning(s)` : '') +\n '.';\n console.error(`\\n\\x1b[31m${summary}\\x1b[0m`);\n return 1;\n }\n\n if (warnings.length > 0) {\n console.log(\n `\\n\\x1b[33mValidation passed with ${warnings.length} warning(s).\\x1b[0m`,\n );\n } else {\n console.log(\n '\\x1b[32m[tessera]\\x1b[0m Validation passed — no issues found.',\n );\n }\n if (showA11yTip) {\n console.log(\n `\\x1b[2m[tessera] Static checks only. For a full runtime accessibility audit, run: pnpm a11y ${basename(projectRoot)}\\x1b[0m`,\n );\n }\n return 0;\n}\n","import { runAudit, type AuditOptions, type ImpactLevel } from './a11y/audit.js';\n\nconst VALID_THRESHOLDS: ImpactLevel[] = [\n 'minor',\n 'moderate',\n 'serious',\n 'critical',\n];\n\nexport type ParsedA11yArgs =\n | { ok: true; args: AuditOptions }\n | { ok: false; error: string };\n\nexport function parseA11yArgs(argv: string[]): ParsedA11yArgs {\n let threshold: ImpactLevel | undefined;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === '--threshold') {\n const value = argv[++i] as ImpactLevel;\n if (!VALID_THRESHOLDS.includes(value)) {\n return {\n ok: false,\n error: `--threshold must be one of: ${VALID_THRESHOLDS.join(', ')}`,\n };\n }\n threshold = value;\n } else {\n return { ok: false, error: `Unknown argument: ${arg}` };\n }\n }\n\n const args: AuditOptions = {};\n if (threshold !== undefined) args.threshold = threshold;\n return { ok: true, args };\n}\n\nexport async function runA11y(\n projectRoot: string,\n workspaceRoot: string,\n argv: string[],\n): Promise<number> {\n const parsed = parseA11yArgs(argv);\n if (!parsed.ok) {\n console.error(`[tessera a11y] ${parsed.error}`);\n return 1;\n }\n return runAudit(projectRoot, workspaceRoot, parsed.args);\n}\n","// Pure, dependency-free helpers shared by the `tessera new` subcommand and the\n// `create-tessera` scaffolder. Kept import-free so create-tessera can bundle it\n// at build time without dragging in the rest of the plugin (vite, svelte, …).\n\n// npm package name rules: 1-214 chars, lowercase, must start with [a-z0-9],\n// allowed chars [a-z0-9._-], no leading dot or underscore.\nexport function validateProjectName(\n name: string,\n label = 'Project name',\n): string | null {\n if (!name) return `${label} is required`;\n if (name.length > 214) return `${label} must be 214 characters or fewer`;\n if (name !== name.toLowerCase()) return `${label} must be lowercase`;\n if (!/^[a-z0-9]/.test(name)) {\n return `${label} must start with a letter or digit`;\n }\n if (!/^[a-z0-9._-]+$/.test(name)) {\n return `${label} may only contain lowercase letters, digits, \"-\", \"_\", and \".\"`;\n }\n return null;\n}\n\nexport function toTitleCase(slug: string): string {\n return slug\n .split(/[-_.\\s]+/)\n .filter(Boolean)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { validateProjectName } from './project-name.js';\n\nexport interface ResolvedCourse {\n ok: true;\n courseRoot: string;\n workspaceRoot: string;\n}\n\nexport type ResolveResult = ResolvedCourse | { ok: false; error: string };\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction isCourse(dir: string): boolean {\n return existsSync(join(dir, 'course.config.js'));\n}\n\n// A workspace is the nearest ancestor holding a courses/ directory. The walk\n// includes the starting dir, so the workspace root resolves to itself.\nexport function findWorkspaceRoot(cwd: string): string | null {\n for (let dir = resolve(cwd); ; dir = dirname(dir)) {\n if (isDir(join(dir, 'courses'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n }\n}\n\nfunction scanCourses(workspaceRoot: string): {\n courses: string[];\n malformed: string[];\n} {\n const coursesDir = join(workspaceRoot, 'courses');\n const courses: string[] = [];\n const malformed: string[] = [];\n try {\n for (const e of readdirSync(coursesDir, { withFileTypes: true })) {\n if (!e.isDirectory()) continue;\n const dir = join(coursesDir, e.name);\n if (isCourse(dir)) courses.push(e.name);\n else if (isDir(join(dir, 'pages'))) malformed.push(e.name);\n }\n } catch {\n return { courses, malformed };\n }\n return { courses: courses.sort(), malformed: malformed.sort() };\n}\n\nexport function listCourses(workspaceRoot: string): string[] {\n return scanCourses(workspaceRoot).courses;\n}\n\nexport function listMalformedCourses(workspaceRoot: string): string[] {\n return scanCourses(workspaceRoot).malformed;\n}\n\nconst NOT_A_WORKSPACE =\n 'Not inside a Tessera workspace — no `courses/` directory was found at or above the current directory.';\n\nfunction malformedHint(malformed: string[]): string {\n if (malformed.length === 0) return '';\n return (\n `\\nSkipped (missing course.config.js):\\n` +\n malformed.map((c) => ` courses/${c}`).join('\\n')\n );\n}\n\nfunction listHint(workspaceRoot: string): string {\n const { courses, malformed } = scanCourses(workspaceRoot);\n if (courses.length === 0) {\n return (\n '\\nNo courses found. Create one with `tessera new <name>`.' +\n malformedHint(malformed)\n );\n }\n return (\n `\\nAvailable courses:\\n${courses.map((c) => ` ${c}`).join('\\n')}` +\n '\\nName one (`tessera <command> <course>`) or cd into its folder.' +\n malformedHint(malformed)\n );\n}\n\n// A name argument always wins; otherwise the cwd must itself be a course. There\n// is deliberately no \"single course → use it implicitly\" rule, so a bare command\n// never changes meaning when a second course is added.\nexport function resolveCourse(cwd: string, name?: string): ResolveResult {\n const here = resolve(cwd);\n\n if (name) {\n const nameError = validateProjectName(name, 'the name');\n if (nameError) {\n return {\n ok: false,\n error: `Invalid course name \"${name}\" — ${nameError}.`,\n };\n }\n const workspaceRoot = findWorkspaceRoot(here);\n if (!workspaceRoot) return { ok: false, error: NOT_A_WORKSPACE };\n const courseRoot = join(workspaceRoot, 'courses', name);\n if (!isCourse(courseRoot)) {\n return {\n ok: false,\n error: `Course \"${name}\" not found in courses/.${listHint(workspaceRoot)}`,\n };\n }\n return { ok: true, courseRoot, workspaceRoot };\n }\n\n if (isCourse(here)) {\n const workspaceRoot = findWorkspaceRoot(here) ?? dirname(dirname(here));\n return { ok: true, courseRoot: here, workspaceRoot };\n }\n\n const workspaceRoot = findWorkspaceRoot(here);\n if (!workspaceRoot) return { ok: false, error: NOT_A_WORKSPACE };\n return {\n ok: false,\n error: `No course specified.${listHint(workspaceRoot)}`,\n };\n}\n","import {\n copyFileSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\n\n// npm's tarball packing strips/renames leading-dot files, so templates store\n// them prefixed and we restore the dot on copy. (create-vite convention.)\nconst RENAME: Record<string, string> = {\n _gitignore: '.gitignore',\n _gitkeep: '.gitkeep',\n};\n\n// Text files get token substitution; everything else is copied byte-for-byte.\nconst TEXT = /\\.(svelte|js|ts|json|css|md|html)$/;\n\nfunction applyTokens(s: string, tokens: Record<string, string>): string {\n return s.replace(/__([A-Z_]+)__/g, (m, key) =>\n key in tokens ? tokens[key] : m,\n );\n}\n\nexport function copyTemplate(\n srcDir: string,\n destDir: string,\n tokens: Record<string, string>,\n): void {\n mkdirSync(destDir, { recursive: true });\n for (const entry of readdirSync(srcDir, { withFileTypes: true })) {\n const src = join(srcDir, entry.name);\n const dest = join(destDir, RENAME[entry.name] ?? entry.name);\n if (entry.isDirectory()) {\n copyTemplate(src, dest, tokens);\n } else if (TEXT.test(entry.name)) {\n writeFileSync(dest, applyTokens(readFileSync(src, 'utf-8'), tokens));\n } else {\n copyFileSync(src, dest);\n }\n }\n}\n","import { existsSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\nimport { join, relative, resolve } from 'node:path';\nimport { findWorkspaceRoot } from './course-root.js';\nimport { validateProjectName, toTitleCase } from './project-name.js';\nimport { copyTemplate } from './template-copy.js';\nimport { resolvePackageRoot } from './package-root.js';\n\n// `tessera new <name>` — stamp a new course into courses/<name> inside the\n// current workspace. No install step: the workspace already owns the deps.\nexport function runNew(name: string | undefined, cwd: string): number {\n if (name === '--help' || name === '-h') {\n console.log(\n 'Usage: tessera new <name>\\n\\n' +\n 'Scaffold a new course into courses/<name> inside the current workspace.',\n );\n return 0;\n }\n if (!name) {\n console.error('Usage: tessera new <name>');\n return 1;\n }\n\n const nameError = validateProjectName(name, 'Course name');\n if (nameError) {\n console.error(`[tessera new] ${nameError}`);\n return 1;\n }\n\n const workspaceRoot = findWorkspaceRoot(resolve(cwd));\n if (!workspaceRoot) {\n console.error(\n '[tessera new] Not inside a Tessera workspace — run this from a workspace (a directory with a `courses/` folder).',\n );\n return 1;\n }\n\n const courseDir = join(workspaceRoot, 'courses', name);\n if (existsSync(courseDir)) {\n console.error(`[tessera new] Course \"${name}\" already exists.`);\n return 1;\n }\n\n const templateDir = join(resolvePackageRoot(), 'templates', 'course');\n copyTemplate(templateDir, courseDir, {\n PROJECT_TITLE: toTitleCase(name),\n COURSE_ID: `urn:uuid:${randomUUID()}`,\n });\n\n const rel = relative(workspaceRoot, courseDir);\n console.log(`\\nCreated ${rel}.\\n\\nNext steps:\\n pnpm tessera dev ${name}\\n`);\n return 0;\n}\n","import { cpSync, existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\nimport { basename, join, relative } from 'node:path';\nimport { resolveCourse } from './course-root.js';\nimport { validateProjectName } from './project-name.js';\n\n// The top-level `id` property as scaffolders write it: the key (bare or quoted)\n// first on its own line, then its value. Anchoring to line start keeps `id:`\n// inside a comment or a string value from matching, and CourseConfig has no\n// nested `id`, so the first match is always the course identity.\nconst ID_LINE =\n /^([ \\t]*'?id'?[ \\t]*:[ \\t]*)('(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*`|[^\\n,}]*)/m;\nconst DEFAULT_OBJECT = /export\\s+default\\s*\\{/;\n\n// A verbatim copy inherits the source's `id`; mint a fresh one so the duplicate\n// is a distinct course (own storage key + LRS activity id).\nfunction reidentifyCourse(courseRoot: string): void {\n const configPath = join(courseRoot, 'course.config.js');\n if (!existsSync(configPath)) return;\n const text = readFileSync(configPath, 'utf-8');\n const newId = `'urn:uuid:${randomUUID()}'`;\n\n if (ID_LINE.test(text)) {\n writeFileSync(configPath, text.replace(ID_LINE, `$1${newId}`));\n return;\n }\n const obj = DEFAULT_OBJECT.exec(text);\n if (obj) {\n const at = obj.index + obj[0].length;\n writeFileSync(\n configPath,\n `${text.slice(0, at)}\\n id: ${newId},${text.slice(at)}`,\n );\n return;\n }\n console.warn(\n `[tessera duplicate] Could not set a unique id in ${configPath} — the copy shares the source's identity. Add a unique \"id\" manually.`,\n );\n}\n\nconst HELP =\n 'Usage: tessera duplicate <source> <new>\\n\\n' +\n 'Copy courses/<source>/ to courses/<new>/ within the current workspace.';\n\n// Generated/build artifacts that should never travel with a verbatim copy. The\n// a11y throwaway build and Vite's cache live under node_modules, so they're\n// already pruned by the node_modules skip; the rest are belt-and-suspenders.\nconst SKIP = new Set(['node_modules', 'dist', 'a11y-report.json', '.vite']);\n\nfunction skip(srcPath: string): boolean {\n const name = basename(srcPath);\n return SKIP.has(name) || name.startsWith('.tessera');\n}\n\n// `tessera duplicate <source> <new>` — copy an existing course verbatim within\n// the current workspace. Unlike `new`, there is no template stamping: the JS\n// config (including its title) is copied untouched.\nexport function runDuplicate(\n source: string | undefined,\n target: string | undefined,\n cwd: string,\n): number {\n if (\n source === '--help' ||\n source === '-h' ||\n target === '--help' ||\n target === '-h'\n ) {\n console.log(HELP);\n return 0;\n }\n if (!source || !target) {\n console.error('Usage: tessera duplicate <source> <new>');\n return 1;\n }\n\n const nameError = validateProjectName(target, 'Course name');\n if (nameError) {\n console.error(`[tessera duplicate] ${nameError}`);\n return 1;\n }\n\n const resolved = resolveCourse(cwd, source);\n if (!resolved.ok) {\n console.error(`[tessera duplicate] ${resolved.error}`);\n return 1;\n }\n const { courseRoot: srcDir, workspaceRoot } = resolved;\n\n const destDir = join(workspaceRoot, 'courses', target);\n if (existsSync(destDir)) {\n console.error(`[tessera duplicate] Course \"${target}\" already exists.`);\n return 1;\n }\n\n cpSync(srcDir, destDir, {\n recursive: true,\n filter: (src) => src === srcDir || !skip(src),\n });\n reidentifyCourse(destDir);\n\n const rel = relative(workspaceRoot, destDir);\n const srcRel = relative(workspaceRoot, srcDir);\n console.log(\n `\\nCreated ${rel} (duplicated from ${srcRel}).\\n\\n` +\n `Remember to update the title in ${rel}/course.config.js.\\n\\n` +\n `Next steps:\\n pnpm tessera dev ${target}\\n`,\n );\n return 0;\n}\n","#!/usr/bin/env node\nimport { runValidate } from './validate-cli.js';\nimport { runA11y } from './a11y-cli.js';\nimport { runNew } from './new-cli.js';\nimport { runDuplicate } from './duplicate-cli.js';\nimport { resolveCourse } from './course-root.js';\n\nconst USAGE = `Usage: tessera <command> [course] [options]\n\nCommands:\n new <name> Scaffold a new course into courses/<name>\n duplicate <source> <new> Copy courses/<source> to courses/<new>\n dev [course] Start the Vite dev server\n export [course] Build and package the course for its LMS standard\n validate [course] Fast static structure checks\n a11y [course] Runtime accessibility audit (builds + drives Playwright)\n check [course] Run validate, then a11y\n\nRun a command from inside a course folder, or name the course explicitly.\n\na11y/check options:\n --threshold <minor|moderate|serious|critical> Failing impact (default: serious)`;\n\n// The course is a leading positional: `tessera <cmd> [course] [flags]`. Only the\n// first token can be the course, and only when it isn't a flag — otherwise a flag\n// value (e.g. the `serious` in `--threshold serious`) would be misread as a name.\nexport function splitCourseArg(rest: string[]): {\n course?: string;\n flags: string[];\n} {\n if (rest.length > 0 && !rest[0].startsWith('-')) {\n return { course: rest[0], flags: rest.slice(1) };\n }\n return { course: undefined, flags: rest };\n}\n\ntype CourseCommand = (\n courseRoot: string,\n workspaceRoot: string,\n flags: string[],\n) => number | Promise<number>;\n\nconst COURSE_COMMANDS: Record<string, CourseCommand> = {\n dev: async (courseRoot, workspaceRoot) =>\n (await import('./build-commands.js')).runDev(courseRoot, workspaceRoot),\n export: async (courseRoot, workspaceRoot) =>\n (await import('./build-commands.js')).runBuild(courseRoot, workspaceRoot),\n validate: (courseRoot) => runValidate(courseRoot),\n a11y: (courseRoot, workspaceRoot, flags) =>\n runA11y(courseRoot, workspaceRoot, flags),\n check: (courseRoot, workspaceRoot, flags) => {\n const validateCode = runValidate(courseRoot, { showA11yTip: false });\n if (validateCode !== 0) return validateCode;\n return runA11y(courseRoot, workspaceRoot, flags);\n },\n};\n\nexport async function main(\n argv: string[],\n cwd: string = process.cwd(),\n): Promise<number> {\n const [sub, ...rest] = argv;\n\n if (sub === 'new') return runNew(rest[0], cwd);\n if (sub === 'duplicate') return runDuplicate(rest[0], rest[1], cwd);\n\n if (sub !== undefined && Object.hasOwn(COURSE_COMMANDS, sub)) {\n if (rest.includes('--help') || rest.includes('-h')) {\n console.log(USAGE);\n return 0;\n }\n const { course, flags } = splitCourseArg(rest);\n const resolved = resolveCourse(cwd, course);\n if (!resolved.ok) {\n console.error(`[tessera] ${resolved.error}`);\n return 1;\n }\n return COURSE_COMMANDS[sub](\n resolved.courseRoot,\n resolved.workspaceRoot,\n flags,\n );\n }\n\n if (sub === '--help' || sub === '-h') {\n console.log(USAGE);\n return 0;\n }\n if (sub === undefined) {\n console.error(`No command given.\\n\\n${USAGE}`);\n return 1;\n }\n console.error(`Unknown command: ${sub}\\n\\n${USAGE}`);\n return 1;\n}\n\n// import.meta.main is true only when this module is the program entry point,\n// and resolves symlinks itself (pnpm/npm bin shims) — Node >= 24.\nif (import.meta.main) {\n void main(process.argv.slice(2)).then((code) => process.exit(code));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../../src/plugin/validate-cli.ts","../../src/plugin/a11y-cli.ts","../../src/plugin/project-name.ts","../../src/plugin/course-root.ts","../../src/plugin/template-copy.ts","../../src/plugin/new-cli.ts","../../src/plugin/duplicate-cli.ts","../../src/plugin/cli.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { validateProject, reportValidationIssues } from './validation.js';\n\nexport function runValidate(\n projectRoot: string,\n {\n showA11yTip = true,\n standardOverride,\n }: { showA11yTip?: boolean; standardOverride?: string } = {},\n): number {\n const { errors, warnings } = validateProject(projectRoot, standardOverride);\n\n reportValidationIssues({ errors, warnings });\n\n if (errors.length > 0) {\n const summary =\n `Validation failed with ${errors.length} error(s)` +\n (warnings.length > 0 ? ` and ${warnings.length} warning(s)` : '') +\n '.';\n console.error(`\\n\\x1b[31m${summary}\\x1b[0m`);\n return 1;\n }\n\n if (warnings.length > 0) {\n console.log(\n `\\n\\x1b[33mValidation passed with ${warnings.length} warning(s).\\x1b[0m`,\n );\n } else {\n console.log(\n '\\x1b[32m[tessera]\\x1b[0m Validation passed — no issues found.',\n );\n }\n if (showA11yTip) {\n console.log(\n `\\x1b[2m[tessera] Static checks only. For a full runtime accessibility audit, run: pnpm a11y ${basename(projectRoot)}\\x1b[0m`,\n );\n }\n return 0;\n}\n","import { runAudit, type AuditOptions, type ImpactLevel } from './a11y/audit.js';\n\nconst VALID_THRESHOLDS: ImpactLevel[] = [\n 'minor',\n 'moderate',\n 'serious',\n 'critical',\n];\n\nexport type ParsedA11yArgs =\n | { ok: true; args: AuditOptions }\n | { ok: false; error: string };\n\nexport function parseA11yArgs(argv: string[]): ParsedA11yArgs {\n let threshold: ImpactLevel | undefined;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === '--threshold') {\n const value = argv[++i] as ImpactLevel;\n if (!VALID_THRESHOLDS.includes(value)) {\n return {\n ok: false,\n error: `--threshold must be one of: ${VALID_THRESHOLDS.join(', ')}`,\n };\n }\n threshold = value;\n } else {\n return { ok: false, error: `Unknown argument: ${arg}` };\n }\n }\n\n const args: AuditOptions = {};\n if (threshold !== undefined) args.threshold = threshold;\n return { ok: true, args };\n}\n\nexport async function runA11y(\n projectRoot: string,\n workspaceRoot: string,\n argv: string[],\n): Promise<number> {\n const parsed = parseA11yArgs(argv);\n if (!parsed.ok) {\n console.error(`[tessera a11y] ${parsed.error}`);\n return 1;\n }\n return runAudit(projectRoot, workspaceRoot, parsed.args);\n}\n","// Pure, dependency-free helpers shared by the `tessera new` subcommand and the\n// `create-tessera` scaffolder. Kept import-free so create-tessera can bundle it\n// at build time without dragging in the rest of the plugin (vite, svelte, …).\n\n// npm package name rules: 1-214 chars, lowercase, must start with [a-z0-9],\n// allowed chars [a-z0-9._-], no leading dot or underscore.\nexport function validateProjectName(\n name: string,\n label = 'Project name',\n): string | null {\n if (!name) return `${label} is required`;\n if (name.length > 214) return `${label} must be 214 characters or fewer`;\n if (name !== name.toLowerCase()) return `${label} must be lowercase`;\n if (!/^[a-z0-9]/.test(name)) {\n return `${label} must start with a letter or digit`;\n }\n if (!/^[a-z0-9._-]+$/.test(name)) {\n return `${label} may only contain lowercase letters, digits, \"-\", \"_\", and \".\"`;\n }\n return null;\n}\n\nexport function toTitleCase(slug: string): string {\n return slug\n .split(/[-_.\\s]+/)\n .filter(Boolean)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { validateProjectName } from './project-name.js';\n\nexport interface ResolvedCourse {\n ok: true;\n courseRoot: string;\n workspaceRoot: string;\n}\n\nexport type ResolveResult = ResolvedCourse | { ok: false; error: string };\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction isCourse(dir: string): boolean {\n return existsSync(join(dir, 'course.config.js'));\n}\n\n// A workspace is the nearest ancestor holding a courses/ directory. The walk\n// includes the starting dir, so the workspace root resolves to itself.\nexport function findWorkspaceRoot(cwd: string): string | null {\n for (let dir = resolve(cwd); ; dir = dirname(dir)) {\n if (isDir(join(dir, 'courses'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n }\n}\n\nfunction scanCourses(workspaceRoot: string): {\n courses: string[];\n malformed: string[];\n} {\n const coursesDir = join(workspaceRoot, 'courses');\n const courses: string[] = [];\n const malformed: string[] = [];\n try {\n for (const e of readdirSync(coursesDir, { withFileTypes: true })) {\n if (!e.isDirectory()) continue;\n const dir = join(coursesDir, e.name);\n if (isCourse(dir)) courses.push(e.name);\n else if (isDir(join(dir, 'pages'))) malformed.push(e.name);\n }\n } catch {\n return { courses, malformed };\n }\n return { courses: courses.sort(), malformed: malformed.sort() };\n}\n\nexport function listCourses(workspaceRoot: string): string[] {\n return scanCourses(workspaceRoot).courses;\n}\n\nexport function listMalformedCourses(workspaceRoot: string): string[] {\n return scanCourses(workspaceRoot).malformed;\n}\n\nconst NOT_A_WORKSPACE =\n 'Not inside a Tessera workspace — no `courses/` directory was found at or above the current directory.';\n\nfunction malformedHint(malformed: string[]): string {\n if (malformed.length === 0) return '';\n return (\n `\\nSkipped (missing course.config.js):\\n` +\n malformed.map((c) => ` courses/${c}`).join('\\n')\n );\n}\n\nfunction listHint(workspaceRoot: string): string {\n const { courses, malformed } = scanCourses(workspaceRoot);\n if (courses.length === 0) {\n return (\n '\\nNo courses found. Create one with `tessera new <name>`.' +\n malformedHint(malformed)\n );\n }\n return (\n `\\nAvailable courses:\\n${courses.map((c) => ` ${c}`).join('\\n')}` +\n '\\nName one (`tessera <command> <course>`) or cd into its folder.' +\n malformedHint(malformed)\n );\n}\n\n// A name argument always wins; otherwise the cwd must itself be a course. There\n// is deliberately no \"single course → use it implicitly\" rule, so a bare command\n// never changes meaning when a second course is added.\nexport function resolveCourse(cwd: string, name?: string): ResolveResult {\n const here = resolve(cwd);\n\n if (name) {\n const nameError = validateProjectName(name, 'the name');\n if (nameError) {\n return {\n ok: false,\n error: `Invalid course name \"${name}\" — ${nameError}.`,\n };\n }\n const workspaceRoot = findWorkspaceRoot(here);\n if (!workspaceRoot) return { ok: false, error: NOT_A_WORKSPACE };\n const courseRoot = join(workspaceRoot, 'courses', name);\n if (!isCourse(courseRoot)) {\n return {\n ok: false,\n error: `Course \"${name}\" not found in courses/.${listHint(workspaceRoot)}`,\n };\n }\n return { ok: true, courseRoot, workspaceRoot };\n }\n\n if (isCourse(here)) {\n const workspaceRoot = findWorkspaceRoot(here) ?? dirname(dirname(here));\n return { ok: true, courseRoot: here, workspaceRoot };\n }\n\n const workspaceRoot = findWorkspaceRoot(here);\n if (!workspaceRoot) return { ok: false, error: NOT_A_WORKSPACE };\n return {\n ok: false,\n error: `No course specified.${listHint(workspaceRoot)}`,\n };\n}\n","import {\n copyFileSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\n\n// npm's tarball packing strips/renames leading-dot files, so templates store\n// them prefixed and we restore the dot on copy. (create-vite convention.)\nconst RENAME: Record<string, string> = {\n _gitignore: '.gitignore',\n _gitkeep: '.gitkeep',\n};\n\n// Text files get token substitution; everything else is copied byte-for-byte.\nconst TEXT = /\\.(svelte|js|ts|json|css|md|html)$/;\n\nfunction applyTokens(s: string, tokens: Record<string, string>): string {\n return s.replace(/__([A-Z_]+)__/g, (m, key) =>\n key in tokens ? tokens[key] : m,\n );\n}\n\nexport function copyTemplate(\n srcDir: string,\n destDir: string,\n tokens: Record<string, string>,\n): void {\n mkdirSync(destDir, { recursive: true });\n for (const entry of readdirSync(srcDir, { withFileTypes: true })) {\n const src = join(srcDir, entry.name);\n const dest = join(destDir, RENAME[entry.name] ?? entry.name);\n if (entry.isDirectory()) {\n copyTemplate(src, dest, tokens);\n } else if (TEXT.test(entry.name)) {\n writeFileSync(dest, applyTokens(readFileSync(src, 'utf-8'), tokens));\n } else {\n copyFileSync(src, dest);\n }\n }\n}\n","import { existsSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\nimport { join, relative, resolve } from 'node:path';\nimport { findWorkspaceRoot } from './course-root.js';\nimport { validateProjectName, toTitleCase } from './project-name.js';\nimport { copyTemplate } from './template-copy.js';\nimport { resolvePackageRoot } from './package-root.js';\n\n// `tessera new <name>` — stamp a new course into courses/<name> inside the\n// current workspace. No install step: the workspace already owns the deps.\nexport function runNew(name: string | undefined, cwd: string): number {\n if (name === '--help' || name === '-h') {\n console.log(\n 'Usage: tessera new <name>\\n\\n' +\n 'Scaffold a new course into courses/<name> inside the current workspace.',\n );\n return 0;\n }\n if (!name) {\n console.error('Usage: tessera new <name>');\n return 1;\n }\n\n const nameError = validateProjectName(name, 'Course name');\n if (nameError) {\n console.error(`[tessera new] ${nameError}`);\n return 1;\n }\n\n const workspaceRoot = findWorkspaceRoot(resolve(cwd));\n if (!workspaceRoot) {\n console.error(\n '[tessera new] Not inside a Tessera workspace — run this from a workspace (a directory with a `courses/` folder).',\n );\n return 1;\n }\n\n const courseDir = join(workspaceRoot, 'courses', name);\n if (existsSync(courseDir)) {\n console.error(`[tessera new] Course \"${name}\" already exists.`);\n return 1;\n }\n\n const templateDir = join(resolvePackageRoot(), 'templates', 'course');\n copyTemplate(templateDir, courseDir, {\n PROJECT_TITLE: toTitleCase(name),\n COURSE_ID: `urn:uuid:${randomUUID()}`,\n });\n\n const rel = relative(workspaceRoot, courseDir);\n console.log(`\\nCreated ${rel}.\\n\\nNext steps:\\n pnpm tessera dev ${name}\\n`);\n return 0;\n}\n","import { cpSync, existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\nimport { basename, join, relative } from 'node:path';\nimport { resolveCourse } from './course-root.js';\nimport { validateProjectName } from './project-name.js';\n\n// The top-level `id` property as scaffolders write it: the key (bare or quoted)\n// first on its own line, then its value. Anchoring to line start keeps `id:`\n// inside a comment or a string value from matching, and CourseConfig has no\n// nested `id`, so the first match is always the course identity.\nconst ID_LINE =\n /^([ \\t]*'?id'?[ \\t]*:[ \\t]*)('(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*`|[^\\n,}]*)/m;\nconst DEFAULT_OBJECT = /export\\s+default\\s*\\{/;\n\n// A verbatim copy inherits the source's `id`; mint a fresh one so the duplicate\n// is a distinct course (own storage key + LRS activity id).\nfunction reidentifyCourse(courseRoot: string): void {\n const configPath = join(courseRoot, 'course.config.js');\n if (!existsSync(configPath)) return;\n const text = readFileSync(configPath, 'utf-8');\n const newId = `'urn:uuid:${randomUUID()}'`;\n\n if (ID_LINE.test(text)) {\n writeFileSync(configPath, text.replace(ID_LINE, `$1${newId}`));\n return;\n }\n const obj = DEFAULT_OBJECT.exec(text);\n if (obj) {\n const at = obj.index + obj[0].length;\n writeFileSync(\n configPath,\n `${text.slice(0, at)}\\n id: ${newId},${text.slice(at)}`,\n );\n return;\n }\n console.warn(\n `[tessera duplicate] Could not set a unique id in ${configPath} — the copy shares the source's identity. Add a unique \"id\" manually.`,\n );\n}\n\nconst HELP =\n 'Usage: tessera duplicate <source> <new>\\n\\n' +\n 'Copy courses/<source>/ to courses/<new>/ within the current workspace.';\n\n// Generated/build artifacts that should never travel with a verbatim copy. The\n// a11y throwaway build and Vite's cache live under node_modules, so they're\n// already pruned by the node_modules skip; the rest are belt-and-suspenders.\nconst SKIP = new Set(['node_modules', 'dist', 'a11y-report.json', '.vite']);\n\nfunction skip(srcPath: string): boolean {\n const name = basename(srcPath);\n return SKIP.has(name) || name.startsWith('.tessera');\n}\n\n// `tessera duplicate <source> <new>` — copy an existing course verbatim within\n// the current workspace. Unlike `new`, there is no template stamping: the JS\n// config (including its title) is copied untouched.\nexport function runDuplicate(\n source: string | undefined,\n target: string | undefined,\n cwd: string,\n): number {\n if (\n source === '--help' ||\n source === '-h' ||\n target === '--help' ||\n target === '-h'\n ) {\n console.log(HELP);\n return 0;\n }\n if (!source || !target) {\n console.error('Usage: tessera duplicate <source> <new>');\n return 1;\n }\n\n const nameError = validateProjectName(target, 'Course name');\n if (nameError) {\n console.error(`[tessera duplicate] ${nameError}`);\n return 1;\n }\n\n const resolved = resolveCourse(cwd, source);\n if (!resolved.ok) {\n console.error(`[tessera duplicate] ${resolved.error}`);\n return 1;\n }\n const { courseRoot: srcDir, workspaceRoot } = resolved;\n\n const destDir = join(workspaceRoot, 'courses', target);\n if (existsSync(destDir)) {\n console.error(`[tessera duplicate] Course \"${target}\" already exists.`);\n return 1;\n }\n\n cpSync(srcDir, destDir, {\n recursive: true,\n filter: (src) => src === srcDir || !skip(src),\n });\n reidentifyCourse(destDir);\n\n const rel = relative(workspaceRoot, destDir);\n const srcRel = relative(workspaceRoot, srcDir);\n console.log(\n `\\nCreated ${rel} (duplicated from ${srcRel}).\\n\\n` +\n `Remember to update the title in ${rel}/course.config.js.\\n\\n` +\n `Next steps:\\n pnpm tessera dev ${target}\\n`,\n );\n return 0;\n}\n","#!/usr/bin/env node\nimport { runValidate } from './validate-cli.js';\nimport { runA11y } from './a11y-cli.js';\nimport { runNew } from './new-cli.js';\nimport { runDuplicate } from './duplicate-cli.js';\nimport { resolveCourse } from './course-root.js';\nimport { VALID_EXPORT_STANDARDS } from './validation.js';\n\nconst USAGE = `Usage: tessera <command> [course] [options]\n\nCommands:\n new <name> Scaffold a new course into courses/<name>\n duplicate <source> <new> Copy courses/<source> to courses/<new>\n dev [course] Start the Vite dev server\n export [course] Build and package the course for its LMS standard\n validate [course] Fast static structure checks\n a11y [course] Runtime accessibility audit (builds + drives Playwright)\n check [course] Run validate, then a11y\n\nRun a command from inside a course folder, or name the course explicitly.\n\nexport/validate options:\n --standard <web|scorm12|scorm2004|cmi5|xapi> Override course.config.js export.standard\n\na11y/check options:\n --threshold <minor|moderate|serious|critical> Failing impact (default: serious)`;\n\n// Validate here, against the config validator's list, so an unknown standard\n// fails before Vite spins up.\nexport function parseExportFlags(flags: string[]): {\n standardOverride?: string;\n error?: string;\n} {\n let standardOverride: string | undefined;\n for (let i = 0; i < flags.length; i++) {\n const arg = flags[i];\n let value: string | undefined;\n if (arg === '--standard') {\n value = flags[++i];\n } else if (arg.startsWith('--standard=')) {\n value = arg.slice('--standard='.length);\n } else {\n return { error: `Unknown argument: ${arg}` };\n }\n if (value === undefined || value.startsWith('-')) {\n return { error: '--standard requires a value' };\n }\n if (!VALID_EXPORT_STANDARDS.includes(value)) {\n return {\n error: `--standard must be one of ${VALID_EXPORT_STANDARDS.join(', ')}, got \"${value}\"`,\n };\n }\n standardOverride = value;\n }\n return standardOverride ? { standardOverride } : {};\n}\n\n// The course is a leading positional: `tessera <cmd> [course] [flags]`. Only the\n// first token can be the course, and only when it isn't a flag — otherwise a flag\n// value (e.g. the `serious` in `--threshold serious`) would be misread as a name.\nexport function splitCourseArg(rest: string[]): {\n course?: string;\n flags: string[];\n} {\n if (rest.length > 0 && !rest[0].startsWith('-')) {\n return { course: rest[0], flags: rest.slice(1) };\n }\n return { course: undefined, flags: rest };\n}\n\ntype CourseCommand = (\n courseRoot: string,\n workspaceRoot: string,\n flags: string[],\n) => number | Promise<number>;\n\nconst COURSE_COMMANDS: Record<string, CourseCommand> = {\n dev: async (courseRoot, workspaceRoot) =>\n (await import('./build-commands.js')).runDev(courseRoot, workspaceRoot),\n export: async (courseRoot, workspaceRoot, flags) => {\n const { standardOverride, error } = parseExportFlags(flags);\n if (error) {\n console.error(`[tessera] ${error}`);\n return 1;\n }\n return (await import('./build-commands.js')).runBuild(\n courseRoot,\n workspaceRoot,\n standardOverride,\n );\n },\n validate: (courseRoot, _workspaceRoot, flags) => {\n const { standardOverride, error } = parseExportFlags(flags);\n if (error) {\n console.error(`[tessera] ${error}`);\n return 1;\n }\n return runValidate(courseRoot, { standardOverride });\n },\n a11y: (courseRoot, workspaceRoot, flags) =>\n runA11y(courseRoot, workspaceRoot, flags),\n check: (courseRoot, workspaceRoot, flags) => {\n const validateCode = runValidate(courseRoot, { showA11yTip: false });\n if (validateCode !== 0) return validateCode;\n return runA11y(courseRoot, workspaceRoot, flags);\n },\n};\n\nexport async function main(\n argv: string[],\n cwd: string = process.cwd(),\n): Promise<number> {\n const [sub, ...rest] = argv;\n\n if (sub === 'new') return runNew(rest[0], cwd);\n if (sub === 'duplicate') return runDuplicate(rest[0], rest[1], cwd);\n\n if (sub !== undefined && Object.hasOwn(COURSE_COMMANDS, sub)) {\n if (rest.includes('--help') || rest.includes('-h')) {\n console.log(USAGE);\n return 0;\n }\n const { course, flags } = splitCourseArg(rest);\n const resolved = resolveCourse(cwd, course);\n if (!resolved.ok) {\n console.error(`[tessera] ${resolved.error}`);\n return 1;\n }\n return COURSE_COMMANDS[sub](\n resolved.courseRoot,\n resolved.workspaceRoot,\n flags,\n );\n }\n\n if (sub === '--help' || sub === '-h') {\n console.log(USAGE);\n return 0;\n }\n if (sub === undefined) {\n console.error(`No command given.\\n\\n${USAGE}`);\n return 1;\n }\n console.error(`Unknown command: ${sub}\\n\\n${USAGE}`);\n return 1;\n}\n\n// import.meta.main is true only when this module is the program entry point,\n// and resolves symlinks itself (pnpm/npm bin shims) — Node >= 24.\nif (import.meta.main) {\n void main(process.argv.slice(2)).then((code) => process.exit(code));\n}\n"],"mappings":"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"}
@@ -56,6 +56,9 @@ interface CourseConfig {
56
56
  description?: string;
57
57
  author?: string;
58
58
  version?: string;
59
+ /** Resume policy. 'auto' (default) restores saved progress unless the page
60
+ * structure changed since it was saved; 'never' always starts fresh. */
61
+ resume?: 'auto' | 'never';
59
62
  /** BCP-47 language tag for <html lang>. Defaults to 'en'. WCAG 3.1.1. */
60
63
  language?: string;
61
64
  /** Accessibility checker configuration. */
@@ -265,10 +268,13 @@ type ImpactLevel = 'minor' | 'moderate' | 'serious' | 'critical';
265
268
  declare function runAudit(projectRoot: string, workspaceRoot: string, options?: AuditOptions): Promise<number>;
266
269
  //#endregion
267
270
  //#region src/plugin/index.d.ts
268
- declare function tesseraPlugin(): (Plugin<any> | Plugin<any>[])[];
271
+ declare function tesseraPlugin(options?: {
272
+ standardOverride?: string;
273
+ }): (Plugin<any> | Plugin<any>[])[];
269
274
  /** Fill runtime defaults into a parsed course.config.js. Exported for tests. */
270
275
  declare function mergeCourseConfig(userConfig: Partial<CourseConfig>): {
271
276
  title: string;
277
+ resume: "auto" | "never";
272
278
  navigation: {
273
279
  mode: string;
274
280
  canAccess?: AccessFn;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/runtime/xapi/types.ts","../../src/runtime/types.ts","../../src/plugin/manifest.ts","../../src/runtime/progress.svelte.ts","../../src/runtime/access.ts","../../src/plugin/a11y/audit.ts","../../src/plugin/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/runtime/xapi/types.ts","../../src/runtime/types.ts","../../src/plugin/manifest.ts","../../src/runtime/progress.svelte.ts","../../src/runtime/access.ts","../../src/plugin/a11y/audit.ts","../../src/plugin/index.ts"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { n as runAudit } from "../audit-DkXqQTqn.js";
2
- import { n as tesseraPlugin, t as mergeCourseConfig } from "../plugin-CFUFgwHB.js";
1
+ import { n as runAudit } from "../audit-DsYqXbqm.js";
2
+ import { n as tesseraPlugin, t as mergeCourseConfig } from "../plugin-BuMiDTmU.js";
3
3
  export { mergeCourseConfig, runAudit, tesseraPlugin };
@@ -1,4 +1,4 @@
1
- import { a as isPlausibleLanguageTag, c as validateProject, d as generateManifest, f as readCourseConfig, i as isIgnored, l as buildCsp, o as normalizeA11y, r as resolvePackageRoot, s as reportValidationIssues, t as AUDIT_ENV_FLAG, u as courseIdentity } from "./audit-DkXqQTqn.js";
1
+ import { a as isIgnored, c as reportValidationIssues, d as courseIdentity, f as generateManifest, l as validateProject, m as readResolvedConfig, o as isPlausibleLanguageTag, p as readCourseConfig, r as resolvePackageRoot, s as normalizeA11y, t as AUDIT_ENV_FLAG, u as buildCsp } from "./audit-DsYqXbqm.js";
2
2
  import { svelte } from "@sveltejs/vite-plugin-svelte";
3
3
  import { isAbsolute, relative, resolve } from "node:path";
4
4
  import { cpSync, createWriteStream, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
@@ -304,7 +304,8 @@ function virtualModule(name, virtualId, load) {
304
304
  }
305
305
  };
306
306
  }
307
- function tesseraPlugin() {
307
+ function tesseraPlugin(options = {}) {
308
+ const { standardOverride } = options;
308
309
  const manifestRef = {
309
310
  current: null,
310
311
  root: ""
@@ -335,25 +336,25 @@ function tesseraPlugin() {
335
336
  }
336
337
  }),
337
338
  tesseraA11yCompilerPlugin(a11y),
338
- tesseraValidationPlugin(),
339
- tesseraEntryPlugin(),
339
+ tesseraValidationPlugin(standardOverride),
340
+ tesseraEntryPlugin(standardOverride),
340
341
  tesseraConfigDefaultsPlugin(),
341
- tesseraConfigPlugin(),
342
+ tesseraConfigPlugin(standardOverride),
342
343
  tesseraPagesPlugin(),
343
344
  tesseraManifestPlugin(manifestRef),
344
345
  tesseraLayoutPlugin(),
345
346
  tesseraQuizPlugin(),
346
- tesseraAdapterPlugin(),
347
- tesseraXAPISetupPlugin(),
347
+ tesseraAdapterPlugin(standardOverride),
348
+ tesseraXAPISetupPlugin(standardOverride),
348
349
  tesseraFirstPagePreloadPlugin(manifestRef),
349
- tesseraExportPlugin()
350
+ tesseraExportPlugin(standardOverride)
350
351
  ];
351
352
  }
352
353
  const VIRTUAL_ENTRY_ID = "virtual:tessera-entry";
353
354
  const RESOLVED_ENTRY_ID = "\0virtual:tessera-entry";
354
355
  const VIRTUAL_MAIN_ID = "/virtual:tessera-main";
355
356
  const RESOLVED_MAIN_ID = "\0virtual:tessera-main";
356
- function tesseraEntryPlugin() {
357
+ function tesseraEntryPlugin(standardOverride) {
357
358
  const runtimeDir = resolveRuntimeDir();
358
359
  const stylesDir = resolveStylesDir();
359
360
  const appSveltePath = resolve(runtimeDir, "App.svelte");
@@ -370,7 +371,7 @@ function tesseraEntryPlugin() {
370
371
  },
371
372
  buildStart() {
372
373
  if (isBuild) {
373
- const read = readCourseConfig(projectRoot);
374
+ const read = readResolvedConfig(projectRoot, standardOverride);
374
375
  writeFileSync(resolve(projectRoot, "index.html"), generateIndexHtml(readLanguage(read), cspMeta(read)), "utf-8");
375
376
  }
376
377
  },
@@ -418,12 +419,8 @@ function readLanguage(read) {
418
419
  const lang = read.ok ? read.config.language : void 0;
419
420
  return isPlausibleLanguageTag(lang) ? lang : "en";
420
421
  }
421
- function readExportStandard(read) {
422
- if (!read.ok) return "unknown";
423
- return read.config.export?.standard || "web";
424
- }
425
422
  function cspMeta(read) {
426
- if (readExportStandard(read) !== "web") return "";
423
+ if (read.standard !== "web") return "";
427
424
  const csp = read.ok ? read.config.export?.csp : void 0;
428
425
  if (csp === false) return "";
429
426
  return `\n <meta http-equiv="Content-Security-Policy" content="${buildCsp(csp)}" />`;
@@ -503,6 +500,7 @@ function mergeCourseConfig(userConfig) {
503
500
  return {
504
501
  ...userConfig,
505
502
  title: userConfig.title || "Untitled Course",
503
+ resume: userConfig.resume ?? "auto",
506
504
  navigation: {
507
505
  mode: "free",
508
506
  ...userConfig.navigation
@@ -521,11 +519,11 @@ function mergeCourseConfig(userConfig) {
521
519
  }
522
520
  };
523
521
  }
524
- function tesseraConfigPlugin() {
522
+ function tesseraConfigPlugin(standardOverride) {
525
523
  return virtualModule("tessera:config", VIRTUAL_CONFIG_ID, function({ projectRoot }) {
526
524
  const configPath = resolve(projectRoot, "course.config.js");
527
525
  if (existsSync(configPath)) this.addWatchFile(configPath);
528
- const read = readCourseConfig(projectRoot);
526
+ const read = readResolvedConfig(projectRoot, standardOverride);
529
527
  const userConfig = read.ok ? read.config : {};
530
528
  return `export default ${JSON.stringify(mergeCourseConfig(userConfig))};`;
531
529
  });
@@ -550,7 +548,7 @@ function tesseraPagesPlugin() {
550
548
  return `export default import.meta.glob('/pages/**/*.svelte');`;
551
549
  });
552
550
  }
553
- function tesseraValidationPlugin() {
551
+ function tesseraValidationPlugin(standardOverride) {
554
552
  let projectRoot;
555
553
  let isBuild = false;
556
554
  return {
@@ -559,10 +557,10 @@ function tesseraValidationPlugin() {
559
557
  configResolved(config) {
560
558
  projectRoot = config.root;
561
559
  isBuild = config.command === "build";
562
- if (!isBuild) runValidation(projectRoot);
560
+ if (!isBuild) runValidation(projectRoot, standardOverride);
563
561
  },
564
562
  buildStart() {
565
- if (isBuild) runValidation(projectRoot);
563
+ if (isBuild) runValidation(projectRoot, standardOverride);
566
564
  }
567
565
  };
568
566
  }
@@ -596,12 +594,12 @@ function tesseraA11yCompilerPlugin(a11y) {
596
594
  }
597
595
  };
598
596
  }
599
- function runValidation(projectRoot) {
600
- const result = validateProject(projectRoot);
597
+ function runValidation(projectRoot, standardOverride) {
598
+ const result = validateProject(projectRoot, standardOverride);
601
599
  reportValidationIssues(result);
602
600
  if (result.errors.length > 0) throw new Error(`Tessera validation failed with ${result.errors.length} error(s). Fix the errors above to continue.`);
603
601
  }
604
- function tesseraExportPlugin() {
602
+ function tesseraExportPlugin(standardOverride) {
605
603
  let projectRoot;
606
604
  let isBuild = false;
607
605
  return {
@@ -614,7 +612,7 @@ function tesseraExportPlugin() {
614
612
  async closeBundle() {
615
613
  if (!isBuild) return;
616
614
  if (isAuditBuild()) return;
617
- const read = readCourseConfig(projectRoot);
615
+ const read = readResolvedConfig(projectRoot, standardOverride);
618
616
  if (!read.ok) {
619
617
  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.");
620
618
  if (read.reason === "no-export") throw new Error("[tessera:export] course.config.js: could not locate `export default { ... }`. Cannot determine export.standard.");
@@ -712,12 +710,10 @@ export function createAdapter() {
712
710
  }
713
711
  `;
714
712
  }
715
- function tesseraAdapterPlugin() {
713
+ function tesseraAdapterPlugin(standardOverride) {
716
714
  return virtualModule("tessera:adapter", VIRTUAL_ADAPTER_ID, ({ projectRoot, isBuild }) => {
717
715
  if (!isBuild) return `export { createAdapter } from 'tessera-learn/runtime/adapters/index.js';`;
718
- let standard = "web";
719
- const read = readCourseConfig(projectRoot);
720
- if (read.ok && typeof read.config.export?.standard === "string") standard = read.config.export.standard;
716
+ let standard = readResolvedConfig(projectRoot, standardOverride).standard;
721
717
  if (isAuditBuild()) standard = "web";
722
718
  if (standard in LMS_ADAPTER_GEN) return generateLmsAdapterModule(standard);
723
719
  return `
@@ -729,18 +725,13 @@ export function createAdapter(config, options) {
729
725
  });
730
726
  }
731
727
  const VIRTUAL_XAPI_SETUP_ID = "virtual:tessera-xapi-setup";
732
- function tesseraXAPISetupPlugin() {
728
+ function tesseraXAPISetupPlugin(standardOverride) {
733
729
  return virtualModule("tessera:xapi-setup", VIRTUAL_XAPI_SETUP_ID, ({ projectRoot, isBuild }) => {
734
730
  if (!isBuild) return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;
735
731
  if (isAuditBuild()) return `export async function buildXAPIClient() { return null; }`;
736
- let standard = "web";
737
- let hasXapi = false;
738
- const read = readCourseConfig(projectRoot);
739
- if (read.ok) {
740
- if (typeof read.config.export?.standard === "string") standard = read.config.export.standard;
741
- hasXapi = read.config.xapi != null;
742
- }
743
- if (hasXapi || standard === "cmi5" || standard === "xapi") return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;
732
+ const read = readResolvedConfig(projectRoot, standardOverride);
733
+ const standard = read.standard;
734
+ if (read.ok && read.config.xapi != null || standard === "cmi5" || standard === "xapi") return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;
744
735
  return `export async function buildXAPIClient() { return null; }`;
745
736
  });
746
737
  }
@@ -771,4 +762,4 @@ function tesseraFirstPagePreloadPlugin(manifestRef) {
771
762
  //#endregion
772
763
  export { tesseraPlugin as n, mergeCourseConfig as t };
773
764
 
774
- //# sourceMappingURL=plugin-CFUFgwHB.js.map
765
+ //# sourceMappingURL=plugin-BuMiDTmU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-BuMiDTmU.js","names":[],"sources":["../src/runtime/slugify.ts","../src/plugin/export.ts","../src/plugin/override-plugin.ts","../src/plugin/layout.ts","../src/plugin/quiz.ts","../src/plugin/index.ts"],"sourcesContent":["/**\n * Slugify a string for use as a URL-safe / filename-safe identifier.\n * \"My Course Title\" → \"my-course-title\"\n *\n * Shared by the runtime (`WebAdapter` localStorage key) and the build-time\n * exporter (`runExport` zip filename). Both want identical, deterministic\n * output so a course's storage key matches its package name.\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n","import {\n existsSync,\n readdirSync,\n statSync,\n writeFileSync,\n unlinkSync,\n} from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createWriteStream } from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport { ZipArchive } from 'archiver';\nimport { slugify } from '../runtime/slugify.js';\nimport { courseIdentity } from '../runtime/types.js';\n\n// ---------- Types ----------\n\ninterface ExportConfig {\n title: string;\n id?: string;\n description?: string;\n version?: string;\n scoring?: { passingScore?: number };\n completion?: { mode?: 'quiz' | 'percentage' };\n export?: { standard?: string };\n}\n\n// ---------- Helpers ----------\n\nconst UNTITLED_TITLE = 'Untitled Course';\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * Recursively collect all file paths relative to a directory.\n */\nfunction collectFiles(dir: string, base: string = ''): string[] {\n const files: string[] = [];\n if (!existsSync(dir)) return files;\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const relPath = base ? `${base}/${entry.name}` : entry.name;\n // Dirent is lstat-based; stat symlinks so a symlinked dir still recurses.\n const isDir = entry.isSymbolicLink()\n ? statSync(resolve(dir, entry.name)).isDirectory()\n : entry.isDirectory();\n if (isDir) {\n files.push(...collectFiles(resolve(dir, entry.name), relPath));\n } else {\n files.push(relPath);\n }\n }\n return files;\n}\n\n/**\n * Derive a stable URN IRI from a seed string. cmi5 §13.1 / xs:anyURI\n * require course / AU ids to be IRIs — bare hex or UUID-shaped strings\n * (without correct version/variant bits) aren't conformant URNs and may\n * be rejected by strict LMS importers.\n *\n * Hash the seed so the id survives rebuilds, then format as\n * `urn:tessera:<kind>:<hex>`. The same seed always produces the same\n * IRI, so existing LRS records are not orphaned by re-export.\n */\nfunction stableUrn(kind: 'course' | 'au', seed: string): string {\n const h = createHash('sha256').update(seed).digest('hex');\n // 32 hex chars (128 bits of entropy) is plenty; trim to keep ids short.\n return `urn:tessera:${kind}:${h.slice(0, 32)}`;\n}\n\n// AU activity id, derived from the course id so re-exports don't orphan LRS\n// records. Shared by the cmi5 and tincan manifests.\nfunction auIdFor(config: ExportConfig): string {\n const id = courseIdentity(config);\n return stableUrn('au', id ? `${id}#au` : 'tessera-au');\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ---------- Manifest Generators ----------\n\n/** Per-version XML differences in imsmanifest.xml between SCORM 1.2 and 2004. */\ninterface ScormManifestDialect {\n rootNs: string;\n adlcpNs: string;\n schemaversion: string;\n /** Attribute name on <resource>: SCORM 1.2 uses lowercase, 2004 uses camelCase. */\n scormTypeAttr: 'scormtype' | 'scormType';\n /** Whitespace-separated namespace+XSD pairs for xsi:schemaLocation. */\n schemaLocation: string;\n}\n\nconst SCORM_DIALECTS: Record<'1.2' | '2004', ScormManifestDialect> = {\n '1.2': {\n rootNs: 'http://www.imsproject.org/xsd/imscp_rootv1p1p2',\n adlcpNs: 'http://www.adlnet.org/xsd/adlcp_rootv1p2',\n schemaversion: '1.2',\n scormTypeAttr: 'scormtype',\n schemaLocation:\n 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd ' +\n 'http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd ' +\n 'http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd',\n },\n '2004': {\n rootNs: 'http://www.imsglobal.org/xsd/imscp_v1p1',\n adlcpNs: 'http://www.adlnet.org/xsd/adlcp_v1p3',\n schemaversion: '2004 4th Edition',\n scormTypeAttr: 'scormType',\n schemaLocation:\n 'http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd ' +\n 'http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd',\n },\n};\n\nexport function generateScormManifest(\n version: '1.2' | '2004',\n config: ExportConfig,\n distDir: string,\n): string {\n const dialect = SCORM_DIALECTS[version];\n const title = escapeXml(config.title || UNTITLED_TITLE);\n const files = collectFiles(distDir);\n const fileElements = files\n .map((f) => ` <file href=\"${escapeXml(f)}\" />`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<manifest identifier=\"tessera-course\" version=\"1.0\"\n xmlns=\"${dialect.rootNs}\"\n xmlns:adlcp=\"${dialect.adlcpNs}\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"${dialect.schemaLocation}\">\n <metadata>\n <schema>ADL SCORM</schema>\n <schemaversion>${dialect.schemaversion}</schemaversion>\n </metadata>\n <organizations default=\"org-1\">\n <organization identifier=\"org-1\">\n <title>${title}</title>\n <item identifier=\"item-1\" identifierref=\"res-1\">\n <title>${title}</title>\n </item>\n </organization>\n </organizations>\n <resources>\n <resource identifier=\"res-1\" type=\"webcontent\" adlcp:${dialect.scormTypeAttr}=\"sco\" href=\"index.html\">\n${fileElements}\n </resource>\n </resources>\n</manifest>`;\n}\n\nexport function generateCMI5Xml(config: ExportConfig): string {\n const title = escapeXml(config.title || UNTITLED_TITLE);\n const description = escapeXml(config.description || '');\n // Derive stable IDs from the course id so they survive rebuilds without\n // orphaning existing learner records in the LRS.\n const courseId = stableUrn(\n 'course',\n courseIdentity(config) || 'tessera-course',\n );\n const auId = auIdFor(config);\n // cmi5 §10.2.4 caps masteryScore at 4 decimals; avoid float drift like 0.7000000000000001.\n const masteryScore = Number(\n ((config.scoring?.passingScore ?? 70) / 100).toFixed(4),\n );\n // cmi5 §13.1.4 — `moveOn` decides which verb(s) the LMS treats as\n // satisfying the AU. For graded courses (completion gated on a quiz)\n // a learner who completes without passing should NOT receive credit, so\n // the LMS needs both a Completed AND a Passed before satisfaction.\n // Percentage-mode courses don't surface pass/fail, so completion alone\n // is the right signal.\n const moveOn =\n config.completion?.mode === 'quiz' ? 'CompletedAndPassed' : 'Completed';\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<courseStructure xmlns=\"https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd\">\n <course id=\"${courseId}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n </course>\n <au id=\"${auId}\" launchMethod=\"AnyWindow\" moveOn=\"${moveOn}\" masteryScore=\"${masteryScore}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n <url>index.html</url>\n </au>\n</courseStructure>`;\n}\n\nexport function generateTincanXml(config: ExportConfig): string {\n const title = escapeXml(config.title || UNTITLED_TITLE);\n const description = escapeXml(config.description || '');\n // Reuse the cmi5/SCORM stable-id scheme so re-exports don't orphan LRS records.\n const auId = auIdFor(config);\n // tincan.xml carries NO xAPI version — the version is set at runtime by the\n // adapter's X-Experience-API-Version header, not declared in the manifest.\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<tincan xmlns=\"http://projecttincan.com/tincan.xsd\">\n <activities>\n <activity id=\"${auId}\" type=\"http://adlnet.gov/expapi/activities/course\">\n <name>${title}</name>\n <description lang=\"en-US\">${description}</description>\n <launch lang=\"en-US\">index.html</launch>\n </activity>\n </activities>\n</tincan>`;\n}\n\n// ---------- ZIP Packaging ----------\n\nexport async function createZip(\n distDir: string,\n outputPath: string,\n): Promise<number> {\n return new Promise((res, reject) => {\n const output = createWriteStream(outputPath);\n const archive = new ZipArchive({ zlib: { level: 9 } });\n\n output.on('close', () => {\n res(archive.pointer());\n });\n output.on('error', reject);\n archive.on('error', reject);\n\n archive.pipe(output);\n archive.directory(distDir, false);\n void archive.finalize();\n });\n}\n\n// ---------- Main Export ----------\n\n/**\n * Run the export process after Vite build completes.\n * Writes manifest XML into dist/, then packages into ZIP if needed.\n */\n/** Remove any previously built zips for this package to prevent accumulation. */\nfunction cleanOldZips(projectRoot: string, slug: string): void {\n try {\n for (const f of readdirSync(projectRoot)) {\n if (f.startsWith(`${slug}-`) && f.endsWith('.zip')) {\n try {\n unlinkSync(resolve(projectRoot, f));\n } catch {}\n }\n }\n } catch {}\n}\n\n/** Packaged (zipped) export targets: which manifest file to write and how. */\nconst PACKAGED_EXPORTS: Record<\n 'scorm12' | 'scorm2004' | 'cmi5' | 'xapi',\n {\n manifestFile: string;\n label: string;\n generate: (config: ExportConfig, distDir: string) => string;\n }\n> = {\n scorm12: {\n manifestFile: 'imsmanifest.xml',\n label: 'SCORM 1.2',\n generate: (config, distDir) =>\n generateScormManifest('1.2', config, distDir),\n },\n scorm2004: {\n manifestFile: 'imsmanifest.xml',\n label: 'SCORM 2004',\n generate: (config, distDir) =>\n generateScormManifest('2004', config, distDir),\n },\n cmi5: {\n manifestFile: 'cmi5.xml',\n label: 'CMI5',\n generate: (config) => generateCMI5Xml(config),\n },\n xapi: {\n manifestFile: 'tincan.xml',\n label: 'xAPI 1.0.3',\n generate: (config) => generateTincanXml(config),\n },\n};\n\nexport async function runExport(\n projectRoot: string,\n config: ExportConfig,\n): Promise<void> {\n const distDir = resolve(projectRoot, 'dist');\n const standard = config.export?.standard || 'web';\n const slug = slugify(config.title || 'tessera-course') || 'tessera-course';\n const version = config.version || '1.0.0';\n const zipName = `${slug}-${version}.zip`;\n const zipPath = resolve(projectRoot, zipName);\n\n if (standard === 'web') {\n const files = collectFiles(distDir);\n let totalSize = 0;\n for (const f of files) totalSize += statSync(resolve(distDir, f)).size;\n console.log(`✓ Web export: dist/ (${formatSize(totalSize)})`);\n return;\n }\n\n const spec = PACKAGED_EXPORTS[standard as keyof typeof PACKAGED_EXPORTS];\n if (!spec) return; // unknown standard — the validator rejects these upstream\n\n writeFileSync(\n resolve(distDir, spec.manifestFile),\n spec.generate(config, distDir),\n 'utf-8',\n );\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(`✓ ${spec.label} export: ${zipName} (${formatSize(zipSize)})`);\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { normalizePath } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport interface OverridePluginOptions {\n name: string;\n virtualId: string;\n projectFile: string;\n /** Built-in re-exported when the project file is absent; null export otherwise. */\n builtinFile?: string;\n}\n\n/**\n * A virtual module that resolves to a project-root override file when present,\n * and to the built-in (or a null export) otherwise. Shared by the layout and\n * quiz plugins — they differ only in the virtual id, file name, and built-in.\n */\nexport function createOverridePlugin({\n name,\n virtualId,\n projectFile,\n builtinFile,\n}: OverridePluginOptions): Plugin {\n const resolvedId = '\\0' + virtualId;\n const fallback = builtinFile\n ? `export { default } from '${normalizePath(builtinFile)}';`\n : 'export default null;';\n let filePath: string;\n\n return {\n name,\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n filePath = resolve(config.root, projectFile);\n },\n\n resolveId(id) {\n if (id === virtualId) return resolvedId;\n return null;\n },\n\n load(id) {\n if (id !== resolvedId) return null;\n if (existsSync(filePath)) {\n // Only watch when it exists — addWatchFile on a missing path makes\n // Vite's importAnalysis try to resolve it as a real import.\n this.addWatchFile(filePath);\n return `export { default } from '${normalizePath(filePath)}';`;\n }\n return fallback;\n },\n\n configureServer(server: ViteDevServer) {\n // Only add/unlink flips load()'s output between the override and the\n // fallback; a `change` leaves it identical and Svelte's own HMR handles\n // the underlying file.\n server.watcher.on('all', (event, changed) => {\n if (changed !== filePath) return;\n if (event !== 'add' && event !== 'unlink') return;\n const mod = server.moduleGraph.getModuleById(resolvedId);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n });\n },\n };\n}\n","import type { Plugin } from 'vite';\nimport { createOverridePlugin } from './override-plugin.js';\n\nexport function tesseraLayoutPlugin(): Plugin {\n return createOverridePlugin({\n name: 'tessera:layout',\n virtualId: 'virtual:tessera-layout',\n projectFile: 'layout.svelte',\n });\n}\n","import type { Plugin } from 'vite';\nimport { resolve } from 'node:path';\nimport { createOverridePlugin } from './override-plugin.js';\nimport { resolvePackageRoot } from './package-root.js';\n\nexport function tesseraQuizPlugin(): Plugin {\n const builtinQuiz = resolve(\n resolvePackageRoot(),\n 'src',\n 'components',\n 'Quiz.svelte',\n );\n return createOverridePlugin({\n name: 'tessera:quiz',\n virtualId: 'virtual:tessera-quiz',\n projectFile: 'quiz.svelte',\n builtinFile: builtinQuiz,\n });\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { svelte } from '@sveltejs/vite-plugin-svelte';\nimport { resolve, relative, isAbsolute } from 'node:path';\nimport {\n existsSync,\n readdirSync,\n statSync,\n writeFileSync,\n unlinkSync,\n cpSync,\n mkdirSync,\n} from 'node:fs';\nimport {\n generateManifest,\n readCourseConfig,\n readResolvedConfig,\n type CourseConfigRead,\n} from './manifest.js';\nimport type { Manifest } from './manifest.js';\nimport type { CourseConfig } from '../runtime/types.js';\nimport {\n DEFAULT_PASSING_SCORE,\n DEFAULT_PERCENTAGE_THRESHOLD,\n} from '../runtime/defaults.js';\nimport {\n validateProject,\n reportValidationIssues,\n normalizeA11y,\n isPlausibleLanguageTag,\n isIgnored,\n type A11ySettings,\n} from './validation.js';\nimport { buildCsp } from './csp.js';\nimport { runExport } from './export.js';\nimport { tesseraLayoutPlugin } from './layout.js';\nimport { tesseraQuizPlugin } from './quiz.js';\nimport { resolvePackageRoot } from './package-root.js';\n\nimport { AUDIT_ENV_FLAG } from './a11y/audit.js';\n\nexport { runAudit } from './a11y/audit.js';\nexport type { AuditOptions, ImpactLevel } from './a11y/audit.js';\n\nfunction isAuditBuild(): boolean {\n return process.env[AUDIT_ENV_FLAG] === '1';\n}\n\n// Resolve the runtime directory where App.svelte lives\nfunction resolveRuntimeDir(): string {\n return resolve(resolvePackageRoot(), 'src', 'runtime');\n}\n\n// Resolve the framework styles directory\nfunction resolveStylesDir(): string {\n return resolve(resolvePackageRoot(), 'styles');\n}\n\n// Tier-1a state shared between the svelte() onwarn handler and the sibling\n// gate plugin. onwarn fires during transform (after the Tier-1b buildStart\n// gate), so a11y warnings are collected here and flushed/gated at buildEnd.\ninterface A11yCompilerState {\n warnings: string[];\n projectRoot: string;\n isBuild: boolean;\n settings: A11ySettings;\n}\n\n// Svelte's onwarn filename is relative to the vite root (e.g. `pages/x.svelte`)\n// in build and may be absolute or a virtual id elsewhere. Return the\n// project-relative path for a real author file, or null to skip framework /\n// node_modules / virtual modules — Tier 0 owns the framework's own warnings.\nfunction projectFileRel(\n filename: string | undefined,\n projectRoot: string,\n): string | null {\n if (!filename || !projectRoot) return null;\n if (\n filename.startsWith('\\0') ||\n filename.includes('virtual:') ||\n filename.includes('node_modules')\n ) {\n return null;\n }\n const abs = isAbsolute(filename) ? filename : resolve(projectRoot, filename);\n const rel = relative(projectRoot, abs);\n if (rel.startsWith('..') || isAbsolute(rel) || rel.includes('node_modules')) {\n return null;\n }\n return rel;\n}\n\ntype VirtualLoadCtx = { projectRoot: string; isBuild: boolean };\n\nfunction virtualModule(\n name: string,\n virtualId: string,\n load: (\n this: import('vite').Rollup.PluginContext,\n ctx: VirtualLoadCtx,\n ) => string | null,\n): Plugin {\n const resolvedId = '\\0' + virtualId;\n let projectRoot = '';\n let isBuild = false;\n return {\n name,\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n resolveId(id) {\n return id === virtualId ? resolvedId : null;\n },\n load(id) {\n return id === resolvedId\n ? load.call(this, { projectRoot, isBuild })\n : null;\n },\n };\n}\n\nexport function tesseraPlugin(options: { standardOverride?: string } = {}) {\n const { standardOverride } = options;\n const manifestRef: { current: Manifest | null; root: string } = {\n current: null,\n root: '',\n };\n const a11y: A11yCompilerState = {\n warnings: [],\n projectRoot: '',\n isBuild: false,\n settings: normalizeA11y(undefined),\n };\n return [\n svelte({\n compilerOptions: { css: 'external' },\n onwarn(warning, defaultHandler) {\n if (warning.code?.startsWith('a11y')) {\n const rel = projectFileRel(warning.filename, a11y.projectRoot);\n if (rel !== null) {\n const msg = `[${warning.code}] ${rel}: ${warning.message}`;\n if (a11y.isBuild) {\n a11y.warnings.push(msg);\n } else if (!a11y.settings.ignore.includes(warning.code)) {\n reportValidationIssues({ errors: [], warnings: [msg] });\n }\n }\n return; // suppress the raw Vite print; we re-emit via the reporter\n }\n defaultHandler?.(warning);\n },\n }),\n tesseraA11yCompilerPlugin(a11y),\n tesseraValidationPlugin(standardOverride),\n tesseraEntryPlugin(standardOverride),\n tesseraConfigDefaultsPlugin(),\n tesseraConfigPlugin(standardOverride),\n tesseraPagesPlugin(),\n tesseraManifestPlugin(manifestRef),\n tesseraLayoutPlugin(),\n tesseraQuizPlugin(),\n tesseraAdapterPlugin(standardOverride),\n tesseraXAPISetupPlugin(standardOverride),\n tesseraFirstPagePreloadPlugin(manifestRef),\n tesseraExportPlugin(standardOverride),\n ];\n}\n\n// ---------- Entry Plugin ----------\n\nconst VIRTUAL_ENTRY_ID = 'virtual:tessera-entry';\nconst RESOLVED_ENTRY_ID = '\\0' + VIRTUAL_ENTRY_ID;\nconst VIRTUAL_MAIN_ID = '/virtual:tessera-main';\nconst RESOLVED_MAIN_ID = '\\0virtual:tessera-main';\n\nfunction tesseraEntryPlugin(standardOverride?: string): Plugin {\n const runtimeDir = resolveRuntimeDir();\n const stylesDir = resolveStylesDir();\n const appSveltePath = resolve(runtimeDir, 'App.svelte');\n let projectRoot: string;\n let outDir: string;\n let isBuild = false;\n\n return {\n name: 'tessera:entry',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n outDir = resolve(config.root, config.build.outDir);\n isBuild = config.command === 'build';\n },\n\n // For build mode: write index.html so Rollup can find it\n buildStart() {\n if (isBuild) {\n const read = readResolvedConfig(projectRoot, standardOverride);\n writeFileSync(\n resolve(projectRoot, 'index.html'),\n generateIndexHtml(readLanguage(read), cspMeta(read)),\n 'utf-8',\n );\n }\n },\n\n // For build mode: clean up temporary index.html and copy assets\n closeBundle() {\n if (isBuild) {\n const htmlPath = resolve(projectRoot, 'index.html');\n if (existsSync(htmlPath)) {\n try {\n unlinkSync(htmlPath);\n } catch {}\n }\n\n // Copy assets/ into the build's assets/ so $assets/ references resolve\n const assetsDir = resolve(projectRoot, 'assets');\n const distAssetsDir = resolve(outDir, 'assets');\n if (existsSync(assetsDir)) {\n mkdirSync(distAssetsDir, { recursive: true });\n cpSync(assetsDir, distAssetsDir, { recursive: true });\n }\n }\n },\n\n // Serve index.html for the dev server\n configureServer(server: ViteDevServer) {\n return () => {\n server.middlewares.use(async (req, res, next) => {\n if (req.url === '/' || req.url === '/index.html') {\n const html = generateIndexHtml(\n readLanguage(readCourseConfig(projectRoot)),\n );\n const transformed = await server.transformIndexHtml(req.url, html);\n res.setHeader('Content-Type', 'text/html');\n res.statusCode = 200;\n res.end(transformed);\n return;\n }\n next();\n });\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;\n if (id === VIRTUAL_MAIN_ID || id === 'virtual:tessera-main')\n return RESOLVED_MAIN_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_ENTRY_ID || id === RESOLVED_MAIN_ID) {\n return generateEntryScript(appSveltePath, stylesDir, projectRoot);\n }\n return null;\n },\n };\n}\n\n// 'en' fallback applied here: the config default-merge runs later than buildStart.\n// Only a validated BCP-47 tag is interpolated into <html lang>, so a malformed\n// value (caught separately as a warning) can't ship a broken attribute.\nfunction readLanguage(read: CourseConfigRead): string {\n const lang = read.ok ? read.config.language : undefined;\n return isPlausibleLanguageTag(lang) ? lang : 'en';\n}\n\n// Web export only — never on LMS packages (whose iframe JS bridges a meta CSP\n// could break) and never on the dev server (a meta connect-src would block\n// Vite's HMR websocket). `export.csp` extends the baseline per-directive, or\n// `false` drops the meta for deployments that set a CSP header themselves.\nfunction cspMeta(read: CourseConfigRead & { standard: string }): string {\n if (read.standard !== 'web') return '';\n const csp = read.ok ? read.config.export?.csp : undefined;\n if (csp === false) return '';\n return `\\n <meta http-equiv=\"Content-Security-Policy\" content=\"${buildCsp(csp)}\" />`;\n}\n\nfunction generateIndexHtml(lang: string, csp = ''): string {\n return `<!DOCTYPE html>\n<html lang=\"${lang}\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />${csp}\n <title>Tessera Course</title>\n</head>\n<body>\n <div id=\"tessera-root\"></div>\n <script type=\"module\" src=\"/virtual:tessera-main\"></script>\n</body>\n</html>`;\n}\n\nfunction generateEntryScript(\n appSveltePath: string,\n frameworkStylesDir: string,\n projectRoot: string,\n): string {\n const normalizedPath = appSveltePath.replace(/\\\\/g, '/');\n\n // Framework CSS imports (theme → base → layout)\n const frameworkCssOrder = ['theme.css', 'base.css', 'layout.css'];\n const frameworkImports = frameworkCssOrder\n .map((file) => resolve(frameworkStylesDir, file).replace(/\\\\/g, '/'))\n .filter((path) => existsSync(path))\n .map((path) => `import '${path}';`)\n .join('\\n');\n\n // User CSS imports from project's styles/ directory\n const userStylesDir = resolve(projectRoot, 'styles');\n let userImports = '';\n if (existsSync(userStylesDir)) {\n const userCssFiles = readdirSync(userStylesDir)\n .filter((f) => f.endsWith('.css'))\n .sort();\n userImports = userCssFiles\n .map((f) => resolve(userStylesDir, f).replace(/\\\\/g, '/'))\n .map((path) => `import '${path}';`)\n .join('\\n');\n }\n\n return `// Framework styles\n${frameworkImports}\n// User styles\n${userImports}\n\nimport { mount } from 'svelte';\nimport App from '${normalizedPath}';\n\nmount(App, {\n target: document.getElementById('tessera-root'),\n});\n`;\n}\n\n// ---------- Config Plugin ----------\n\nconst VIRTUAL_CONFIG_ID = 'virtual:tessera-config';\n\nfunction completionDefaults(mode: string | undefined): {\n completion: Record<string, unknown>;\n passingScore: number;\n} {\n if (mode === 'manual') {\n return { completion: { mode: 'manual' }, passingScore: 0 };\n }\n if (mode === 'quiz') {\n return {\n completion: { mode: 'quiz' },\n passingScore: DEFAULT_PASSING_SCORE,\n };\n }\n return {\n completion: {\n mode: 'percentage',\n percentageThreshold: DEFAULT_PERCENTAGE_THRESHOLD,\n },\n passingScore: DEFAULT_PASSING_SCORE,\n };\n}\n\nfunction tesseraConfigDefaultsPlugin(): Plugin {\n return {\n name: 'tessera:config-defaults',\n enforce: 'pre',\n config(config) {\n const root = config.root || process.cwd();\n return {\n base: './',\n build: { assetsDir: 'tessera' },\n resolve: { alias: { $assets: resolve(root, 'assets') } },\n // tessera-learn ships .ts/.svelte.ts source; Vite's dep optimizer\n // doesn't run vite-plugin-svelte's preprocessor, so skip pre-bundling.\n optimizeDeps: { exclude: ['tessera-learn'] },\n };\n },\n };\n}\n\n/** Fill runtime defaults into a parsed course.config.js. Exported for tests. */\nexport function mergeCourseConfig(userConfig: Partial<CourseConfig>) {\n const { completion, passingScore } = completionDefaults(\n userConfig.completion?.mode,\n );\n return {\n ...userConfig,\n title: userConfig.title || 'Untitled Course',\n resume: userConfig.resume ?? 'auto',\n navigation: { mode: 'free', ...userConfig.navigation },\n completion: { ...completion, ...userConfig.completion },\n scoring: { passingScore, ...userConfig.scoring },\n export: { standard: 'web', ...userConfig.export },\n };\n}\n\nfunction tesseraConfigPlugin(standardOverride?: string): Plugin {\n return virtualModule(\n 'tessera:config',\n VIRTUAL_CONFIG_ID,\n function ({ projectRoot }) {\n const configPath = resolve(projectRoot, 'course.config.js');\n if (existsSync(configPath)) this.addWatchFile(configPath);\n // The runtime reads export.standard too, so readResolvedConfig must apply\n // the override here — the bundled config, not just the manifest/adapter.\n const read = readResolvedConfig(projectRoot, standardOverride);\n const userConfig: Partial<CourseConfig> = read.ok ? read.config : {};\n return `export default ${JSON.stringify(mergeCourseConfig(userConfig))};`;\n },\n );\n}\n\n// ---------- Manifest Watch Helpers ----------\n\n/** Register all _meta.js and .svelte files under pagesDir as watch files for build mode. */\nfunction addWatchFiles(\n ctx: { addWatchFile(id: string): void },\n dir: string,\n): void {\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir)) {\n const full = resolve(dir, entry);\n if (statSync(full).isDirectory()) {\n addWatchFiles(ctx, full);\n } else if (entry.endsWith('.svelte') || entry === '_meta.js') {\n ctx.addWatchFile(full);\n }\n }\n}\n\n// ---------- Pages Plugin ----------\n\nconst VIRTUAL_PAGES_ID = 'virtual:tessera-pages';\n\n/**\n * Provides a virtual module that exports an import.meta.glob map for all .svelte\n * pages. This runs in the user's project context so the glob resolves against their\n * pages/ directory, and Vite can statically analyze it for code splitting.\n */\nfunction tesseraPagesPlugin(): Plugin {\n return virtualModule('tessera:pages', VIRTUAL_PAGES_ID, () => {\n return `export default import.meta.glob('/pages/**/*.svelte');`;\n });\n}\n\n// ---------- Validation Plugin ----------\n\nfunction tesseraValidationPlugin(standardOverride?: string): Plugin {\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:validation',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n // Run validation during dev (configResolved fires before server starts)\n if (!isBuild) {\n runValidation(projectRoot, standardOverride);\n }\n },\n\n buildStart() {\n // Run validation during build (buildStart fires once before bundling)\n if (isBuild) {\n runValidation(projectRoot, standardOverride);\n }\n },\n };\n}\n\n// Tier 1a: flush + gate the Svelte compiler's a11y warnings at buildEnd, after\n// every module is transformed. svelte() accepts `onwarn` but not arbitrary\n// Rollup hooks, so the gate lives here and shares the onwarn closure.\nfunction tesseraA11yCompilerPlugin(a11y: A11yCompilerState): Plugin {\n return {\n name: 'tessera:a11y-compiler',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n a11y.projectRoot = config.root;\n a11y.isBuild = config.command === 'build';\n const read = readCourseConfig(config.root);\n a11y.settings = normalizeA11y(read.ok ? read.config.a11y : undefined);\n },\n\n buildEnd() {\n if (!a11y.isBuild || a11y.warnings.length === 0) return;\n const ignored = new Set(a11y.settings.ignore);\n const warnings = a11y.warnings.filter((msg) => !isIgnored(msg, ignored));\n a11y.warnings = [];\n if (warnings.length === 0) return;\n if (a11y.settings.level === 'error') {\n reportValidationIssues({ errors: warnings, warnings: [] });\n throw new Error(\n `Tessera: ${warnings.length} a11y issue(s) with a11y.level: 'error'. Fix the errors above to continue.`,\n );\n }\n reportValidationIssues({ errors: [], warnings });\n },\n };\n}\n\nfunction runValidation(projectRoot: string, standardOverride?: string): void {\n const result = validateProject(projectRoot, standardOverride);\n reportValidationIssues(result);\n if (result.errors.length > 0) {\n throw new Error(\n `Tessera validation failed with ${result.errors.length} error(s). Fix the errors above to continue.`,\n );\n }\n}\n\n// ---------- Export Plugin ----------\n\nfunction tesseraExportPlugin(standardOverride?: string): Plugin {\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:export',\n enforce: 'post',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n\n async closeBundle() {\n if (!isBuild) return;\n if (isAuditBuild()) return;\n\n const read = readResolvedConfig(projectRoot, standardOverride);\n if (!read.ok) {\n // Validation already required a parseable course.config.js — getting\n // here means it vanished or broke mid-build. Surface that loudly\n // rather than shipping a bundle with no LMS export silently.\n if (read.reason === 'missing') {\n throw new Error(\n '[tessera:export] course.config.js not found at closeBundle. The file must exist for the export step to run.',\n );\n }\n if (read.reason === 'no-export') {\n throw new Error(\n '[tessera:export] course.config.js: could not locate `export default { ... }`. Cannot determine export.standard.',\n );\n }\n throw new Error(\n `[tessera:export] course.config.js: failed to parse export-default object literal — ${(read.error as Error).message}`,\n );\n }\n\n await runExport(\n projectRoot,\n read.config as Parameters<typeof runExport>[1],\n );\n },\n };\n}\n\n// ---------- Manifest Plugin ----------\n\nconst VIRTUAL_MANIFEST_ID = 'virtual:tessera-manifest';\nconst RESOLVED_MANIFEST_ID = '\\0' + VIRTUAL_MANIFEST_ID;\n\nfunction tesseraManifestPlugin(manifestRef: {\n current: Manifest | null;\n root: string;\n}): Plugin {\n let projectRoot: string;\n let pagesDir: string;\n\n function buildManifest(): Manifest {\n const m = generateManifest(pagesDir);\n manifestRef.current = m;\n return m;\n }\n\n return {\n name: 'tessera:manifest',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n pagesDir = resolve(projectRoot, 'pages');\n manifestRef.root = projectRoot;\n },\n\n configureServer(devServer: ViteDevServer) {\n // Watch the pages directory for changes\n devServer.watcher.on('all', (event, filePath) => {\n if (!filePath.startsWith(pagesDir)) return;\n\n // Rebuild manifest on relevant file changes\n const isRelevant =\n filePath.endsWith('.svelte') ||\n filePath.endsWith('_meta.js') ||\n event === 'addDir' ||\n event === 'unlinkDir';\n\n if (isRelevant) {\n manifestRef.current = null; // invalidate cache\n\n // Invalidate the virtual module to trigger HMR\n const mod = devServer.moduleGraph.getModuleById(RESOLVED_MANIFEST_ID);\n if (mod) {\n devServer.moduleGraph.invalidateModule(mod);\n devServer.ws.send({ type: 'full-reload' });\n }\n\n console.log(\n `[tessera] Manifest rebuilt (${event}: ${filePath.replace(projectRoot, '')})`,\n );\n }\n });\n },\n\n buildStart() {\n buildManifest();\n },\n\n resolveId(id) {\n if (id === VIRTUAL_MANIFEST_ID) return RESOLVED_MANIFEST_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_MANIFEST_ID) {\n if (!manifestRef.current) {\n buildManifest();\n }\n\n // Register watch files so Vite's built-in watcher (used in build --watch)\n // knows to re-trigger when pages/ content changes.\n addWatchFiles(this, pagesDir);\n\n // Encode as base64 to prevent Vite's import analysis from\n // scanning .svelte importPath strings as module imports.\n // Replace Infinity with 1e9 since JSON.stringify drops it.\n const json = JSON.stringify(manifestRef.current, (_key, value) =>\n value === Infinity ? 1e9 : value,\n );\n const b64 = Buffer.from(json).toString('base64');\n // atob yields Latin1 bytes; decode through UTF-8 or non-ASCII titles ship as mojibake.\n return `export default JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(\"${b64}\"),(c)=>c.charCodeAt(0))));`;\n }\n return null;\n },\n };\n}\n\nconst VIRTUAL_ADAPTER_ID = 'virtual:tessera-adapter';\n\n// `takesApi`: SCORM detectors return the API object the constructor needs;\n// cmi5/xAPI ones return a boolean.\nconst LMS_ADAPTER_GEN: Record<\n 'scorm12' | 'scorm2004' | 'cmi5' | 'xapi',\n { adapter: string; module: string; detect: string; takesApi: boolean }\n> = {\n scorm12: {\n adapter: 'SCORM12Adapter',\n module: 'scorm12',\n detect: 'findSCORM12API',\n takesApi: true,\n },\n scorm2004: {\n adapter: 'SCORM2004Adapter',\n module: 'scorm2004',\n detect: 'findSCORM2004API',\n takesApi: true,\n },\n cmi5: {\n adapter: 'CMI5Adapter',\n module: 'cmi5',\n detect: 'hasCMI5LaunchParams',\n takesApi: false,\n },\n xapi: {\n adapter: 'XAPIAdapter',\n module: 'xapi',\n detect: 'hasXAPILaunchParams',\n takesApi: false,\n },\n};\n\nfunction generateLmsAdapterModule(\n standard: keyof typeof LMS_ADAPTER_GEN,\n): string {\n const { adapter, module, detect, takesApi } = LMS_ADAPTER_GEN[standard];\n const guard = takesApi\n ? `const api = ${detect}();\\n if (!api) throw missingApiError('${standard}');\\n return new ${adapter}(api);`\n : `if (!${detect}()) throw missingApiError('${standard}');\\n return new ${adapter}();`;\n return `\nimport { ${adapter} } from 'tessera-learn/runtime/adapters/${module}.js';\nimport { ${detect} } from 'tessera-learn/runtime/adapters/discovery.js';\nimport { missingApiError } from 'tessera-learn/runtime/adapters/lms-error.js';\nexport function createAdapter() {\n ${guard}\n}\n`;\n}\n\nfunction tesseraAdapterPlugin(standardOverride?: string): Plugin {\n return virtualModule(\n 'tessera:adapter',\n VIRTUAL_ADAPTER_ID,\n ({ projectRoot, isBuild }) => {\n // In dev, defer to the runtime selector so its WebAdapter fallback\n // for unreachable LMS APIs keeps working.\n if (!isBuild) {\n return `export { createAdapter } from 'tessera-learn/runtime/adapters/index.js';`;\n }\n\n let standard = readResolvedConfig(projectRoot, standardOverride).standard;\n\n // The audit renders headless with no LMS in the frame chain; the SCORM/\n // cmi5 adapters throw when their API is absent, so render with WebAdapter.\n if (isAuditBuild()) standard = 'web';\n\n if (standard in LMS_ADAPTER_GEN) {\n return generateLmsAdapterModule(\n standard as keyof typeof LMS_ADAPTER_GEN,\n );\n }\n return `\nimport { WebAdapter } from 'tessera-learn/runtime/adapters/web.js';\nexport function createAdapter(config, options) {\n return new WebAdapter(config, options && options.manifest);\n}\n`;\n },\n );\n}\n\nconst VIRTUAL_XAPI_SETUP_ID = 'virtual:tessera-xapi-setup';\n\nfunction tesseraXAPISetupPlugin(standardOverride?: string): Plugin {\n return virtualModule(\n 'tessera:xapi-setup',\n VIRTUAL_XAPI_SETUP_ID,\n ({ projectRoot, isBuild }) => {\n if (!isBuild) {\n return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;\n }\n\n // The audit runs offline — don't wire real LRS destinations into it.\n if (isAuditBuild()) {\n return `export async function buildXAPIClient() { return null; }`;\n }\n\n const read = readResolvedConfig(projectRoot, standardOverride);\n const standard = read.standard;\n const hasXapi = read.ok && read.config.xapi != null;\n\n // The launch standards (cmi5, plain xAPI) own a publisher the runtime\n // can share for `endpoint: 'lms'`, so wire the client regardless of\n // explicit xapi config.\n if (hasXapi || standard === 'cmi5' || standard === 'xapi') {\n return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;\n }\n\n return `export async function buildXAPIClient() { return null; }`;\n },\n );\n}\n\nfunction tesseraFirstPagePreloadPlugin(manifestRef: {\n current: Manifest | null;\n root: string;\n}): Plugin {\n return {\n name: 'tessera:first-page-preload',\n apply: 'build',\n transformIndexHtml: {\n order: 'post',\n handler(_html, ctx) {\n const firstPagePath = manifestRef.current?.pages[0]?.importPath;\n if (!firstPagePath || !ctx.bundle) return;\n const normalized = resolve(\n manifestRef.root,\n firstPagePath.replace(/^\\//, ''),\n ).replace(/\\\\/g, '/');\n const chunk = Object.values(ctx.bundle).find(\n (c): c is import('vite').Rollup.OutputChunk =>\n c.type === 'chunk' &&\n !!c.facadeModuleId &&\n c.facadeModuleId.replace(/\\\\/g, '/') === normalized,\n );\n if (!chunk) return;\n return [\n {\n tag: 'link',\n attrs: { rel: 'modulepreload', href: `./${chunk.fileName}` },\n injectTo: 'head',\n },\n ];\n },\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tessera-learn",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "LMS tracking runtime for interactive learning content. One adapter layer (SCORM 1.2, SCORM 2004 4th Edition, cmi5, static Web), your choice of components.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -62,8 +62,7 @@
62
62
  function getOptionClass(optIndex) {
63
63
  if (!q.feedbackVisible) return '';
64
64
  if (isCorrectOption(optIndex)) return 'correct';
65
- if (optIndex === selectedOption && !isCorrectOption(optIndex))
66
- return 'incorrect';
65
+ if (optIndex === selectedOption) return 'incorrect';
67
66
  return '';
68
67
  }
69
68
  </script>
@@ -20,12 +20,15 @@ export async function runDev(
20
20
  export async function runBuild(
21
21
  projectRoot: string,
22
22
  workspaceRoot: string,
23
+ standardOverride?: string,
23
24
  ): Promise<number> {
24
25
  const vite = await import('vite');
25
- const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {
26
- command: 'build',
27
- mode: 'production',
28
- });
26
+ const config = await resolveTesseraConfig(
27
+ projectRoot,
28
+ workspaceRoot,
29
+ { command: 'build', mode: 'production' },
30
+ standardOverride,
31
+ );
29
32
  await vite.build(config);
30
33
  return 0;
31
34
  }