tessera-learn 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +63 -13
- package/README.md +4 -4
- package/dist/{audit-CzKAXy3Y.js → audit-BNrvFHq_.js} +66 -26
- package/dist/audit-BNrvFHq_.js.map +1 -0
- package/dist/{build-commands-D101M_qb.js → build-commands-BWnATKat.js} +6 -6
- package/dist/build-commands-BWnATKat.js.map +1 -0
- package/dist/{inline-config-DYHT51G8.js → inline-config-Dudu5r8w.js} +8 -6
- package/dist/inline-config-Dudu5r8w.js.map +1 -0
- package/dist/plugin/cli.d.ts +6 -2
- package/dist/plugin/cli.d.ts.map +1 -1
- package/dist/plugin/cli.js +242 -26
- package/dist/plugin/cli.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2 -2
- package/dist/{plugin-y35ym9A3.js → plugin-diNZaDJK.js} +4 -17
- package/dist/plugin-diNZaDJK.js.map +1 -0
- package/package.json +10 -1
- package/src/plugin/a11y/audit.ts +83 -22
- package/src/plugin/a11y-cli.ts +6 -2
- package/src/plugin/build-commands.ts +10 -4
- package/src/plugin/cli.ts +63 -20
- package/src/plugin/course-root.ts +98 -0
- package/src/plugin/duplicate-cli.ts +74 -0
- package/src/plugin/inline-config.ts +13 -2
- package/src/plugin/new-cli.ts +51 -0
- package/src/plugin/project-name.ts +29 -0
- package/src/plugin/template-copy.ts +43 -0
- package/src/plugin/validate-cli.ts +1 -1
- package/src/runtime/App.svelte +20 -1
- package/src/virtual.d.ts +6 -0
- package/templates/course/course.config.js +11 -0
- package/templates/course/layout.svelte +116 -0
- package/templates/course/pages/01-getting-started/01-welcome/_meta.js +1 -0
- package/templates/course/pages/01-getting-started/01-welcome/welcome.svelte +19 -0
- package/templates/course/pages/01-getting-started/_meta.js +1 -0
- package/templates/course/styles/custom.css +5 -0
- package/dist/audit-CzKAXy3Y.js.map +0 -1
- package/dist/build-commands-D101M_qb.js.map +0 -1
- package/dist/inline-config-DYHT51G8.js.map +0 -1
- package/dist/plugin-y35ym9A3.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-BNrvFHq_.js","names":[],"sources":["../src/plugin/ast.ts","../src/plugin/manifest.ts","../src/runtime/xapi/agent-rules.ts","../src/runtime/xapi/derive-actor.ts","../src/runtime/interaction-format.ts","../src/runtime/types.ts","../src/plugin/a11y/contrast.ts","../src/components/video-embed.ts","../src/plugin/validation.ts","../src/plugin/package-root.ts","../src/plugin/a11y/audit.ts"],"sourcesContent":["import { Parser } from 'acorn';\nimport { tsPlugin } from '@sveltejs/acorn-typescript';\nimport { parse } from 'svelte/compiler';\n\n/**\n * Shared parsing layer for the build-time validator and manifest generator.\n *\n * `.svelte` files go through `svelte/compiler`'s `parse`; plain JS files\n * (`course.config.js`, `_meta.js`) and the module-script fallback go through\n * acorn (with `acorn-typescript` for `as const` / `satisfies T`). Static\n * *values* are still recovered with JSON5 by the callers — only structure\n * parsing lives here.\n */\n\nexport type PropValue =\n | { kind: 'string'; value: string }\n | { kind: 'expr'; raw: string }\n | { kind: 'bool' };\n\nexport interface ComponentMatch {\n name: string;\n props: Map<string, PropValue>;\n hasSpread: boolean;\n}\n\nexport type NamedObjectLiteral =\n | { kind: 'none' }\n | { kind: 'invalid' }\n | { kind: 'literal'; text: string };\n\ninterface Node {\n type: string;\n start: number;\n end: number;\n [key: string]: unknown;\n}\n\ninterface CacheEntry {\n root: Node | null;\n error: string | null;\n}\n\nconst rootCache = new Map<string, CacheEntry>();\n\n/** Drop every cached root. Call at the start of a run to scope the cache. */\nexport function clearParseCache(): void {\n rootCache.clear();\n}\n\nfunction parseRoot(source: string): CacheEntry {\n const cached = rootCache.get(source);\n if (cached !== undefined) return cached;\n let entry: CacheEntry;\n try {\n entry = {\n root: parse(source, { modern: true }) as unknown as Node,\n error: null,\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const firstLine = message.split('\\n')[0].trim();\n entry = { root: null, error: firstLine || 'parse error' };\n }\n rootCache.set(source, entry);\n return entry;\n}\n\nfunction collectComponents(root: Node, names: ReadonlySet<string>): Node[] {\n const found: Node[] = [];\n const seen = new Set<object>();\n const walk = (value: unknown): void => {\n if (!value || typeof value !== 'object') return;\n if (seen.has(value)) return;\n seen.add(value);\n if (Array.isArray(value)) {\n for (const item of value) walk(item);\n return;\n }\n const node = value as Node;\n if (node.type === 'Component' && names.has(node.name as string)) {\n found.push(node);\n }\n for (const key of Object.keys(node)) {\n if (key === 'type') continue;\n walk(node[key]);\n }\n };\n walk(root);\n return found.sort((a, b) => a.start - b.start);\n}\n\nfunction readProps(source: string, node: Node): ComponentMatch {\n const props = new Map<string, PropValue>();\n let hasSpread = false;\n const attributes = (node.attributes as Node[]) ?? [];\n for (const attr of attributes) {\n if (attr.type === 'SpreadAttribute') {\n hasSpread = true;\n continue;\n }\n if (attr.type === 'BindDirective') {\n const expr = (attr as { expression?: Node }).expression;\n if (expr) {\n props.set(attr.name as string, {\n kind: 'expr',\n raw: source.slice(expr.start, expr.end).trim(),\n });\n }\n continue;\n }\n if (attr.type !== 'Attribute') continue;\n const name = attr.name as string;\n const value = attr.value;\n if (value === true) {\n props.set(name, { kind: 'bool' });\n } else if (Array.isArray(value)) {\n if (value.length === 0) {\n props.set(name, { kind: 'string', value: '' });\n } else {\n const first = value[0] as Node;\n const last = value[value.length - 1] as Node;\n props.set(name, {\n kind: 'string',\n value: source.slice(first.start, last.end),\n });\n }\n } else if (\n value &&\n typeof value === 'object' &&\n (value as Node).type === 'ExpressionTag'\n ) {\n const expr = (value as { expression: Node }).expression;\n props.set(name, {\n kind: 'expr',\n raw: source.slice(expr.start, expr.end).trim(),\n });\n if (source[attr.start] === '{') hasSpread = true;\n }\n }\n return { name: node.name as string, props, hasSpread };\n}\n\n/**\n * Return a one-line message if `source` is not valid Svelte, else null. Lets\n * the validator surface a real syntax error itself rather than only failing\n * later in the compiler (and the compile-less CLI would otherwise miss it).\n */\nexport function getParseError(source: string): string | null {\n return parseRoot(source).error;\n}\n\n/**\n * Find every question/media component in a `.svelte` source, anywhere in the\n * markup, with its props. Returns null if the source can't be parsed — callers\n * then skip component validation, matching the old \"skip when unsure\" stance.\n */\nexport function findComponents(\n source: string,\n names: ReadonlySet<string>,\n): ComponentMatch[] | null {\n const { root } = parseRoot(source);\n if (!root) return null;\n return collectComponents(root, names).map((node) => readProps(source, node));\n}\n\nconst TsParser = Parser.extend(\n tsPlugin() as unknown as Parameters<typeof Parser.extend>[0],\n);\n\nfunction parseJsModule(source: string): Node | null {\n try {\n return TsParser.parse(source, {\n ecmaVersion: 'latest',\n sourceType: 'module',\n }) as unknown as Node;\n } catch {\n return null;\n }\n}\n\nfunction unwrapTsCast(node: Node | null): Node | null {\n let current = node;\n while (\n current &&\n (current.type === 'TSAsExpression' ||\n current.type === 'TSSatisfiesExpression' ||\n current.type === 'TSTypeAssertion' ||\n current.type === 'TSNonNullExpression')\n ) {\n current = (current as { expression?: Node }).expression ?? null;\n }\n return current;\n}\n\nfunction findPageConfigInProgram(\n program: Node,\n source: string,\n): NamedObjectLiteral {\n const body = (program.body as Node[]) ?? [];\n for (const node of body) {\n if (node.type !== 'ExportNamedDeclaration') continue;\n const declaration = node.declaration as Node | null;\n if (!declaration || declaration.type !== 'VariableDeclaration') continue;\n for (const decl of declaration.declarations as Node[]) {\n const id = decl.id as Node;\n if (id.type !== 'Identifier' || id.name !== 'pageConfig') continue;\n const init = unwrapTsCast(decl.init as Node | null);\n if (init && init.type === 'ObjectExpression') {\n return { kind: 'literal', text: source.slice(init.start, init.end) };\n }\n return { kind: 'invalid' };\n }\n }\n return { kind: 'none' };\n}\n\n/**\n * Locate the `export default { ... }` object literal in a plain JS source.\n * Returns a discriminated result so callers can tell parse failure from a\n * missing or non-literal default export.\n */\nexport function defaultExportObjectLiteral(\n jsSource: string,\n): NamedObjectLiteral | { kind: 'parse-error' } {\n const program = parseJsModule(jsSource);\n if (!program) return { kind: 'parse-error' };\n for (const node of (program.body as Node[]) ?? []) {\n if (node.type !== 'ExportDefaultDeclaration') continue;\n const decl = unwrapTsCast(\n (node as { declaration?: Node }).declaration ?? null,\n );\n if (decl && decl.type === 'ObjectExpression') {\n return { kind: 'literal', text: jsSource.slice(decl.start, decl.end) };\n }\n return { kind: 'invalid' };\n }\n return { kind: 'none' };\n}\n\nconst MODULE_SCRIPT_OPEN_RE =\n /<script\\s+(?:context\\s*=\\s*[\"']module[\"']|module)[^>]*>/;\nconst SCRIPT_CLOSE = '</script>';\n\nfunction pageConfigFromModuleScriptFallback(\n svelteSource: string,\n): NamedObjectLiteral {\n const open = svelteSource.match(MODULE_SCRIPT_OPEN_RE);\n if (!open || open.index === undefined) return { kind: 'none' };\n const bodyStart = open.index + open[0].length;\n // Try every `</script>` candidate from earliest; the first one whose body\n // parses as JS is the real close (an earlier hit is inside a string literal).\n let from = bodyStart;\n while (true) {\n const closeIdx = svelteSource.indexOf(SCRIPT_CLOSE, from);\n if (closeIdx < 0) return { kind: 'none' };\n const body = svelteSource.slice(bodyStart, closeIdx);\n const program = parseJsModule(body);\n if (program) return findPageConfigInProgram(program, body);\n from = closeIdx + SCRIPT_CLOSE.length;\n }\n}\n\n/**\n * Locate `export const pageConfig = { ... }` in a Svelte page's module script\n * and return the object-literal text. Walks the page-level AST so TypeScript\n * (`lang=\"ts\"`) module scripts are handled by Svelte's own parser.\n */\nexport function pageConfigLiteral(svelteSource: string): NamedObjectLiteral {\n const { root } = parseRoot(svelteSource);\n if (root) {\n const program = (root.module as { content?: Node } | null)?.content;\n if (!program) return { kind: 'none' };\n return findPageConfigInProgram(program, svelteSource);\n }\n return pageConfigFromModuleScriptFallback(svelteSource);\n}\n","import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';\nimport { resolve, basename, extname } from 'node:path';\nimport JSON5 from 'json5';\nimport {\n clearParseCache,\n defaultExportObjectLiteral,\n pageConfigLiteral,\n} from './ast.js';\nimport type { CourseConfig, QuizConfig } from '../runtime/types.js';\n\n// ---------- Types ----------\n\nexport type { QuizConfig };\n\nexport interface ManifestPage {\n index: number;\n title: string;\n slug: string;\n importPath: string;\n quiz: QuizConfig | null;\n completesOn?: 'view';\n}\n\nexport interface ManifestLesson {\n title: string;\n slug: string;\n pages: ManifestPage[];\n}\n\nexport interface ManifestSection {\n title: string;\n slug: string;\n lessons: ManifestLesson[];\n}\n\nexport interface Manifest {\n sections: ManifestSection[];\n pages: ManifestPage[];\n totalPages: number;\n}\n\n/** Append `.svelte` if not already present. Both bare and suffixed names are accepted in author config. */\nexport function ensureSvelteSuffix(name: string): string {\n return name.endsWith('.svelte') ? name : `${name}.svelte`;\n}\n\n// ---------- File read cache ----------\n\n/**\n * Module-level cache of source file contents keyed by absolute path with\n * mtime invalidation. Both `validateProject` and `generateManifest` read the\n * same .svelte / _meta.js / course.config.js files during a single build;\n * sharing the read avoids the second disk hit (and matters most on cold-cache\n * CI runs and large courses).\n */\nconst fileContentCache = new Map<\n string,\n { mtimeMs: number; content: string }\n>();\n\nexport function readSourceFileCached(filePath: string): string {\n const stat = statSync(filePath);\n const cached = fileContentCache.get(filePath);\n if (cached && cached.mtimeMs === stat.mtimeMs) return cached.content;\n const content = readFileSync(filePath, 'utf-8');\n fileContentCache.set(filePath, { mtimeMs: stat.mtimeMs, content });\n return content;\n}\n\n// ---------- Helpers ----------\n\n/** Strip numeric prefix and hyphen: \"01-introduction\" → \"introduction\" */\nexport function stripPrefix(name: string): string {\n return name.replace(/^\\d+-/, '');\n}\n\n/** Title-case a slug: \"getting-started\" → \"Getting Started\" */\nexport function titleCase(slug: string): string {\n return slug\n .split('-')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n/** Derive slug from folder/file name */\nexport function deriveSlug(name: string, isFile = false): string {\n if (isFile) {\n return basename(name, extname(name));\n }\n return stripPrefix(name);\n}\n\nexport type DefaultExportLiteralResult =\n | { kind: 'literal'; text: string }\n | { kind: 'none' }\n | { kind: 'invalid' }\n | { kind: 'parse-error' };\n\n/**\n * Locate `export default { ... }` and return its object-literal text. Returns\n * a discriminated result so callers can tell parse failure from a missing or\n * non-literal default export. Used by both manifest extraction and project\n * validation.\n */\nexport function extractDefaultExportObjectLiteral(\n source: string,\n): DefaultExportLiteralResult {\n return defaultExportObjectLiteral(source);\n}\n\nexport type CourseConfigRead =\n | { ok: true; config: Partial<CourseConfig> }\n | {\n ok: false;\n reason: 'missing' | 'no-export' | 'parse-error';\n error?: unknown;\n };\n\n/**\n * Read and JSON5-parse the `export default { ... }` literal from a project's\n * course.config.js. Shared by the build plugin and the validator so the read,\n * cache, and parse rules live in one place. The discriminated `reason` lets\n * callers that care (export, validation) emit precise errors while callers\n * that just need a value can fall back on `!ok`.\n */\nexport function readCourseConfig(projectRoot: string): CourseConfigRead {\n const configPath = resolve(projectRoot, 'course.config.js');\n if (!existsSync(configPath)) return { ok: false, reason: 'missing' };\n const result = extractDefaultExportObjectLiteral(\n readSourceFileCached(configPath),\n );\n if (result.kind === 'parse-error')\n return { ok: false, reason: 'parse-error' };\n if (result.kind !== 'literal') return { ok: false, reason: 'no-export' };\n try {\n return { ok: true, config: JSON5.parse(result.text) };\n } catch (error) {\n return { ok: false, reason: 'parse-error', error };\n }\n}\n\n/**\n * Read a _meta.js file and extract its default export object.\n * Uses the same JSON5 approach as pageConfig extraction — find the object literal\n * after `export default` and parse it.\n */\nexport function readMetaFile(metaPath: string): {\n title?: string;\n pages?: string[];\n} {\n if (!existsSync(metaPath)) return {};\n\n const result = extractDefaultExportObjectLiteral(\n readSourceFileCached(metaPath),\n );\n if (result.kind !== 'literal') return {};\n\n try {\n return JSON5.parse(result.text);\n } catch {\n return {};\n }\n}\n\n/** Result of parsing a `.svelte` source for its `pageConfig` module-script export. */\nexport type PageConfigParseResult =\n /** No module script, or no `pageConfig =` export. Treat as \"no config\". */\n | { kind: 'none' }\n /** Found and successfully parsed. */\n | {\n kind: 'ok';\n value: { title?: string; quiz?: QuizConfig; completesOn?: 'view' };\n }\n /** Found but couldn't parse as a static object literal — non-literal RHS or JSON5 failure. */\n | { kind: 'invalid' };\n\n/** Source-level pageConfig extraction shared by manifest generation and build-time validation. */\nexport function parsePageConfigFromSource(\n content: string,\n): PageConfigParseResult {\n const literal = pageConfigLiteral(content);\n if (literal.kind === 'none') return { kind: 'none' };\n if (literal.kind === 'invalid') return { kind: 'invalid' };\n\n try {\n return { kind: 'ok', value: JSON5.parse(literal.text) };\n } catch {\n return { kind: 'invalid' };\n }\n}\n\n/** Extract pageConfig from a .svelte file. Throws on parse failure. */\nexport function extractPageConfig(filePath: string): {\n title?: string;\n quiz?: QuizConfig;\n completesOn?: 'view';\n} {\n const result = parsePageConfigFromSource(readSourceFileCached(filePath));\n if (result.kind === 'ok') return result.value;\n if (result.kind === 'invalid') {\n throw new Error(\n `${filePath}: pageConfig must be a static object literal (no variables, function calls, or computed values)`,\n );\n }\n return {};\n}\n\n/**\n * Get sorted subdirectories of a given path.\n */\nfunction getSortedDirs(dirPath: string): string[] {\n if (!existsSync(dirPath)) return [];\n return readdirSync(dirPath)\n .filter((name) => {\n const full = resolve(dirPath, name);\n return statSync(full).isDirectory() && !name.startsWith('.');\n })\n .sort();\n}\n\n/**\n * Get .svelte files in a directory.\n */\nfunction getSvelteFiles(dirPath: string): string[] {\n if (!existsSync(dirPath)) return [];\n return readdirSync(dirPath)\n .filter((name) => name.endsWith('.svelte'))\n .sort();\n}\n\n// ---------- Course structure walker ----------\n\nexport interface WalkedLesson {\n /** Lesson directory name, or null for a section's implicit flat lesson. */\n name: string | null;\n /** Directory holding the `.svelte` files (the section dir for a flat lesson). */\n dir: string;\n /** `_meta.js` path governing this lesson's title and page order. */\n metaPath: string;\n /** Sorted raw `.svelte` filenames in `dir` (pre-ordering). */\n files: string[];\n}\n\nexport interface WalkedSection {\n name: string;\n dir: string;\n metaPath: string;\n lessons: WalkedLesson[];\n}\n\n/**\n * Enumerate the course's section → lesson → file structure. Section-level\n * `.svelte` files become an implicit flat lesson (`name: null`) ordered before\n * the section's explicit lesson directories. Shared by manifest generation and\n * build-time validation so the two never disagree on which files are pages.\n */\nexport function walkPages(pagesDir: string): WalkedSection[] {\n const sections: WalkedSection[] = [];\n for (const sectionName of getSortedDirs(pagesDir)) {\n const dir = resolve(pagesDir, sectionName);\n const metaPath = resolve(dir, '_meta.js');\n const lessons: WalkedLesson[] = [];\n\n const flatFiles = getSvelteFiles(dir);\n if (flatFiles.length > 0) {\n lessons.push({ name: null, dir, metaPath, files: flatFiles });\n }\n\n for (const lessonName of getSortedDirs(dir)) {\n const lessonDir = resolve(dir, lessonName);\n lessons.push({\n name: lessonName,\n dir: lessonDir,\n metaPath: resolve(lessonDir, '_meta.js'),\n files: getSvelteFiles(lessonDir),\n });\n }\n\n sections.push({ name: sectionName, dir, metaPath, lessons });\n }\n return sections;\n}\n\n// ---------- Main ----------\n\n/**\n * Generate a course manifest by scanning the pages/ directory.\n */\nexport function generateManifest(pagesDir: string): Manifest {\n clearParseCache();\n const sections: ManifestSection[] = [];\n const flatPages: ManifestPage[] = [];\n let pageIndex = 0;\n\n for (const walkedSection of walkPages(pagesDir)) {\n const sectionMeta = readMetaFile(walkedSection.metaPath);\n const sectionSlug = deriveSlug(walkedSection.name);\n\n const section: ManifestSection = {\n title: sectionMeta.title || titleCase(sectionSlug),\n slug: sectionSlug,\n lessons: [],\n };\n\n for (const walkedLesson of walkedSection.lessons) {\n // The flat lesson uses the section _meta for ordering and has no title —\n // the sidebar renders its pages without a lesson header.\n const isFlat = walkedLesson.name === null;\n const lessonMeta = isFlat\n ? sectionMeta\n : readMetaFile(walkedLesson.metaPath);\n const lessonSlug = isFlat ? sectionSlug : deriveSlug(walkedLesson.name!);\n const relDir = isFlat\n ? `/pages/${walkedSection.name}`\n : `/pages/${walkedSection.name}/${walkedLesson.name}`;\n\n const lesson: ManifestLesson = {\n title: isFlat ? '' : lessonMeta.title || titleCase(lessonSlug),\n slug: lessonSlug,\n pages: [],\n };\n\n for (const fileName of orderPageFiles(\n walkedLesson.files,\n lessonMeta.pages,\n )) {\n const filePath = resolve(walkedLesson.dir, fileName);\n const pageSlug = deriveSlug(fileName, true);\n\n let pageConfig: {\n title?: string;\n quiz?: QuizConfig;\n completesOn?: 'view';\n } = {};\n try {\n pageConfig = extractPageConfig(filePath);\n } catch (e) {\n // Validation errors will be handled by the validation plugin (Step 11).\n // For now, log and continue with defaults.\n console.warn(`[tessera warning] ${(e as Error).message}`);\n }\n\n const page: ManifestPage = {\n index: pageIndex,\n title: pageConfig.title || titleCase(pageSlug),\n slug: pageSlug,\n importPath: `${relDir}/${fileName}`,\n quiz: pageConfig.quiz || null,\n ...(pageConfig.completesOn === 'view'\n ? { completesOn: 'view' as const }\n : {}),\n };\n\n lesson.pages.push(page);\n flatPages.push(page);\n pageIndex++;\n }\n\n section.lessons.push(lesson);\n }\n\n sections.push(section);\n }\n\n return {\n sections,\n pages: flatPages,\n totalPages: flatPages.length,\n };\n}\n\n/**\n * Order .svelte files: listed in `pages` array first (in order), then unlisted appended alphabetically.\n */\nexport function orderPageFiles(\n allFiles: string[],\n pagesArray?: string[],\n): string[] {\n if (!pagesArray || pagesArray.length === 0) {\n return allFiles;\n }\n\n const listed = pagesArray.map(ensureSvelteSuffix);\n const listedSet = new Set(listed);\n const unlisted = allFiles.filter((f) => !listedSet.has(f)).sort();\n\n // Only include listed files that actually exist\n const validListed = listed.filter((f) => allFiles.includes(f));\n\n return [...validListed, ...unlisted];\n}\n","/**\n * xAPI Identified Agent and Basic-auth credential validation rules.\n *\n * Pure logic — no Svelte/runtime imports. Imported by both `publisher.ts`\n * (runtime validation of resolved actor / auth) and `plugin/validation.ts`\n * (build-time validation of static `course.config.js` actor / auth).\n * Keeping the rules in one place prevents the two callsites from drifting.\n */\n\n/** Join a field label with a validator suffix: `.foo` chains, others get `: `. */\nexport function joinFieldError(label: string, suffix: string): string {\n return suffix.startsWith('.') ? `${label}${suffix}` : `${label}: ${suffix}`;\n}\n\n/**\n * Validate that a candidate is an Identified Agent per xAPI 1.0.3.\n * Returns null on success or a human-readable error suffix on failure.\n *\n * Suffixes are prefix-friendly: callers concatenate their own label\n * (`xapi.actor`, `xapi[0].actor`, etc.) with a single space — no \"actor\"\n * appears in the suffix to avoid doubling.\n */\nexport function validateAgent(actor: unknown): string | null {\n if (!actor || typeof actor !== 'object') {\n return 'must be an object';\n }\n const a = actor as Record<string, unknown>;\n if (Array.isArray(a.member) && a.member.length > 0) {\n return 'is a Group (has `member`); v1 supports Identified Agents only';\n }\n let count = 0;\n if (a.mbox !== undefined) count++;\n if (a.mbox_sha1sum !== undefined) count++;\n if (a.openid !== undefined) count++;\n if (a.account !== undefined) count++;\n if (count === 0) {\n return 'must have one of mbox, mbox_sha1sum, openid, or account (Identified Agent rule)';\n }\n if (count > 1) {\n return 'must have exactly one IFI (mbox / mbox_sha1sum / openid / account), not multiple';\n }\n if (a.mbox !== undefined) {\n if (typeof a.mbox !== 'string' || !a.mbox.startsWith('mailto:')) {\n return '.mbox must be a string starting with \"mailto:\"';\n }\n }\n if (a.mbox_sha1sum !== undefined) {\n if (\n typeof a.mbox_sha1sum !== 'string' ||\n !/^[0-9a-f]{40}$/i.test(a.mbox_sha1sum)\n ) {\n return '.mbox_sha1sum must be a 40-character hex string';\n }\n }\n if (a.openid !== undefined) {\n if (typeof a.openid !== 'string' || !a.openid) {\n return '.openid must be a non-empty string';\n }\n try {\n new URL(a.openid);\n } catch {\n return '.openid must be an absolute URI';\n }\n }\n if (a.account !== undefined) {\n const acc = a.account as Record<string, unknown>;\n if (!acc || typeof acc !== 'object') {\n return '.account must be an object with homePage and name';\n }\n if (typeof acc.homePage !== 'string' || !acc.homePage) {\n return '.account.homePage must be a non-empty string';\n }\n try {\n new URL(acc.homePage);\n } catch {\n return '.account.homePage must be an absolute URL';\n }\n if (typeof acc.name !== 'string' || !acc.name) {\n return '.account.name must be a non-empty string';\n }\n }\n return null;\n}\n\n/**\n * Validate a Basic-auth credential string (the value after \"Basic \").\n * v1 supports Basic only. Bearer is a hard error so OAuth users see the\n * non-goal explicitly.\n */\nexport function validateAuthCredential(auth: string): string | null {\n if (typeof auth !== 'string' || !auth) {\n return 'must be a non-empty string';\n }\n if (/^basic\\s/i.test(auth)) {\n return \"must be the Basic credential value only, not the full header. Drop the 'Basic ' prefix.\";\n }\n if (/^bearer\\s/i.test(auth)) {\n return 'Bearer/OAuth credentials are not supported in v1. Use Basic auth, or wrap your token-exchange in an auth function that returns a Basic credential.';\n }\n return null;\n}\n","import type { XAPIAgent } from './types.js';\nimport type { SCORM12API } from '../adapters/scorm12.js';\nimport type { SCORM2004API } from '../adapters/scorm2004.js';\n\n/**\n * Origin of an http(s) URL, else null. Shared with the config validator, which\n * predicts this result to know when `actorAccountHomePage` becomes required —\n * one helper keeps the two in lockstep.\n */\nexport function httpOrigin(url: string): string | null {\n try {\n const parsed = new URL(url);\n return parsed.protocol === 'http:' || parsed.protocol === 'https:'\n ? parsed.origin\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Synthesize an Identified Agent from the SCORM learner fields.\n *\n * { account: { homePage, name: <id> }, name: <name>, objectType: 'Agent' }\n *\n * The `account` IFI satisfies xAPI's Identified Agent rule. `homePage`\n * defaults to the activityId origin so analytics keyed on actor identity stay\n * stable across LMS hosts; the author's `actorAccountHomePage` overrides when\n * the authority namespace is elsewhere. Returns null if the id is missing —\n * the caller should not construct a publisher (the LRS would 400 every send).\n */\nfunction synthesizeActor(\n readId: () => string,\n readName: () => string,\n activityId: string,\n actorAccountHomePage?: string,\n): XAPIAgent | null {\n let id = '';\n let name = '';\n try {\n id = readId() || '';\n } catch {}\n try {\n name = readName() || '';\n } catch {}\n if (!id) return null;\n const homePage = actorAccountHomePage ?? httpOrigin(activityId);\n if (!homePage) return null;\n const agent: XAPIAgent = {\n account: { homePage, name: id },\n objectType: 'Agent',\n };\n if (name) agent.name = name;\n return agent;\n}\n\n/** SCORM 1.2 actor from `cmi.core.student_id` / `cmi.core.student_name`. */\nexport function synthesizeSCORM12Actor(\n api: SCORM12API,\n activityId: string,\n actorAccountHomePage?: string,\n): XAPIAgent | null {\n return synthesizeActor(\n () => api.LMSGetValue('cmi.core.student_id'),\n () => api.LMSGetValue('cmi.core.student_name'),\n activityId,\n actorAccountHomePage,\n );\n}\n\n/** SCORM 2004 actor from `cmi.learner_id` / `cmi.learner_name` (renamed 2004 fields). */\nexport function synthesizeSCORM2004Actor(\n api: SCORM2004API,\n activityId: string,\n actorAccountHomePage?: string,\n): XAPIAgent | null {\n return synthesizeActor(\n () => api.GetValue('cmi.learner_id'),\n () => api.GetValue('cmi.learner_name'),\n activityId,\n actorAccountHomePage,\n );\n}\n","/**\n * SCORM 1.2 RTE §3.4.7 vs SCORM 2004 4E RTE §4.2.7 differ in delimiter\n * encoding and identifier rules; cmi5 (xAPI) reuses 2004's delimiters but\n * not its identifier slugging.\n */\n\nimport type { Interaction } from './interaction.js';\n\nexport interface InteractionFormat {\n itemDelim: string;\n pairDelim: string;\n rangeDelim: string;\n /**\n * SCORM 1.2 has no numeric range syntax — `correct_responses.n.pattern`\n * is a single CMIDecimal. SCORM 2004 supports `min[:]max`.\n */\n supportsNumericRange: boolean;\n formatBoolean(value: boolean): string;\n identifier(value: string): string;\n}\n\nexport const SCORM12_INTERACTION_FORMAT: InteractionFormat = {\n itemDelim: ',',\n pairDelim: '.',\n rangeDelim: ':',\n supportsNumericRange: false,\n formatBoolean: (v) => (v ? 't' : 'f'),\n identifier: shortIdentifier,\n};\n\n/**\n * Bracketed delimiters are literal text, not regex. xAPI parses them the\n * same way.\n */\nexport const SCORM2004_INTERACTION_FORMAT: InteractionFormat = {\n itemDelim: '[,]',\n pairDelim: '[.]',\n rangeDelim: '[:]',\n supportsNumericRange: true,\n formatBoolean: (v) => (v ? 'true' : 'false'),\n identifier: (v) => v,\n};\n\n// xAPI reuses SCORM 2004's delimiters, numeric-range support, and identity\n// identifier verbatim, so it's the same format object.\nexport const XAPI_INTERACTION_FORMAT = SCORM2004_INTERACTION_FORMAT;\n\n/**\n * SCORM `short_identifier_type` / `CMIIdentifier`: alphanumerics +\n * underscore, max 250 chars. Strict validators (SCORM Cloud) reject raw\n * option labels with spaces or punctuation with error 405/406.\n */\nexport function shortIdentifier(value: string): string {\n const cleaned = value.replace(/[^A-Za-z0-9]+/g, '_').replace(/^_+|_+$/g, '');\n const trimmed = cleaned.slice(0, 250);\n return trimmed || '_';\n}\n\nfunction indexLookup(\n options: string[] | undefined,\n value: string,\n): string | null {\n if (!options) return null;\n const idx = options.indexOf(value);\n return idx >= 0 ? String(idx) : null;\n}\n\nfunction encodeListItem(\n value: string,\n options: string[] | undefined,\n fmt: InteractionFormat,\n): string {\n if (fmt === SCORM12_INTERACTION_FORMAT) {\n const idx = indexLookup(options, value);\n if (idx !== null) return idx;\n }\n return fmt.identifier(value);\n}\n\nexport function formatResponse(\n i: Interaction,\n fmt: InteractionFormat = SCORM2004_INTERACTION_FORMAT,\n): string {\n switch (i.type) {\n case 'choice':\n case 'sequencing':\n return i.response\n .map((v) => encodeListItem(v, i.options, fmt))\n .join(fmt.itemDelim);\n case 'true-false':\n return fmt.formatBoolean(i.response);\n case 'fill-in':\n case 'long-fill-in':\n case 'likert':\n case 'other':\n return i.response;\n case 'matching':\n return i.response\n .map(\n ([l, r]) =>\n `${encodeListItem(l, i.optionPairs?.left, fmt)}${fmt.pairDelim}${encodeListItem(r, i.optionPairs?.right, fmt)}`,\n )\n .join(fmt.itemDelim);\n case 'numeric':\n return String(i.response);\n case 'performance':\n return i.response\n .map(\n ([s, v]) =>\n `${fmt.identifier(s)}${fmt.pairDelim}${fmt.identifier(String(v))}`,\n )\n .join(fmt.itemDelim);\n }\n}\n\n/** Returns null when no correct pattern was provided. */\nexport function formatCorrectPattern(\n i: Interaction,\n fmt: InteractionFormat = SCORM2004_INTERACTION_FORMAT,\n): string | null {\n if (i.correct === undefined) return null;\n switch (i.type) {\n case 'choice':\n case 'sequencing':\n return (i.correct as string[])\n .map((v) => encodeListItem(v, i.options, fmt))\n .join(fmt.itemDelim);\n case 'true-false':\n return fmt.formatBoolean(i.correct as boolean);\n case 'fill-in':\n case 'long-fill-in':\n return (i.correct as string[]).join(fmt.itemDelim);\n case 'matching':\n return (i.correct as Array<[string, string]>)\n .map(\n ([l, r]) =>\n `${encodeListItem(l, i.optionPairs?.left, fmt)}${fmt.pairDelim}${encodeListItem(r, i.optionPairs?.right, fmt)}`,\n )\n .join(fmt.itemDelim);\n case 'numeric': {\n const c = i.correct as { min?: number; max?: number };\n if (c.min !== undefined && c.max !== undefined && c.min === c.max) {\n return String(c.min);\n }\n if (c.min !== undefined && c.max === undefined) return String(c.min);\n if (c.min === undefined && c.max !== undefined) return String(c.max);\n // True range — drop the pattern in 1.2 (rely on `result` for pass/fail).\n if (!fmt.supportsNumericRange) return null;\n return `${c.min ?? ''}${fmt.rangeDelim}${c.max ?? ''}`;\n }\n case 'likert':\n case 'other':\n return i.correct as string;\n case 'performance':\n return (i.correct as Array<[string, string | number]>)\n .map(\n ([s, v]) =>\n `${fmt.identifier(s)}${fmt.pairDelim}${fmt.identifier(String(v))}`,\n )\n .join(fmt.itemDelim);\n }\n}\n\n/** SCORM 1.2 has no `long-fill-in` or `other` — both fall back to `fill-in`. */\nexport function scorm12Type(type: Interaction['type']): string {\n switch (type) {\n case 'long-fill-in':\n return 'fill-in';\n case 'other':\n return 'fill-in';\n default:\n return type;\n }\n}\n\nexport interface ScormInteractionSpec {\n responseField: 'student_response' | 'learner_response';\n timestampField: 'time' | 'timestamp';\n timestamp: string;\n typeValue: string;\n resultLabels: { correct: string; incorrect: string };\n format: InteractionFormat;\n}\n\nexport function buildScormInteractionFields(\n prefix: string,\n questionId: string,\n interaction: Interaction,\n correct: boolean | null,\n spec: ScormInteractionSpec,\n): Array<[string, string]> {\n const fields: Array<[string, string]> = [\n [`${prefix}.id`, spec.format.identifier(questionId)],\n [`${prefix}.type`, spec.typeValue],\n ];\n const pattern = formatCorrectPattern(interaction, spec.format);\n if (pattern !== null) {\n fields.push([`${prefix}.correct_responses.0.pattern`, pattern]);\n }\n fields.push([\n `${prefix}.${spec.responseField}`,\n formatResponse(interaction, spec.format),\n ]);\n if (correct !== null) {\n fields.push([\n `${prefix}.result`,\n correct ? spec.resultLabels.correct : spec.resultLabels.incorrect,\n ]);\n }\n fields.push([`${prefix}.${spec.timestampField}`, spec.timestamp]);\n return fields;\n}\n","import type { AccessFn } from './access.js';\nimport type { XAPIAgent } from './xapi/types.js';\n\n/**\n * Quiz enum domains as runtime tuples. The unions below derive from these, and\n * the build-time validator imports them too — so the accepted value set has a\n * single source and can't drift between the types and the validator.\n */\nexport const FEEDBACK_MODES = ['review', 'immediate', 'never'] as const;\nexport const RETRY_MODES = ['full', 'incorrect-only'] as const;\n\n/**\n * Per-page quiz configuration. Single source of truth — the build plugin\n * extracts this from `pageConfig.quiz` and embeds it in the manifest;\n * the runtime reads it from there. Keep field shapes in sync.\n */\nexport interface QuizConfig {\n graded?: boolean;\n gatesProgress?: boolean;\n maxAttempts?: number;\n feedbackMode?: (typeof FEEDBACK_MODES)[number];\n retryMode?: (typeof RETRY_MODES)[number];\n}\n\nexport interface CourseConfig {\n title: string;\n description?: string;\n author?: string;\n version?: string;\n /** BCP-47 language tag for <html lang>. Defaults to 'en'. WCAG 3.1.1. */\n language?: string;\n /** Accessibility checker configuration. */\n a11y?: A11yConfig;\n branding?: {\n logo?: string;\n primaryColor?: string;\n fontFamily?: string;\n };\n navigation: {\n mode: 'free' | 'sequential';\n canAccess?: AccessFn;\n };\n completion: ManualCompletion | QuizCompletion | PercentageCompletion;\n /** Optional under \"manual\"; required under \"quiz\". */\n scoring: {\n passingScore: number;\n };\n export: {\n standard: 'web' | 'scorm12' | 'scorm2004' | 'cmi5';\n };\n /**\n * Optional xAPI destination(s) for custom statement publishing via\n * `useXAPI()`. A single object or an array of destinations. Under cmi5\n * export, the sentinel `endpoint: 'lms'` re-uses the LMS launch's\n * credentials and shares the cmi5 adapter's queue.\n */\n xapi?: XAPIConfig | XAPIConfig[];\n}\n\n/** Accessibility checker configuration. */\nexport interface A11yConfig {\n /** Build-gate severity for promotable Tier-1 rules + Tier-1a warnings. */\n level?: 'warn' | 'error';\n /** axe ruleset tags for the Tier-2 runtime auditor. */\n standard?: 'wcag2a' | 'wcag2aa' | 'wcag21aa';\n /** Per-rule escape hatch matched literally against each diagnostic's ID. */\n ignore?: string[];\n}\n\nexport interface ManualCompletion {\n mode: 'manual';\n /**\n * Set to \"page\" to opt into a build-time check that at least one page\n * declares `completesOn: \"view\"`. Omit to skip the check; both completion\n * paths still work at runtime.\n */\n trigger?: 'page';\n /** When set, markComplete() also flips successStatus. Omit for unknown. */\n requireSuccessStatus?: 'passed' | 'failed';\n}\n\nexport interface QuizCompletion {\n mode: 'quiz';\n}\n\nexport interface PercentageCompletion {\n mode: 'percentage';\n percentageThreshold?: number;\n}\n\n/**\n * cmi5 launch-inherited destination. Only valid under `export.standard:\n * 'cmi5'`. Auth, actor, activityId, and registration are taken from the\n * launch URL, so no other fields are accepted.\n */\nexport interface XAPILMSConfig {\n endpoint: 'lms';\n}\n\n/**\n * Explicit LRS destination. The author provides every field. `actor` is\n * optional under SCORM (synthesized from `cmi.core.student_id` /\n * `cmi.learner_id`) and required under web.\n */\nexport interface XAPIExplicitConfig {\n /** Absolute http(s) URL of the LRS Statements endpoint base. */\n endpoint: string;\n /**\n * Basic-auth credential value (the part after \"Basic \"), or a function\n * that resolves one. Function form is re-invoked once on 401 to cover\n * short-lived tokens.\n */\n auth: string | (() => string | Promise<string>);\n /**\n * Identified Agent or a resolver function. Required for web export;\n * optional under SCORM where it can be synthesized from the LMS data\n * model. Optional under cmi5 where it can be inherited from the launch.\n */\n actor?: XAPIAgent | (() => XAPIAgent | Promise<XAPIAgent>);\n /** xAPI activity IRI scoped to this destination. */\n activityId: string;\n /** Optional UUID v4 — primarily a cmi5 launch concept. */\n registration?: string;\n /**\n * Override for the SCORM-derived actor's `account.homePage`. Defaults\n * to the activityId origin when activityId is http(s); required when\n * activityId uses a non-http(s) scheme.\n */\n actorAccountHomePage?: string;\n}\n\nexport type XAPIConfig = XAPILMSConfig | XAPIExplicitConfig;\n","/**\n * Pure-JS WCAG contrast helpers. App.svelte's parseColor is canvas-based and\n * browser-only; this runs at build time in the linter (rule 1.7). Only opaque\n * #hex (3/4/6/8) is parsed — other CSS color forms and translucent hex return\n * null and fall through to the Tier-2 axe audit, which uses the browser's own\n * parser.\n */\n\nfunction parseHex(\n input: string,\n): { r: number; g: number; b: number; a: number } | null {\n const v = input.trim();\n const m = /^#([0-9a-fA-F]{3,8})$/.exec(v);\n if (!m) return null;\n const h = m[1];\n let r: number,\n g: number,\n b: number,\n a = 255;\n if (h.length === 3 || h.length === 4) {\n r = parseInt(h[0] + h[0], 16);\n g = parseInt(h[1] + h[1], 16);\n b = parseInt(h[2] + h[2], 16);\n if (h.length === 4) a = parseInt(h[3] + h[3], 16);\n } else if (h.length === 6 || h.length === 8) {\n r = parseInt(h.slice(0, 2), 16);\n g = parseInt(h.slice(2, 4), 16);\n b = parseInt(h.slice(4, 6), 16);\n if (h.length === 8) a = parseInt(h.slice(6, 8), 16);\n } else {\n return null; // 5/7 hex digits are not a valid color\n }\n return { r, g, b, a };\n}\n\nfunction linearize(channel: number): number {\n const c = channel / 255;\n return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n}\n\n/** sRGB hex → relative luminance (0–1), or null if not a parseable opaque hex. */\nexport function relativeLuminance(hex: string): number | null {\n const rgb = parseHex(hex);\n if (!rgb) return null;\n // A translucent color's on-screen luminance depends on the backdrop; defer\n // those to the in-browser Tier-2 audit rather than guess a composite.\n if (rgb.a !== 255) return null;\n return (\n 0.2126 * linearize(rgb.r) +\n 0.7152 * linearize(rgb.g) +\n 0.0722 * linearize(rgb.b)\n );\n}\n\n/**\n * WCAG contrast ratio between two colors. Order-independent — the lighter/darker\n * ordering is handled internally, so callers may pass the colors in any order.\n * Returns null if either color isn't a parseable hex.\n */\nexport function contrastRatio(a: string, b: string): number | null {\n const la = relativeLuminance(a);\n const lb = relativeLuminance(b);\n if (la === null || lb === null) return null;\n const lighter = Math.max(la, lb);\n const darker = Math.min(la, lb);\n return (lighter + 0.05) / (darker + 0.05);\n}\n","/**\n * Shared YouTube/Vimeo embed detection. Used by Video.svelte to pick the iframe\n * vs native-<video> render path, and by the Tier-1b linter (rule 1.4) so its\n * caption/transcript guidance matches what the component actually renders.\n */\n\nconst YOUTUBE_RE =\n /(?:youtube\\.com\\/(?:watch\\?v=|embed\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/;\nconst VIMEO_RE = /vimeo\\.com\\/(?:video\\/)?(\\d+)/;\n\n/** Resolve a source URL to its embed URL, or null if it's not a known embed. */\nexport function resolveVideoEmbedUrl(src: string): string | null {\n const yt = src.match(YOUTUBE_RE);\n if (yt) return `https://www.youtube.com/embed/${yt[1]}`;\n\n const vimeo = src.match(VIMEO_RE);\n if (vimeo) return `https://player.vimeo.com/video/${vimeo[1]}`;\n\n return null;\n}\n\n/** True when the component will render an iframe embed rather than <video>. */\nexport function isVideoEmbed(src: string): boolean {\n return resolveVideoEmbedUrl(src) !== null;\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { resolve, relative } from 'node:path';\nimport JSON5 from 'json5';\nimport {\n extractDefaultExportObjectLiteral,\n parsePageConfigFromSource,\n readSourceFileCached,\n ensureSvelteSuffix,\n readCourseConfig,\n orderPageFiles,\n walkPages,\n type WalkedLesson,\n} from './manifest.js';\nimport {\n clearParseCache,\n findComponents,\n getParseError,\n type PropValue,\n} from './ast.js';\nimport {\n validateAgent,\n validateAuthCredential,\n joinFieldError,\n} from '../runtime/xapi/agent-rules.js';\nimport { httpOrigin } from '../runtime/xapi/derive-actor.js';\nimport { shortIdentifier } from '../runtime/interaction-format.js';\nimport { FEEDBACK_MODES, RETRY_MODES } from '../runtime/types.js';\nimport { contrastRatio } from './a11y/contrast.js';\nimport { isVideoEmbed } from '../components/video-embed.js';\n\n// ---------- Types ----------\n\nexport interface ValidationResult {\n errors: string[];\n warnings: string[];\n}\n\n// ---------- A11y rule IDs ----------\n\n/** Tier-1b rule IDs. `a11y.ignore` matches these literally. */\nconst A11Y_IDS = {\n imageAlt: 'tessera/image-alt',\n mediaTitle: 'tessera/media-title',\n mediaTranscript: 'tessera/media-transcript',\n mediaCaptions: 'tessera/media-captions',\n questionLabel: 'tessera/question-label',\n headingOrder: 'tessera/heading-order',\n primaryContrast: 'tessera/primary-contrast',\n lang: 'tessera/lang',\n} as const;\n\n/** Promotable by `a11y.level: 'error'`; the rest are hard contract errors. */\nconst PROMOTABLE_A11Y_IDS = new Set<string>([\n A11Y_IDS.mediaTranscript,\n A11Y_IDS.mediaCaptions,\n A11Y_IDS.questionLabel,\n A11Y_IDS.headingOrder,\n A11Y_IDS.primaryContrast,\n A11Y_IDS.lang,\n]);\n\n/** Prefix a diagnostic with its rule ID so `a11y.ignore` / `level` can match it. */\nfunction tag(id: string, message: string): string {\n return `[${id}] ${message}`;\n}\n\nfunction diagnosticId(message: string): string | null {\n const m = /^\\[([^\\]]+)\\] /.exec(message);\n return m ? m[1] : null;\n}\n\n/** True when a tagged diagnostic's rule ID is in the ignore set. */\nexport function isIgnored(\n message: string,\n ignore: ReadonlySet<string>,\n): boolean {\n const id = diagnosticId(message);\n return id !== null && ignore.has(id);\n}\n\nexport interface A11ySettings {\n level: 'warn' | 'error';\n standard: 'wcag2a' | 'wcag2aa' | 'wcag21aa';\n ignore: string[];\n}\n\nconst VALID_A11Y_LEVELS = ['warn', 'error'];\nconst VALID_A11Y_STANDARDS = ['wcag2a', 'wcag2aa', 'wcag21aa'];\n\n/** Normalize the raw `a11y` config to defaults, ignoring malformed pieces. */\nexport function normalizeA11y(raw: unknown): A11ySettings {\n const a11y =\n raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};\n const level = a11y.level === 'error' ? 'error' : 'warn';\n const standard = VALID_A11Y_STANDARDS.includes(a11y.standard as string)\n ? (a11y.standard as A11ySettings['standard'])\n : 'wcag2aa';\n const ignore = Array.isArray(a11y.ignore)\n ? a11y.ignore.filter((x): x is string => typeof x === 'string')\n : [];\n return { level, standard, ignore };\n}\n\n/**\n * Apply `a11y.ignore` (drop tagged diagnostics) and `a11y.level` (promote the\n * promotable a11y warnings to errors) to a result in place. `ignore` suppresses\n * at any severity, including hard contract errors; `level` only re-rates.\n */\nfunction applyA11ySettings(\n result: ValidationResult,\n settings: A11ySettings,\n): void {\n if (settings.ignore.length > 0) {\n const ignored = new Set(settings.ignore);\n const keep = (msg: string) => !isIgnored(msg, ignored);\n result.errors = result.errors.filter(keep);\n result.warnings = result.warnings.filter(keep);\n }\n if (settings.level === 'error') {\n const remaining: string[] = [];\n for (const msg of result.warnings) {\n const id = diagnosticId(msg);\n if (id !== null && PROMOTABLE_A11Y_IDS.has(id)) result.errors.push(msg);\n else remaining.push(msg);\n }\n result.warnings = remaining;\n }\n}\n\n/** Print validation warnings (yellow) then errors (red). Shared by the dev/build plugin and the CLI. */\nexport function reportValidationIssues({\n errors,\n warnings,\n}: ValidationResult): void {\n for (const warning of warnings) {\n console.warn(`\\x1b[33m[tessera warning]\\x1b[0m ${warning}`);\n }\n for (const error of errors) {\n console.error(`\\x1b[31m[tessera error]\\x1b[0m ${error}`);\n }\n}\n\n// Known top-level config fields\nconst KNOWN_CONFIG_FIELDS = new Set([\n 'title',\n 'description',\n 'author',\n 'version',\n 'language',\n 'branding',\n 'navigation',\n 'completion',\n 'scoring',\n 'export',\n 'chrome',\n 'xapi',\n 'a11y',\n]);\n\n// Heuristic, not a full BCP-47 grammar: a 2–3 letter primary subtag (any case)\n// plus any number of 1–8 alphanumeric subtags (script/region/variant/singleton).\nconst BCP47_RE = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{1,8})*$/;\n\n/** Plausible BCP-47 tag? Shared by the linter and the <html lang> emitter. */\nexport function isPlausibleLanguageTag(value: unknown): value is string {\n return typeof value === 'string' && BCP47_RE.test(value);\n}\n\nconst VALID_NAV_MODES = ['free', 'sequential'];\nconst VALID_COMPLETION_MODES = ['quiz', 'percentage', 'manual'];\nconst VALID_EXPORT_STANDARDS = ['web', 'scorm12', 'scorm2004', 'cmi5'];\nconst VALID_MANUAL_TRIGGERS = ['page'];\nconst VALID_REQUIRE_SUCCESS_STATUS = ['passed', 'failed'];\n// Derived from the runtime types (single source of truth) — widened to\n// string[] so .includes() accepts an arbitrary author-supplied value.\nconst VALID_FEEDBACK_MODES: readonly string[] = FEEDBACK_MODES;\nconst VALID_RETRY_MODES: readonly string[] = RETRY_MODES;\n\n// ---------- Main ----------\n\n/**\n * Validate a Tessera project at the given root.\n * Returns errors (block build) and warnings (informational).\n */\nexport function validateProject(projectRoot: string): ValidationResult {\n clearParseCache();\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // 1. Check course.config.js exists\n const configPath = resolve(projectRoot, 'course.config.js');\n if (!existsSync(configPath)) {\n errors.push('course.config.js not found in project root');\n return { errors, warnings };\n }\n\n // 2. Parse and validate config\n const config = parseConfig(projectRoot, errors, warnings);\n\n // 3. Validate pages directory\n const pagesDir = resolve(projectRoot, 'pages');\n const assetsDir = resolve(projectRoot, 'assets');\n const pageResults = validatePages(\n pagesDir,\n assetsDir,\n projectRoot,\n config?.export?.standard,\n );\n errors.push(...pageResults.errors);\n warnings.push(...pageResults.warnings);\n\n // 4. Contract-bypass checks on project-root shell files\n for (const shellFile of ['layout.svelte', 'quiz.svelte']) {\n const shellPath = resolve(projectRoot, shellFile);\n if (existsSync(shellPath)) {\n validateContractBypass(\n readSourceFileCached(shellPath),\n shellFile,\n errors,\n );\n }\n }\n\n // 5. Cross-cutting validations\n if (config) {\n crossValidate(config, pageResults, errors, warnings);\n }\n\n const result: ValidationResult = { errors, warnings };\n applyA11ySettings(result, normalizeA11y(config?.a11y));\n return result;\n}\n\n// ---------- Config Validation ----------\n\ninterface ParsedConfig {\n title?: string;\n navigation?: { mode?: string };\n completion?: {\n mode?: string;\n percentageThreshold?: number;\n trigger?: string;\n requireSuccessStatus?: string;\n };\n scoring?: { passingScore?: number };\n export?: { standard?: string };\n [key: string]: unknown;\n}\n\nfunction parseConfig(\n projectRoot: string,\n errors: string[],\n warnings: string[],\n): ParsedConfig | null {\n const read = readCourseConfig(projectRoot);\n if (!read.ok) {\n // 'missing' can't occur — validateProject checks existsSync first.\n if (read.reason === 'no-export') {\n errors.push('course.config.js: must use `export default { ... }` syntax');\n } else if (read.reason === 'parse-error') {\n errors.push(\n 'course.config.js: could not parse — JavaScript syntax error',\n );\n }\n return null;\n }\n const config = read.config as ParsedConfig;\n\n // Check for unknown fields\n for (const key of Object.keys(config)) {\n if (!KNOWN_CONFIG_FIELDS.has(key)) {\n warnings.push(\n `course.config.js: unknown field \"${key}\" — will be ignored`,\n );\n }\n }\n\n // Validate title against the runtime merge `userConfig.title || \"Untitled\n // Course\"`: a missing or empty string falls back to the default (warn), a\n // whitespace-only string is truthy and ships verbatim (warn), and a\n // non-string is a misconfiguration — a truthy one ships as-is, a falsy one\n // falls back, but either way the author should fix it (error).\n if (config.title !== undefined && typeof config.title !== 'string') {\n errors.push(\n `course.config.js: \"title\" must be a string, got ${typeof config.title}`,\n );\n } else if (config.title === undefined || config.title === '') {\n warnings.push(\n 'course.config.js: \"title\" is missing or empty — the course will ship as \"Untitled Course\"',\n );\n } else if (config.title.trim() === '') {\n warnings.push(\n 'course.config.js: \"title\" is only whitespace — it ships verbatim and will not fall back to \"Untitled Course\"',\n );\n }\n\n // Validate branding\n if (config.branding !== undefined) {\n validateBranding(config.branding, warnings);\n }\n\n // Rule 1.8: language present and well-formed (BCP-47)\n if (config.language === undefined) {\n warnings.push(\n tag(\n A11Y_IDS.lang,\n `course.config.js: \"language\" is not set — defaulting <html lang> to \"en\". Set it to the course's language (BCP-47, e.g. \"en\", \"fr-CA\") for WCAG 3.1.1.`,\n ),\n );\n } else if (!isPlausibleLanguageTag(config.language)) {\n warnings.push(\n tag(\n A11Y_IDS.lang,\n `course.config.js: \"language\" (${JSON.stringify(config.language)}) is not a plausible BCP-47 tag — use e.g. \"en\", \"es\", or \"fr-CA\"`,\n ),\n );\n }\n\n // Validate a11y config block\n if (config.a11y !== undefined) {\n validateA11yConfig(config.a11y, errors);\n }\n\n // Validate navigation.mode\n if (config.navigation?.mode !== undefined) {\n if (!VALID_NAV_MODES.includes(config.navigation.mode)) {\n errors.push(\n `course.config.js: \"navigation.mode\" must be \"free\" or \"sequential\", got \"${config.navigation.mode}\"`,\n );\n }\n }\n\n // Validate completion.mode\n if (config.completion?.mode !== undefined) {\n if (!VALID_COMPLETION_MODES.includes(config.completion.mode)) {\n errors.push(\n `course.config.js: \"completion.mode\" must be \"quiz\", \"percentage\", or \"manual\", got \"${config.completion.mode}\"`,\n );\n }\n }\n\n if (config.completion?.trigger !== undefined) {\n if (config.completion.mode !== 'manual') {\n warnings.push(\n `course.config.js: \"completion.trigger\" is ignored unless completion.mode is \"manual\"`,\n );\n } else if (!VALID_MANUAL_TRIGGERS.includes(config.completion.trigger)) {\n errors.push(\n `course.config.js: \"completion.trigger\" must be \"page\" or omitted, got \"${config.completion.trigger}\"`,\n );\n }\n }\n\n if (config.completion?.requireSuccessStatus !== undefined) {\n if (config.completion.mode !== 'manual') {\n warnings.push(\n `course.config.js: \"completion.requireSuccessStatus\" is ignored unless completion.mode is \"manual\"`,\n );\n } else if (\n !VALID_REQUIRE_SUCCESS_STATUS.includes(\n config.completion.requireSuccessStatus,\n )\n ) {\n errors.push(\n `course.config.js: \"completion.requireSuccessStatus\" must be \"passed\" or \"failed\" (omit for \"unknown\"), got \"${config.completion.requireSuccessStatus}\"`,\n );\n }\n }\n\n // Validate export.standard\n if (config.export?.standard !== undefined) {\n if (!VALID_EXPORT_STANDARDS.includes(config.export.standard)) {\n errors.push(\n `course.config.js: \"export.standard\" must be \"web\", \"scorm12\", \"scorm2004\", or \"cmi5\", got \"${config.export.standard}\"`,\n );\n }\n }\n\n // Validate scoring.passingScore\n if (config.scoring?.passingScore !== undefined) {\n const score = config.scoring.passingScore;\n if (typeof score !== 'number' || score < 0 || score > 100) {\n errors.push(\n `course.config.js: \"scoring.passingScore\" must be 0–100, got ${score}`,\n );\n }\n }\n\n // Validate completion.percentageThreshold\n if (config.completion?.percentageThreshold !== undefined) {\n const threshold = config.completion.percentageThreshold;\n if (typeof threshold !== 'number' || threshold < 0 || threshold > 100) {\n errors.push(\n `course.config.js: \"completion.percentageThreshold\" must be 0–100, got ${threshold}`,\n );\n }\n }\n\n // Validate xapi (publisher destinations)\n if (config.xapi !== undefined) {\n validateXAPIConfig(\n config.xapi,\n config.export?.standard ?? 'web',\n errors,\n warnings,\n );\n }\n\n return config;\n}\n\n// ---------- Branding Validation ----------\n\n// Permissive approximation of the browser's accepted color set: hex 3/4/6/8,\n// any CSS functional notation (rgb/hsl/hwb/lab/lch/oklab/oklch/color), or a\n// bare keyword (named colors, transparent, currentColor). parseColor's real\n// check (App.svelte) is browser-only and the runtime degrades gracefully, so\n// an unrecognized value is advisory, never an error — lean permissive to avoid\n// rejecting values the browser would accept.\nconst HEX_COLOR_RE = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\nconst FUNC_COLOR_RE =\n /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\\(.*\\)$/i;\nconst NAMED_COLOR_RE = /^[a-zA-Z]+$/;\n\nfunction isPlausibleColor(value: string): boolean {\n const v = value.trim();\n return (\n HEX_COLOR_RE.test(v) || FUNC_COLOR_RE.test(v) || NAMED_COLOR_RE.test(v)\n );\n}\n\n/**\n * Format checks on the branding block (advisory) plus rule 1.7's contrast check\n * on primaryColor. Runtime failures are mild: an unresolved logo ships a broken\n * <img src>, an unparseable color falls back to theme defaults.\n */\nfunction describeType(raw: unknown): string {\n return raw === null ? 'null' : Array.isArray(raw) ? 'array' : typeof raw;\n}\n\nfunction validateBranding(raw: unknown, warnings: string[]): void {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {\n warnings.push(\n `course.config.js: \"branding\" must be an object, got ${describeType(raw)} — will be ignored`,\n );\n return;\n }\n const branding = raw as Record<string, unknown>;\n\n const logo = branding.logo;\n if (logo !== undefined) {\n if (typeof logo !== 'string') {\n warnings.push(\n `course.config.js: \"branding.logo\" must be a string, got ${typeof logo}`,\n );\n } else if (logo.startsWith('$assets/')) {\n warnings.push(\n 'course.config.js: \"branding.logo\" starts with \"$assets/\", but branding paths are not asset-resolved — it will ship as a literal, broken src. Use a URL or a path relative to the deployed root.',\n );\n }\n }\n\n const primaryColor = branding.primaryColor;\n if (primaryColor !== undefined) {\n if (typeof primaryColor !== 'string') {\n warnings.push(\n `course.config.js: \"branding.primaryColor\" must be a string, got ${typeof primaryColor}`,\n );\n } else if (!isPlausibleColor(primaryColor)) {\n warnings.push(\n `course.config.js: \"branding.primaryColor\" \"${primaryColor}\" does not look like a valid CSS color — the theme will fall back to its default shades if the browser can't parse it`,\n );\n } else {\n // Rule 1.7: primaryColor is used both as links on the default white page\n // background and as a button fill behind white text — symmetric, so one\n // ratio covers both. Non-#hex valid colors return null and defer to Tier 2.\n const ratio = contrastRatio(primaryColor, '#ffffff');\n if (ratio !== null && ratio < 4.5) {\n warnings.push(\n tag(\n A11Y_IDS.primaryContrast,\n `course.config.js: branding.primaryColor (${primaryColor}) is ${ratio.toFixed(2)}:1 against white — it's used both for links on the page background and as a button fill behind white text, and WCAG AA needs 4.5:1 for each`,\n ),\n );\n }\n }\n }\n\n const fontFamily = branding.fontFamily;\n if (fontFamily !== undefined && typeof fontFamily !== 'string') {\n warnings.push(\n `course.config.js: \"branding.fontFamily\" must be a string, got ${typeof fontFamily}`,\n );\n }\n}\n\n// ---------- a11y Config Validation ----------\n\n/** Shape-check the `a11y` block. Malformed values can't be silenced by `ignore`. */\nfunction validateA11yConfig(raw: unknown, errors: string[]): void {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {\n errors.push(\n `course.config.js: \"a11y\" must be an object, got ${describeType(raw)}`,\n );\n return;\n }\n const a11y = raw as Record<string, unknown>;\n\n if (\n a11y.level !== undefined &&\n !VALID_A11Y_LEVELS.includes(a11y.level as string)\n ) {\n errors.push(\n `course.config.js: \"a11y.level\" must be \"warn\" or \"error\", got ${JSON.stringify(a11y.level)}`,\n );\n }\n if (\n a11y.standard !== undefined &&\n !VALID_A11Y_STANDARDS.includes(a11y.standard as string)\n ) {\n errors.push(\n `course.config.js: \"a11y.standard\" must be \"wcag2a\", \"wcag2aa\", or \"wcag21aa\", got ${JSON.stringify(a11y.standard)}`,\n );\n }\n if (a11y.ignore !== undefined) {\n if (\n !Array.isArray(a11y.ignore) ||\n a11y.ignore.some((x) => typeof x !== 'string')\n ) {\n errors.push(\n `course.config.js: \"a11y.ignore\" must be an array of rule-ID strings`,\n );\n }\n }\n}\n\n// ---------- xAPI Config Validation ----------\n\nconst UUID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nfunction validateXAPIConfig(\n raw: unknown,\n standard: string,\n errors: string[],\n warnings: string[],\n): void {\n if (raw === undefined || raw === null) return;\n\n // Normalize to array form. The single-object case is shorthand for a\n // one-element array — same machinery, no special case in the runtime.\n const entries: unknown[] = Array.isArray(raw) ? raw : [raw];\n\n if (Array.isArray(raw)) {\n if (entries.length === 0) {\n errors.push(\n 'course.config.js: xapi must contain at least one destination, or be omitted',\n );\n return;\n }\n // At most one 'lms' entry — more than one is never legitimate.\n const lmsCount = entries.filter(\n (e) =>\n e &&\n typeof e === 'object' &&\n (e as { endpoint?: unknown }).endpoint === 'lms',\n ).length;\n if (lmsCount > 1) {\n errors.push(\n \"course.config.js: xapi has multiple entries with endpoint: 'lms' — only one cmi5 launch-inherited destination is allowed\",\n );\n }\n // Warn on duplicate explicit endpoints.\n const seen = new Map<string, number>();\n for (const e of entries) {\n if (e && typeof e === 'object') {\n const ep = (e as { endpoint?: unknown }).endpoint;\n if (typeof ep === 'string' && ep !== 'lms') {\n seen.set(ep, (seen.get(ep) ?? 0) + 1);\n }\n }\n }\n for (const [ep, count] of seen) {\n if (count > 1) {\n warnings.push(\n `course.config.js: xapi has ${count} entries with endpoint \"${ep}\" — usually a copy-paste mistake; ` +\n 'fan-out to the same LRS with different actors/activityIds is supported but uncommon.',\n );\n }\n }\n } else if (typeof raw !== 'object') {\n errors.push(\n 'course.config.js: xapi must be an object or an array of objects',\n );\n return;\n }\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const label = Array.isArray(raw) ? `xapi[${i}]` : 'xapi';\n if (!entry || typeof entry !== 'object') {\n errors.push(`course.config.js: ${label} must be an object`);\n continue;\n }\n validateSingleXAPIEntry(\n entry as Record<string, unknown>,\n label,\n standard,\n errors,\n warnings,\n );\n }\n}\n\nfunction validateSingleXAPIEntry(\n entry: Record<string, unknown>,\n label: string,\n standard: string,\n errors: string[],\n warnings: string[],\n): void {\n const endpoint = entry.endpoint;\n if (endpoint === undefined) {\n errors.push(`course.config.js: ${label}.endpoint is required`);\n return;\n }\n if (typeof endpoint !== 'string') {\n errors.push(`course.config.js: ${label}.endpoint must be a string`);\n return;\n }\n\n if (endpoint === 'lms') {\n // Forbid under non-cmi5 export.\n if (standard !== 'cmi5') {\n errors.push(\n `course.config.js: ${label}.endpoint: 'lms' requires export.standard: 'cmi5' (you have \"${standard}\"). ` +\n 'Either change the export standard or specify an explicit LRS endpoint.',\n );\n }\n // Forbid extra fields — everything is inherited from the cmi5 launch.\n const forbidden = [\n 'auth',\n 'actor',\n 'activityId',\n 'registration',\n 'actorAccountHomePage',\n ];\n for (const f of forbidden) {\n if (entry[f] !== undefined) {\n errors.push(\n `course.config.js: ${label}.${f} must be omitted when ${label}.endpoint is 'lms' — it is inherited from the cmi5 launch.`,\n );\n }\n }\n return;\n }\n\n // Explicit endpoint — must be an absolute http(s) URL.\n let url: URL;\n try {\n url = new URL(endpoint);\n } catch {\n errors.push(\n `course.config.js: ${label}.endpoint must be an absolute http(s) URL, got \"${endpoint}\"`,\n );\n return;\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n errors.push(\n `course.config.js: ${label}.endpoint must use http: or https:, got \"${url.protocol}\"`,\n );\n return;\n }\n if (url.protocol === 'http:' && process.env.NODE_ENV === 'production') {\n warnings.push(\n `course.config.js: ${label}.endpoint uses http:; LRS credentials will travel in cleartext. Use https in production.`,\n );\n }\n if (!endpoint.endsWith('/')) {\n warnings.push(\n `course.config.js: ${label}.endpoint should end with a slash to avoid concatenation surprises ` +\n `(e.g. 'https://lrs.example.com/xapi/' not 'https://lrs.example.com/xapi'). Runtime normalizes regardless.`,\n );\n }\n\n // auth — required for explicit endpoints.\n const auth = entry.auth;\n if (auth === undefined) {\n errors.push(`course.config.js: ${label}.auth is required`);\n } else if (typeof auth === 'string') {\n const authErr = validateAuthCredential(auth);\n if (authErr) {\n errors.push(\n `course.config.js: ${joinFieldError(`${label}.auth`, authErr)}`,\n );\n } else {\n warnings.push(\n `course.config.js: ${label}.auth is a static string and will be embedded in the bundle. ` +\n 'For production, pass a function that fetches a short-lived token from a server endpoint.',\n );\n }\n } else if (typeof auth !== 'function') {\n errors.push(\n `course.config.js: ${label}.auth must be a string or a function, got ${typeof auth}`,\n );\n }\n\n // activityId — required IRI.\n const activityId = entry.activityId;\n if (activityId === undefined || activityId === '') {\n errors.push(`course.config.js: ${label}.activityId is required`);\n } else if (typeof activityId !== 'string') {\n errors.push(`course.config.js: ${label}.activityId must be a string`);\n } else {\n try {\n // Any absolute IRI — the URL constructor accepts uncommon schemes.\n new URL(activityId);\n } catch {\n errors.push(\n `course.config.js: ${label}.activityId must be an absolute IRI, got \"${activityId}\"`,\n );\n }\n }\n\n // actor — required under web; optional otherwise.\n const actor = entry.actor;\n if (actor === undefined) {\n if (standard === 'web') {\n errors.push(\n `course.config.js: ${label}.actor is required for web export — there is no LMS to derive a learner identity from. ` +\n 'Provide either a static actor object or a function that resolves one (e.g. from your auth system).',\n );\n }\n } else if (typeof actor === 'object' && actor !== null) {\n const err = validateAgent(actor);\n if (err) {\n errors.push(`course.config.js: ${joinFieldError(`${label}.actor`, err)}`);\n }\n } else if (typeof actor !== 'function') {\n errors.push(\n `course.config.js: ${label}.actor must be an object or function, got ${typeof actor}`,\n );\n }\n\n // actorAccountHomePage — optional, only meaningful under SCORM with no\n // explicit actor.\n const aahp = entry.actorAccountHomePage;\n if (aahp !== undefined) {\n if (typeof aahp !== 'string') {\n errors.push(\n `course.config.js: ${label}.actorAccountHomePage must be a string`,\n );\n } else {\n try {\n new URL(aahp);\n } catch {\n errors.push(\n `course.config.js: ${label}.actorAccountHomePage must be an absolute URL`,\n );\n }\n }\n if (actor !== undefined) {\n warnings.push(\n `course.config.js: ${label}.actorAccountHomePage is ignored when ${label}.actor is supplied explicitly.`,\n );\n }\n if (standard === 'cmi5' || standard === 'web') {\n warnings.push(\n `course.config.js: ${label}.actorAccountHomePage is only used under scorm12/scorm2004 actor synthesis; ignored under \"${standard}\".`,\n );\n }\n }\n\n // SCORM with auto-derived actor and a non-http(s) activityId:\n // actorAccountHomePage becomes required.\n if (\n actor === undefined &&\n (standard === 'scorm12' || standard === 'scorm2004') &&\n typeof activityId === 'string' &&\n httpOrigin(activityId) === null &&\n aahp === undefined\n ) {\n errors.push(\n `course.config.js: ${label}.activityId is not an http(s) URL, so its origin can't be used as the SCORM actor's account.homePage. ` +\n `Provide ${label}.actorAccountHomePage explicitly.`,\n );\n }\n\n // registration — optional UUID v4.\n const registration = entry.registration;\n if (registration !== undefined) {\n if (typeof registration !== 'string' || !UUID_RE.test(registration)) {\n errors.push(\n `course.config.js: ${label}.registration must be a UUID v4, got \"${String(registration)}\"`,\n );\n }\n if (standard !== 'cmi5') {\n warnings.push(\n `course.config.js: ${label}.registration is a cmi5 concept; the LRS will accept it under \"${standard}\" but most analytics tools won't know what to do with it.`,\n );\n }\n }\n}\n\n// ---------- Pages Validation ----------\n\ninterface PageInfo {\n fileRel: string;\n navIndex: number;\n hasGradedQuiz: boolean;\n hasQuiz: boolean;\n completesOnView: boolean;\n}\n\ninterface PagesValidationResult extends ValidationResult {\n totalPages: number;\n totalQuizzes: number;\n hasGradedQuiz: boolean;\n hasParseErrors: boolean;\n pages: PageInfo[];\n}\n\n/**\n * Validate a single page .svelte file. Used for both section-level (flat) and\n * lesson-level pages — the validation is identical, only the containing\n * directory differs.\n */\nfunction validatePageFile(\n filePath: string,\n projectRoot: string,\n assetsDir: string,\n navIndex: number,\n errors: string[],\n warnings: string[],\n assetExistsCache: Map<string, boolean>,\n exportStandard?: string,\n): {\n page: PageInfo;\n isQuiz: boolean;\n isGradedQuiz: boolean;\n parseError: boolean;\n} {\n const fileRel = relative(projectRoot, filePath);\n const content = readSourceFileCached(filePath);\n\n const parseError = getParseError(content);\n if (parseError) {\n errors.push(`${fileRel}: could not parse — ${parseError}`);\n return {\n page: {\n fileRel,\n navIndex,\n hasGradedQuiz: false,\n hasQuiz: false,\n completesOnView: false,\n },\n isQuiz: false,\n isGradedQuiz: false,\n parseError: true,\n };\n }\n\n const pageConfig = validatePageConfig(content, fileRel, errors);\n\n const isQuiz = !!pageConfig?.quiz;\n let isGradedQuiz = false;\n if (pageConfig?.quiz) {\n validateQuizConfig(pageConfig.quiz, fileRel, errors);\n if ((pageConfig.quiz as { graded?: unknown }).graded === true) {\n isGradedQuiz = true;\n }\n }\n\n const completesOnView = validateCompletesOn(pageConfig, fileRel, errors);\n\n validateAssetRefs(content, fileRel, assetsDir, warnings, assetExistsCache);\n validateQuestionComponents(\n content,\n fileRel,\n errors,\n warnings,\n exportStandard,\n );\n validateMediaComponents(content, fileRel, errors, warnings);\n validateHeadingOrder(content, fileRel, warnings);\n validateContractBypass(content, fileRel, errors);\n if (\n pageConfig?.quiz &&\n !HAS_USE_QUESTION_RE.test(content) &&\n !HAS_QUESTION_TAG_RE.test(content) &&\n !HAS_LOCAL_SVELTE_IMPORT_RE.test(content)\n ) {\n warnings.push(\n `${fileRel}: quiz page has no question components or useQuestion() calls — ` +\n `the quiz will have nothing to score`,\n );\n }\n\n return {\n page: {\n fileRel,\n navIndex,\n hasGradedQuiz: isGradedQuiz,\n hasQuiz: isQuiz,\n completesOnView,\n },\n isQuiz,\n isGradedQuiz,\n parseError: false,\n };\n}\n\nfunction validatePages(\n pagesDir: string,\n assetsDir: string,\n projectRoot: string,\n exportStandard?: string,\n): PagesValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n const pages: PageInfo[] = [];\n let totalPages = 0;\n let totalQuizzes = 0;\n let hasGradedQuiz = false;\n let hasParseErrors = false;\n // One existsSync per unique asset for the whole pass.\n const assetExistsCache = new Map<string, boolean>();\n\n const noPages = (): PagesValidationResult => {\n errors.push(\n 'No pages found. Create at least one section with a lesson and page in pages/',\n );\n return {\n errors,\n warnings,\n totalPages,\n totalQuizzes,\n hasGradedQuiz,\n hasParseErrors,\n pages,\n };\n };\n\n if (!existsSync(pagesDir)) return noPages();\n\n // walkPages only descends into section dirs, so scan pages/ root separately.\n for (const entry of readdirSync(pagesDir)) {\n const fullPath = resolve(pagesDir, entry);\n if (entry.endsWith('.svelte') && statSync(fullPath).isFile()) {\n warnings.push(\n `${relative(projectRoot, fullPath)}: this file is outside the section/lesson structure and will be ignored`,\n );\n }\n }\n\n const sections = walkPages(pagesDir);\n if (sections.length === 0) return noPages();\n\n // For a flat lesson `meta` is the section's _meta. Same ordering as generateManifest.\n const validateLesson = (\n lesson: WalkedLesson,\n meta: { pages?: string[] } | null,\n ): void => {\n if (meta?.pages) {\n for (const pageName of meta.pages) {\n const fileName = ensureSvelteSuffix(pageName);\n if (!lesson.files.includes(fileName)) {\n errors.push(\n `${relative(projectRoot, lesson.metaPath)}: pages array lists \"${pageName}\" but ${fileName} not found in this directory`,\n );\n }\n }\n }\n if (meta?.pages && meta.pages.length > 0) {\n const listedSet = new Set(meta.pages.map(ensureSvelteSuffix));\n for (const file of lesson.files) {\n if (!listedSet.has(file)) {\n warnings.push(\n `${relative(projectRoot, resolve(lesson.dir, file))}: not listed in _meta.js pages array — will be appended at end`,\n );\n }\n }\n }\n\n for (const fileName of orderPageFiles(lesson.files, meta?.pages)) {\n const result = validatePageFile(\n resolve(lesson.dir, fileName),\n projectRoot,\n assetsDir,\n totalPages,\n errors,\n warnings,\n assetExistsCache,\n exportStandard,\n );\n totalPages++;\n if (result.isQuiz) totalQuizzes++;\n if (result.isGradedQuiz) hasGradedQuiz = true;\n if (result.parseError) hasParseErrors = true;\n pages.push(result.page);\n }\n };\n\n for (const section of sections) {\n const sectionRel = relative(projectRoot, section.dir);\n const pagesBeforeSection = totalPages;\n\n const sectionMeta = validateMetaFile(section.metaPath, sectionRel, errors);\n\n for (const lesson of section.lessons) {\n if (lesson.name === null) {\n // Flat lesson uses the section _meta, already validated above.\n validateLesson(lesson, sectionMeta);\n } else {\n const meta = validateMetaFile(\n lesson.metaPath,\n relative(projectRoot, lesson.dir),\n errors,\n );\n validateLesson(lesson, meta);\n }\n }\n\n // The page-count delta covers both the no-lessons and empty-lessons cases.\n if (totalPages === pagesBeforeSection) {\n warnings.push(\n `${sectionRel}: section contributed no pages and will be empty`,\n );\n }\n }\n\n if (totalPages === 0) return noPages();\n\n return {\n errors,\n warnings,\n totalPages,\n totalQuizzes,\n hasGradedQuiz,\n hasParseErrors,\n pages,\n };\n}\n\n// ---------- _meta.js Validation ----------\n\nfunction validateMetaFile(\n metaPath: string,\n parentRel: string,\n errors: string[],\n): { title?: string; pages?: string[] } | null {\n if (!existsSync(metaPath)) return null;\n\n const metaRel = `${parentRel}/_meta.js`;\n const result = extractDefaultExportObjectLiteral(\n readSourceFileCached(metaPath),\n );\n\n if (result.kind === 'parse-error') {\n errors.push(`${metaRel}: could not parse — JavaScript syntax error`);\n return null;\n }\n if (result.kind !== 'literal') {\n errors.push(\n `${metaRel}: syntax error — must export default { title: \"...\" }`,\n );\n return null;\n }\n\n let meta: { title?: string; pages?: string[] };\n try {\n meta = JSON5.parse(result.text);\n } catch {\n errors.push(\n `${metaRel}: syntax error — must export default { title: \"...\" }`,\n );\n return null;\n }\n\n if (!meta.title) {\n errors.push(`${metaRel}: missing required \"title\" field`);\n }\n\n return meta;\n}\n\n// ---------- pageConfig Validation ----------\n\nfunction validatePageConfig(\n content: string,\n fileRel: string,\n errors: string[],\n): { title?: string; quiz?: unknown; completesOn?: unknown } | null {\n const result = parsePageConfigFromSource(content);\n if (result.kind === 'ok') return result.value;\n if (result.kind === 'invalid') {\n errors.push(\n `${fileRel}: pageConfig must be a static object literal (no variables, function calls, or computed values)`,\n );\n }\n return null;\n}\n\nfunction validateCompletesOn(\n pageConfig: { completesOn?: unknown } | null,\n fileRel: string,\n errors: string[],\n): boolean {\n if (!pageConfig || pageConfig.completesOn === undefined) return false;\n if (pageConfig.completesOn === 'view') return true;\n errors.push(\n `${fileRel}: pageConfig.completesOn must be \"view\", got ${JSON.stringify(pageConfig.completesOn)}`,\n );\n return false;\n}\n\n// ---------- Quiz Config Validation ----------\n\nfunction validateQuizConfig(\n quiz: unknown,\n fileRel: string,\n errors: string[],\n): void {\n if (!quiz || typeof quiz !== 'object') return;\n const cfg = quiz as Record<string, unknown>;\n\n if (cfg.maxAttempts !== undefined) {\n const val = cfg.maxAttempts;\n if (\n val !== Infinity &&\n (typeof val !== 'number' || val <= 0 || !Number.isFinite(val))\n ) {\n errors.push(\n `${fileRel}: quiz.maxAttempts must be a positive number or Infinity, got ${String(val)}`,\n );\n }\n }\n\n for (const field of ['graded', 'gatesProgress']) {\n if (cfg[field] !== undefined && typeof cfg[field] !== 'boolean') {\n errors.push(\n `${fileRel}: quiz.${field} must be a boolean, got ${typeof cfg[field]}`,\n );\n }\n }\n\n if (\n cfg.feedbackMode !== undefined &&\n !VALID_FEEDBACK_MODES.includes(cfg.feedbackMode as string)\n ) {\n errors.push(\n `${fileRel}: quiz.feedbackMode must be \"review\", \"immediate\", or \"never\", got \"${String(cfg.feedbackMode)}\"`,\n );\n }\n if (\n cfg.retryMode !== undefined &&\n !VALID_RETRY_MODES.includes(cfg.retryMode as string)\n ) {\n errors.push(\n `${fileRel}: quiz.retryMode must be \"full\" or \"incorrect-only\", got \"${String(cfg.retryMode)}\"`,\n );\n }\n}\n\n// ---------- Question Component Validation ----------\n\nconst QUESTION_COMPONENT_REQUIRED: Record<string, string[]> = {\n MultipleChoice: ['question', 'options', 'correct'],\n FillInTheBlank: ['question', 'answers'],\n Matching: ['question', 'pairs'],\n Sorting: ['question', 'items', 'targets', 'correct'],\n};\n\nfunction staticArray(prop: PropValue | undefined): unknown[] | null {\n if (prop?.kind !== 'expr' || !prop.raw.startsWith('[')) return null;\n try {\n const parsed = JSON5.parse(prop.raw);\n return Array.isArray(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction staticNumber(prop: PropValue | undefined): number | null {\n if (prop?.kind !== 'expr') return null;\n try {\n const parsed = JSON5.parse(prop.raw);\n return typeof parsed === 'number' ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction validateQuestionComponents(\n content: string,\n fileRel: string,\n errors: string[],\n warnings: string[],\n exportStandard?: string,\n): void {\n const components = findComponents(\n content,\n new Set(Object.keys(QUESTION_COMPONENT_REQUIRED)),\n );\n if (!components) return;\n const seenIds = new Set<string>();\n const seenSanitized = new Set<string>();\n for (const { name, props, hasSpread } of components) {\n for (const req of QUESTION_COMPONENT_REQUIRED[name]) {\n if (!hasSpread && !props.has(req)) {\n errors.push(`${fileRel}: <${name}> is missing required prop \"${req}\"`);\n }\n }\n\n // Rule 1.5: empty option/answer labels are both an a11y and a scoring bug.\n for (const labelProp of ['options', 'answers']) {\n const entries = staticArray(props.get(labelProp));\n if (entries?.some((e) => typeof e === 'string' && e.trim() === '')) {\n warnings.push(\n tag(\n A11Y_IDS.questionLabel,\n `${fileRel}: <${name}> has an empty ${labelProp === 'options' ? 'option' : 'answer'} label`,\n ),\n );\n }\n }\n\n const idProp = props.get('id');\n if (idProp?.kind === 'string') {\n if (seenIds.has(idProp.value)) {\n errors.push(\n `${fileRel}: duplicate question id \"${idProp.value}\" — each question on a page needs a unique id`,\n );\n } else if (exportStandard === 'scorm12') {\n // scorm12-only: shortIdentifier strips non-alphanumerics, so distinct\n // raw ids can collide after sanitization. Skip raw duplicates (already\n // flagged above) to avoid double-reporting the same id.\n const sane = shortIdentifier(idProp.value);\n if (sane !== idProp.value) {\n warnings.push(\n `${fileRel}: question id \"${idProp.value}\" will be rewritten to \"${sane}\" for SCORM 1.2 — use only letters and digits (underscores only between them)`,\n );\n }\n if (seenSanitized.has(sane)) {\n errors.push(\n `${fileRel}: question id \"${idProp.value}\" collides with a prior id after SCORM 1.2 sanitization (\"${sane}\")`,\n );\n }\n seenSanitized.add(sane);\n }\n seenIds.add(idProp.value);\n }\n\n const weightProp = props.get('weight');\n if (weightProp?.kind === 'string') {\n warnings.push(\n `${fileRel}: <${name}> weight=\"${weightProp.value}\" is a string and is ignored (treated as 1) — pass a number: weight={${weightProp.value}}`,\n );\n } else {\n const weight = staticNumber(weightProp);\n if (weight !== null) {\n if (!Number.isFinite(weight)) {\n errors.push(\n `${fileRel}: <${name}> weight must be finite — a non-finite weight makes the weighted score NaN, got ${weight}`,\n );\n } else if (!(weight > 0)) {\n warnings.push(\n `${fileRel}: <${name}> weight ${weight} is not positive and is ignored (treated as 1)`,\n );\n }\n }\n }\n\n if (name === 'MultipleChoice') {\n const options = staticArray(props.get('options'));\n const correct = staticNumber(props.get('correct'));\n if (options && correct !== null) {\n if (\n !Number.isInteger(correct) ||\n correct < 0 ||\n correct >= options.length\n ) {\n errors.push(\n `${fileRel}: <MultipleChoice> correct={${correct}} is out of range for ${options.length} options (valid: 0–${options.length - 1})`,\n );\n }\n }\n const optionFeedback = staticArray(props.get('optionFeedback'));\n if (options && optionFeedback && optionFeedback.length > options.length) {\n warnings.push(\n `${fileRel}: <MultipleChoice> optionFeedback has ${optionFeedback.length} entries but only ${options.length} options — the extra entries can never be shown`,\n );\n }\n } else if (name === 'Sorting') {\n const items = staticArray(props.get('items'));\n const targets = staticArray(props.get('targets'));\n const correct = staticArray(props.get('correct'));\n if (items && correct && correct.length !== items.length) {\n errors.push(\n `${fileRel}: <Sorting> correct has ${correct.length} entries but items has ${items.length} — they must be parallel arrays`,\n );\n }\n if (targets && correct) {\n for (const idx of correct) {\n if (\n typeof idx !== 'number' ||\n !Number.isInteger(idx) ||\n idx < 0 ||\n idx >= targets.length\n ) {\n errors.push(\n `${fileRel}: <Sorting> correct contains ${JSON.stringify(idx)}, out of range for ${targets.length} targets (valid: 0–${targets.length - 1})`,\n );\n break;\n }\n }\n }\n } else if (name === 'Matching') {\n const pairs = staticArray(props.get('pairs'));\n if (pairs) {\n const bad = pairs.some(\n (p) =>\n typeof p !== 'object' ||\n p === null ||\n typeof (p as { left?: unknown }).left !== 'string' ||\n typeof (p as { right?: unknown }).right !== 'string',\n );\n if (bad) {\n errors.push(\n `${fileRel}: <Matching> pairs must be an array of { left: string, right: string } objects`,\n );\n }\n }\n } else if (name === 'FillInTheBlank') {\n const answers = staticArray(props.get('answers'));\n if (answers) {\n if (answers.length === 0) {\n errors.push(`${fileRel}: <FillInTheBlank> answers must not be empty`);\n } else if (answers.some((a) => typeof a !== 'string')) {\n errors.push(\n `${fileRel}: <FillInTheBlank> answers must be an array of strings`,\n );\n }\n }\n }\n }\n}\n\n// ---------- Media Component Validation (rules 1.3 / 1.4) ----------\n\n/** Remove HTML/Svelte comments so commented-out markup isn't scanned as live. */\nconst HTML_COMMENT_RE = /<!--[\\s\\S]*?-->/g;\n\nconst SCRIPT_STYLE_RE = /<(script|style)\\b[\\s\\S]*?<\\/\\1>/gi;\n\n// Loop until stable: one pass can leave a reconstructed tag behind (e.g. `<scr<script></script>ipt>`).\nfunction stripRepeated(input: string, patterns: RegExp[]): string {\n let out = input;\n for (const pattern of patterns) {\n let prev: string;\n do {\n prev = out;\n out = out.replace(pattern, '');\n } while (out !== prev);\n }\n return out;\n}\n\n/**\n * Sibling to validateQuestionComponents kept out of QUESTION_COMPONENT_REQUIRED\n * so media isn't treated as gradable questions. Declares `warnings` directly.\n * Non-static (kind 'expr') values are skipped, matching the rest of the linter.\n */\nfunction validateMediaComponents(\n content: string,\n fileRel: string,\n errors: string[],\n warnings: string[],\n): void {\n const components = findComponents(\n content,\n new Set(['Image', 'Video', 'Audio']),\n );\n if (!components) return;\n for (const { name, props, hasSpread } of components) {\n if (name === 'Image') {\n const alt = props.get('alt');\n const decorative = props.get('decorative');\n // A string value is truthy at runtime (so decorative=\"false\" hides the\n // image), but the parser sees a string, not a boolean — flag the misuse.\n if (decorative?.kind === 'string') {\n errors.push(\n tag(\n A11Y_IDS.imageAlt,\n `${fileRel}: <Image> \"decorative\" must be a boolean — use decorative or decorative={true}, not the string ${JSON.stringify(decorative.value)}`,\n ),\n );\n continue;\n }\n const hasDecorative =\n decorative?.kind === 'bool' ||\n (decorative?.kind === 'expr' && decorative.raw.trim() === 'true');\n const altIsEmpty = alt?.kind === 'string' && alt.value.trim() === '';\n if (!hasDecorative && !hasSpread && (alt === undefined || altIsEmpty)) {\n errors.push(\n tag(\n A11Y_IDS.imageAlt,\n `${fileRel}: <Image> needs alt text, or mark it decorative={true} if purely ornamental`,\n ),\n );\n }\n if (hasDecorative && alt?.kind === 'string' && alt.value.trim() !== '') {\n warnings.push(\n tag(\n A11Y_IDS.imageAlt,\n `${fileRel}: <Image> is decorative but also has alt text — the alt will be dropped`,\n ),\n );\n }\n continue;\n }\n\n // Video / Audio\n const title = props.get('title');\n const titleIsEmpty = title?.kind === 'string' && title.value.trim() === '';\n if (!hasSpread && (title === undefined || titleIsEmpty)) {\n errors.push(\n tag(\n A11Y_IDS.mediaTitle,\n `${fileRel}: <${name}> needs a title — it's the accessible name for the player`,\n ),\n );\n }\n const src = props.get('src');\n const isEmbed = src?.kind === 'string' && isVideoEmbed(src.value);\n if (\n name === 'Video' &&\n !hasSpread &&\n isEmbed &&\n props.get('transcript') === undefined\n ) {\n warnings.push(\n tag(\n A11Y_IDS.mediaTranscript,\n `${fileRel}: <Video> embeds can't carry caption tracks — provide a transcript for WCAG 1.2`,\n ),\n );\n }\n if (\n name === 'Video' &&\n !hasSpread &&\n src?.kind === 'string' &&\n !isEmbed &&\n props.get('tracks') === undefined &&\n props.get('transcript') === undefined\n ) {\n warnings.push(\n tag(\n A11Y_IDS.mediaCaptions,\n `${fileRel}: native <Video> has no caption tracks or transcript — add tracks={[…]} or a transcript for WCAG 1.2.2`,\n ),\n );\n }\n if (\n name === 'Audio' &&\n !hasSpread &&\n props.get('transcript') === undefined\n ) {\n warnings.push(\n tag(\n A11Y_IDS.mediaTranscript,\n `${fileRel}: <Audio> has no transcript — required for WCAG 1.2.1`,\n ),\n );\n }\n }\n}\n\n// ---------- Heading Order Validation (rule 1.6) ----------\n\n/**\n * Warn on a skipped heading level (e.g. h2 → h4). Scripts, styles, and comments\n * are stripped first so string literals, CSS, and commented-out markup can't be\n * miscounted. No \"one h1 per page\" check — the layout owns the page h1 and child\n * components emit headings a static scan can't see; that belongs to the Tier-2\n * audit.\n */\nfunction validateHeadingOrder(\n content: string,\n fileRel: string,\n warnings: string[],\n): void {\n const html = stripRepeated(content, [SCRIPT_STYLE_RE, HTML_COMMENT_RE]);\n const levels = [...html.matchAll(/<h([1-6])\\b/gi)].map((h) => Number(h[1]));\n let prevSeen: number | null = null;\n for (const level of levels) {\n if (prevSeen !== null && level - prevSeen > 1) {\n warnings.push(\n tag(\n A11Y_IDS.headingOrder,\n `${fileRel}: heading level jumps from h${prevSeen} to h${level} — don't skip levels (WCAG 1.3.1)`,\n ),\n );\n }\n prevSeen = level;\n }\n}\n\n// ---------- Contract Bypass Detection ----------\n\nconst QUIZ_COMPLETE_DISPATCH_RE =\n /(?:new\\s+CustomEvent\\s*\\(\\s*['\"]tessera-quiz-complete['\"]|dispatchEvent\\s*\\([\\s\\S]{0,120}tessera-quiz-complete)/;\nconst RUNTIME_INTERNAL_IMPORT_RE = /from\\s+['\"]tessera-learn\\/runtime\\//;\nconst HAS_USE_QUESTION_RE = /\\buseQuestion\\s*\\(/;\nconst HAS_QUESTION_TAG_RE = new RegExp(\n `<(${Object.keys(QUESTION_COMPONENT_REQUIRED).join('|')})(?=[\\\\s/>])`,\n);\n// Custom widget imported from a local `.svelte` file may wrap useQuestion.\n// Treat its presence as enough to suppress the \"no questions\" warning —\n// false negatives are acceptable for a heuristic that's already advisory.\nconst HAS_LOCAL_SVELTE_IMPORT_RE = /from\\s+['\"][^'\"]+\\.svelte['\"]/;\n\n/**\n * Detect ways an author file can bypass the LMS data contract. These check\n * source text for known escape hatches — they never inspect course content,\n * so they constrain how you wire things up, not what you build.\n */\nfunction validateContractBypass(\n content: string,\n fileRel: string,\n errors: string[],\n): void {\n if (QUIZ_COMPLETE_DISPATCH_RE.test(content)) {\n errors.push(\n `${fileRel}: dispatches \"tessera-quiz-complete\" directly — submit through ` +\n `useQuiz().submit() so the result reaches the LMS`,\n );\n }\n if (RUNTIME_INTERNAL_IMPORT_RE.test(content)) {\n errors.push(\n `${fileRel}: imports from tessera-learn/runtime/* — use the public hooks ` +\n `(useQuiz, useQuestion, useNavigation, …) instead`,\n );\n }\n}\n\n// ---------- Asset Reference Validation ----------\n\nconst ASSET_REF_RE = /\\$assets\\/([^\\s\"'`)]+)/g;\n\n/** Match $assets/... refs in any context (src attrs, import statements, url() etc) and dedupe. */\nfunction collectAssetRefs(content: string): string[] {\n const seen = new Set<string>();\n let match: RegExpExecArray | null;\n ASSET_REF_RE.lastIndex = 0;\n while ((match = ASSET_REF_RE.exec(content)) !== null) {\n seen.add(match[1].replace(/[?#].*$/, ''));\n }\n return [...seen];\n}\n\nfunction validateAssetRefs(\n content: string,\n fileRel: string,\n assetsDir: string,\n warnings: string[],\n existsCache: Map<string, boolean>,\n): void {\n for (const assetPath of collectAssetRefs(content)) {\n const fullAssetPath = resolve(assetsDir, assetPath);\n let exists = existsCache.get(fullAssetPath);\n if (exists === undefined) {\n exists = existsSync(fullAssetPath);\n existsCache.set(fullAssetPath, exists);\n }\n if (!exists) {\n warnings.push(\n `${fileRel}: \"$assets/${assetPath}\" not found in assets/ directory`,\n );\n }\n }\n}\n\n// ---------- Cross-Cutting Validations ----------\n\nfunction crossValidate(\n config: ParsedConfig,\n pageResults: PagesValidationResult,\n errors: string[],\n warnings: string[],\n): void {\n // completion.mode \"quiz\" but no graded quizzes\n if (\n config.completion?.mode === 'quiz' &&\n !pageResults.hasGradedQuiz &&\n !pageResults.hasParseErrors\n ) {\n errors.push(\n 'completion.mode is \"quiz\" but no pages have quiz config with graded: true',\n );\n }\n\n // completion.mode \"quiz\" with an implicit pass threshold — the merge defaults\n // to 70, so this is a nudge, not an error.\n if (\n config.completion?.mode === 'quiz' &&\n config.scoring?.passingScore === undefined\n ) {\n warnings.push(\n 'completion.mode is \"quiz\" but scoring.passingScore is not set — defaulting to 70%. Set it explicitly to be sure.',\n );\n }\n\n const isManual = config.completion?.mode === 'manual';\n const completesOnPages = pageResults.pages.filter((p) => p.completesOnView);\n\n if (\n isManual &&\n config.completion?.trigger === 'page' &&\n completesOnPages.length === 0 &&\n !pageResults.hasParseErrors\n ) {\n errors.push(\n 'completion.mode is \"manual\" with trigger: \"page\", but no page declares pageConfig.completesOn: \"view\". ' +\n 'Either add a completesOn page or remove the trigger field to drop the static check.',\n );\n }\n\n if (isManual) {\n for (const page of pageResults.pages) {\n if (page.hasGradedQuiz) {\n warnings.push(\n `${page.fileRel}: quiz.graded is true under completion.mode: \"manual\". ` +\n 'The score will be reported to the LMS for transcripts, but it will not drive ' +\n \"completion or success status — `markComplete()` / completesOn does. If that's \" +\n 'not what you want, set graded: false or change completion.mode.',\n );\n }\n }\n }\n\n if (isManual && config.completion?.percentageThreshold !== undefined) {\n warnings.push(\n 'course.config.js: \"completion.percentageThreshold\" is ignored under completion.mode: \"manual\"',\n );\n }\n if (!isManual) {\n for (const page of completesOnPages) {\n warnings.push(\n `${page.fileRel}: pageConfig.completesOn is ignored — completion.mode is \"${config.completion?.mode ?? 'percentage'}\"`,\n );\n }\n }\n for (const page of pageResults.pages) {\n if (page.completesOnView && page.hasQuiz) {\n warnings.push(\n `${page.fileRel}: completion fires on view, before the quiz can be answered — likely a mistake`,\n );\n }\n }\n\n if (isManual) {\n const firstPage = pageResults.pages.find((p) => p.navIndex === 0);\n if (firstPage?.completesOnView) {\n warnings.push(\n `${firstPage.fileRel}: pageConfig.completesOn: \"view\" is on the first page — the course will complete immediately on launch, before the learner sees any other content.`,\n );\n }\n }\n\n // SCORM 1.2 + high page count warning\n if (config.export?.standard === 'scorm12') {\n // Estimate worst-case suspend_data size when all pages are visited, all\n // quizzes completed, all chunks revealed, and a modest amount of\n // usePersistence / standalone-question state has accumulated.\n //\n // SavedState shape (see runtime/persistence.ts) — single-letter keys:\n // b (bookmark), v (visited[]), q (quiz scores), d (duration),\n // c (chunk progress), s (standalone scores), gs (graded standalone pages),\n // u (user state from usePersistence)\n //\n // We can't statically detect calls to `useQuestion({ graded: true })` or\n // `usePersistence`, so reserve a fixed buffer per page for those.\n let visitedChars = 0;\n for (let i = 0; i < pageResults.totalPages; i++) {\n visitedChars += String(i).length + 1; // digit chars + comma\n }\n const overhead = 60; // top-level JSON overhead with all keys\n const quizBytes = pageResults.totalQuizzes * 15; // q: \"NNN\":100,\n const chunkBytes = pageResults.totalPages * 12; // c: \"NNN\":NN,\n const standaloneBytes = pageResults.totalPages * 30; // s/gs: conservative buffer per page\n const userStateBuffer = 256; // usePersistence headroom\n const estimatedSize =\n overhead +\n visitedChars +\n quizBytes +\n chunkBytes +\n standaloneBytes +\n userStateBuffer;\n\n if (estimatedSize > 3200) {\n warnings.push(\n `Course has ${pageResults.totalPages} pages with ${pageResults.totalQuizzes} quizzes — estimated SCORM 1.2 suspend_data ~${estimatedSize} bytes may exceed the 4096-byte limit when fully populated (visited + chunks + standalone scores + usePersistence). Consider using \"scorm2004\" or \"cmi5\".`,\n );\n }\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\n\n// Walk up from this module to the tessera-learn package root (the dir holding\n// its package.json). Resolving by directory depth — resolve(dirname, '..', '..')\n// — is brittle: tsdown may emit plugin code at dist/plugin/ or hoist it into a\n// shared chunk at dist/, and those differ by one level. The package ships src/\n// and styles/, so its package.json is the stable anchor for both.\nexport function resolvePackageRoot(): string {\n const dir = import.meta.dirname;\n for (let up = dir; up !== dirname(up); up = dirname(up)) {\n const pkgPath = resolve(up, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const { name } = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n if (name === 'tessera-learn') return up;\n } catch {\n // unreadable / non-JSON package.json — keep walking up\n }\n }\n }\n // Fallback to the historical depth assumption (dist/plugin → package root).\n return resolve(dir, '..', '..');\n}\n","import { existsSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { generateManifest, readCourseConfig } from '../manifest.js';\nimport { normalizeA11y, type A11ySettings } from '../validation.js';\n\nexport interface AuditOptions {\n /** Minimum violation impact that fails the run (CI gate). Default 'serious'. */\n threshold?: ImpactLevel;\n /** Force a fresh `vite build` even if dist/ exists. */\n rebuild?: boolean;\n}\n\nexport type ImpactLevel = 'minor' | 'moderate' | 'serious' | 'critical';\n\nconst IMPACT_RANK: Record<ImpactLevel, number> = {\n minor: 1,\n moderate: 2,\n serious: 3,\n critical: 4,\n};\n\n// Set by runAudit during its build/preview; the plugin forces the WebAdapter,\n// skips export packaging, and stubs xAPI while it's set. See plugin/index.ts.\nexport const AUDIT_ENV_FLAG = 'TESSERA_A11Y_AUDIT';\n\ninterface AxeViolation {\n id: string;\n impact: ImpactLevel | null;\n help: string;\n helpUrl: string;\n nodes: number;\n}\n\ninterface PageAuditResult {\n index: number;\n title: string;\n violations: AxeViolation[];\n loadFailed?: boolean;\n}\n\ninterface AuditReport {\n standard: A11ySettings['standard'];\n threshold: ImpactLevel;\n pages: PageAuditResult[];\n pagesAudited: number;\n totalPages: number;\n pagesFailedToLoad: number;\n totalViolations: number;\n failingViolations: number;\n passed: boolean;\n}\n\n/** Map the `a11y.standard` enum to axe's cumulative `runOnly` tag list. */\nexport function axeTags(standard: A11ySettings['standard']): string[] {\n switch (standard) {\n case 'wcag2a':\n return ['wcag2a'];\n case 'wcag21aa':\n return ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'];\n case 'wcag2aa':\n default:\n return ['wcag2a', 'wcag2aa'];\n }\n}\n\n/** axe-applicable ignore entries: drop the Tier-1a/1b namespaces. */\nexport function axeIgnoreRules(ignore: string[]): string[] {\n return ignore.filter(\n (id) => !id.startsWith('tessera/') && !id.startsWith('a11y_'),\n );\n}\n\nexport function isMissingBrowserError(message: string): boolean {\n return /Executable doesn't exist|playwright install/i.test(message);\n}\n\n// A violation with no impact is treated as failing rather than slipping the\n// gate at every threshold.\nfunction isFailing(v: AxeViolation, thresholdRank: number): boolean {\n return !v.impact || IMPACT_RANK[v.impact] >= thresholdRank;\n}\n\n// Optional deps loaded by variable specifier so tsc doesn't require them to be\n// installed — Tier 2 is opt-in and the absence is handled with a clear message.\nasync function tryImport(specifier: string): Promise<unknown> {\n return import(specifier);\n}\n\ninterface LoadedDeps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n chromium: any;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n AxeBuilder: any;\n}\n\nasync function loadDeps(): Promise<\n { ok: true; deps: LoadedDeps } | { ok: false; missing: string }\n> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let chromium: any;\n for (const spec of ['playwright', '@playwright/test']) {\n try {\n const mod = (await tryImport(spec)) as { chromium?: unknown };\n if (mod.chromium) {\n chromium = mod.chromium;\n break;\n }\n } catch {\n // try the next specifier\n }\n }\n if (!chromium) return { ok: false, missing: 'playwright' };\n\n try {\n const mod = (await tryImport('@axe-core/playwright')) as {\n default?: unknown;\n };\n if (!mod.default) return { ok: false, missing: '@axe-core/playwright' };\n return { ok: true, deps: { chromium, AxeBuilder: mod.default } };\n } catch {\n return { ok: false, missing: '@axe-core/playwright' };\n }\n}\n\n/**\n * Run the Tier-2 runtime accessibility audit against a built course. Builds (or\n * reuses) dist/, serves it, drives Playwright + axe-core over each page, writes\n * a11y-report.json, and returns a process exit code (0 pass, 1 fail/error).\n */\nexport async function runAudit(\n projectRoot: string,\n workspaceRoot: string,\n options: AuditOptions = {},\n): Promise<number> {\n const threshold: ImpactLevel = options.threshold ?? 'serious';\n\n const deps = await loadDeps();\n if (!deps.ok) {\n console.error(\n `\\x1b[31m[tessera a11y]\\x1b[0m Tier 2 needs Playwright + axe-core, which aren't installed.\\n` +\n ` Install them to run the runtime audit:\\n` +\n ` pnpm add -D playwright @axe-core/playwright\\n` +\n ` pnpm exec playwright install chromium`,\n );\n return 1;\n }\n const { chromium, AxeBuilder } = deps.deps;\n\n const read = readCourseConfig(projectRoot);\n const settings = normalizeA11y(read.ok ? read.config.a11y : undefined);\n const tags = axeTags(settings.standard);\n const disableRules = axeIgnoreRules(settings.ignore);\n\n const manifest = generateManifest(resolve(projectRoot, 'pages'));\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const vite = (await import('vite')) as any;\n const { resolveTesseraConfig } = await import('../inline-config.js');\n // Carries tesseraPlugin() + the Svelte compiler; without it the plugin-less\n // build would silently produce a broken bundle (there is no vite.config.js).\n const auditBaseConfig = await resolveTesseraConfig(\n projectRoot,\n workspaceRoot,\n {\n command: 'build',\n mode: 'production',\n },\n );\n\n // A throwaway web build, kept out of dist/ so a real LMS export is untouched.\n const auditDist = resolve(projectRoot, 'node_modules', '.tessera-a11y');\n const distHtml = resolve(auditDist, 'index.html');\n\n const prevEnv = process.env[AUDIT_ENV_FLAG];\n process.env[AUDIT_ENV_FLAG] = '1';\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let server: any;\n try {\n if (options.rebuild || !existsSync(distHtml)) {\n console.log('[tessera a11y] Building course…');\n await vite.build(\n vite.mergeConfig(auditBaseConfig, {\n build: { outDir: auditDist, emptyOutDir: true },\n logLevel: 'warn',\n }),\n );\n }\n\n server = await vite.preview({\n root: projectRoot,\n base: auditBaseConfig.base,\n build: { outDir: auditDist },\n preview: { port: 0, host: '127.0.0.1' },\n logLevel: 'warn',\n });\n const baseUrl: string | undefined = server.resolvedUrls?.local?.[0];\n if (!baseUrl) {\n console.error('[tessera a11y] Could not determine preview server URL.');\n return 1;\n }\n\n let browser;\n try {\n browser = await chromium.launch();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (isMissingBrowserError(message)) {\n console.error(\n `\\x1b[31m[tessera a11y]\\x1b[0m Chromium isn't installed for Playwright.\\n` +\n ` Install it once:\\n` +\n ` pnpm exec playwright install chromium`,\n );\n return 1;\n }\n throw err;\n }\n const pages: PageAuditResult[] = [];\n try {\n // axe-core/playwright requires a page from an explicit context.\n const context = await browser.newContext();\n const page = await context.newPage();\n // ?__tessera_audit unlocks navigation so quiz-gated pages can be scanned.\n const auditUrl = new URL(baseUrl);\n auditUrl.searchParams.set('__tessera_audit', '1');\n await page.goto(auditUrl.href, { waitUntil: 'networkidle' });\n await page.waitForSelector('#tessera-app', { timeout: 20_000 });\n\n const scan = async (): Promise<AxeViolation[]> => {\n const builder = new AxeBuilder({ page }).withTags(tags);\n if (disableRules.length > 0) builder.disableRules(disableRules);\n const out = await builder.analyze();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return out.violations.map((v: any) => ({\n id: v.id,\n impact: v.impact ?? null,\n help: v.help,\n helpUrl: v.helpUrl,\n nodes: v.nodes.length,\n }));\n };\n\n const recordPage = async (\n index: number,\n title: string,\n ): Promise<PageAuditResult> => {\n const loadFailed = await page.evaluate(\n () =>\n document.getElementById('tessera-app')?.dataset.tesseraPageError ===\n 'true',\n );\n if (loadFailed) return { index, title, violations: [], loadFailed };\n return { index, title, violations: await scan() };\n };\n\n const totalPages = manifest.pages.length;\n const hasAuditHook = await page.evaluate(\n () => typeof window.__tesseraAudit?.goToIndex === 'function',\n );\n\n if (!hasAuditHook) {\n // No navigation hook — audit the entry only, but flag the reduced scope\n // rather than passing it off as full coverage.\n if (totalPages > 1) {\n console.warn(\n `\\x1b[33m[tessera a11y]\\x1b[0m Could not enumerate pages; auditing the entry page only ` +\n `(1 of ${totalPages}). The report records the reduced scope.`,\n );\n }\n pages.push(await recordPage(0, manifest.pages[0]?.title ?? '(entry)'));\n } else {\n for (let i = 0; i < totalPages; i++) {\n await page.evaluate(\n (idx: number) => window.__tesseraAudit!.goToIndex(idx),\n i,\n );\n await page.waitForFunction(\n (idx: number) =>\n document.getElementById('tessera-app')?.dataset\n .tesseraPageIndex === String(idx),\n i,\n { timeout: 20_000 },\n );\n await page.waitForLoadState('networkidle');\n pages.push(\n await recordPage(i, manifest.pages[i]?.title ?? `Page ${i + 1}`),\n );\n }\n }\n } finally {\n await browser.close();\n }\n\n const thresholdRank = IMPACT_RANK[threshold];\n let totalViolations = 0;\n let failingViolations = 0;\n let pagesFailedToLoad = 0;\n for (const p of pages) {\n if (p.loadFailed) pagesFailedToLoad++;\n for (const v of p.violations) {\n totalViolations++;\n if (isFailing(v, thresholdRank)) failingViolations++;\n }\n }\n\n const report: AuditReport = {\n standard: settings.standard,\n threshold,\n pages,\n pagesAudited: pages.length,\n totalPages: manifest.pages.length,\n pagesFailedToLoad,\n totalViolations,\n failingViolations,\n passed: failingViolations === 0 && pagesFailedToLoad === 0,\n };\n const reportPath = resolve(projectRoot, 'a11y-report.json');\n writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');\n\n printSummary(report, reportPath);\n return report.passed ? 0 : 1;\n } catch (err) {\n console.error(\n `\\x1b[31m[tessera a11y]\\x1b[0m Audit could not complete: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return 1;\n } finally {\n server?.httpServer?.close?.();\n if (prevEnv === undefined) delete process.env[AUDIT_ENV_FLAG];\n else process.env[AUDIT_ENV_FLAG] = prevEnv;\n }\n}\n\nfunction printSummary(report: AuditReport, reportPath: string): void {\n const thresholdRank = IMPACT_RANK[report.threshold];\n for (const p of report.pages) {\n if (p.loadFailed) {\n console.log(`\\x1b[31m ✗\\x1b[0m ${p.title} — failed to load`);\n continue;\n }\n if (p.violations.length === 0) {\n console.log(`\\x1b[32m ✓\\x1b[0m ${p.title}`);\n continue;\n }\n const failing = p.violations.some((v) => isFailing(v, thresholdRank));\n const mark = failing ? '\\x1b[31m ✗\\x1b[0m' : '\\x1b[33m ⚠\\x1b[0m';\n console.log(`${mark} ${p.title}`);\n for (const v of p.violations) {\n console.log(\n ` [${v.impact ?? 'n/a'}] ${v.id} — ${v.help} (${v.nodes} node${v.nodes === 1 ? '' : 's'})`,\n );\n }\n }\n console.log(`\\n[tessera a11y] Report written to ${reportPath}`);\n if (report.pagesAudited < report.totalPages) {\n console.log(\n `\\x1b[33m[tessera a11y] Covered ${report.pagesAudited} of ${report.totalPages} page(s)\\x1b[0m — reduced scope, the rest were not audited.`,\n );\n } else if (report.pagesFailedToLoad > 0) {\n const scanned = report.pagesAudited - report.pagesFailedToLoad;\n console.log(\n `[tessera a11y] Reached all ${report.totalPages} page(s); scanned ${scanned}, ${report.pagesFailedToLoad} failed to load.`,\n );\n } else {\n console.log(`[tessera a11y] Covered all ${report.totalPages} page(s).`);\n }\n if (report.passed) {\n console.log(\n `\\x1b[32m[tessera a11y] Passed\\x1b[0m — ${report.totalViolations} total finding(s), none at/above \"${report.threshold}\".`,\n );\n } else {\n const reasons: string[] = [];\n if (report.failingViolations > 0) {\n reasons.push(\n `${report.failingViolations} finding(s) at/above \"${report.threshold}\" (of ${report.totalViolations} total)`,\n );\n }\n if (report.pagesFailedToLoad > 0) {\n reasons.push(`${report.pagesFailedToLoad} page(s) failed to load`);\n }\n console.log(\n `\\x1b[31m[tessera a11y] Failed\\x1b[0m — ${reasons.join('; ')}.`,\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { resolveTesseraConfig } from "./inline-config-
|
|
1
|
+
import { resolveTesseraConfig } from "./inline-config-Dudu5r8w.js";
|
|
2
2
|
//#region src/plugin/build-commands.ts
|
|
3
|
-
async function runDev(projectRoot) {
|
|
3
|
+
async function runDev(projectRoot, workspaceRoot) {
|
|
4
4
|
const vite = await import("vite");
|
|
5
|
-
const config = await resolveTesseraConfig(projectRoot, {
|
|
5
|
+
const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {
|
|
6
6
|
command: "serve",
|
|
7
7
|
mode: "development"
|
|
8
8
|
});
|
|
@@ -12,9 +12,9 @@ async function runDev(projectRoot) {
|
|
|
12
12
|
server.bindCLIShortcuts({ print: true });
|
|
13
13
|
return new Promise(() => {});
|
|
14
14
|
}
|
|
15
|
-
async function runBuild(projectRoot) {
|
|
15
|
+
async function runBuild(projectRoot, workspaceRoot) {
|
|
16
16
|
const vite = await import("vite");
|
|
17
|
-
const config = await resolveTesseraConfig(projectRoot, {
|
|
17
|
+
const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {
|
|
18
18
|
command: "build",
|
|
19
19
|
mode: "production"
|
|
20
20
|
});
|
|
@@ -24,4 +24,4 @@ async function runBuild(projectRoot) {
|
|
|
24
24
|
//#endregion
|
|
25
25
|
export { runBuild, runDev };
|
|
26
26
|
|
|
27
|
-
//# sourceMappingURL=build-commands-
|
|
27
|
+
//# sourceMappingURL=build-commands-BWnATKat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-commands-BWnATKat.js","names":[],"sources":["../src/plugin/build-commands.ts"],"sourcesContent":["import { resolveTesseraConfig } from './inline-config.js';\n\nexport async function runDev(\n projectRoot: string,\n workspaceRoot: string,\n): Promise<number> {\n const vite = await import('vite');\n const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {\n command: 'serve',\n mode: 'development',\n });\n const server = await vite.createServer(config);\n await server.listen();\n server.printUrls();\n server.bindCLIShortcuts({ print: true });\n // Never resolve: the CLI wrapper would process.exit and kill the server.\n return new Promise<number>(() => {});\n}\n\nexport async function runBuild(\n projectRoot: string,\n workspaceRoot: string,\n): Promise<number> {\n const vite = await import('vite');\n const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {\n command: 'build',\n mode: 'production',\n });\n await vite.build(config);\n return 0;\n}\n"],"mappings":";;AAEA,eAAsB,OACpB,aACA,eACiB;CACjB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,SAAS,MAAM,qBAAqB,aAAa,eAAe;EACpE,SAAS;EACT,MAAM;CACR,CAAC;CACD,MAAM,SAAS,MAAM,KAAK,aAAa,MAAM;CAC7C,MAAM,OAAO,OAAO;CACpB,OAAO,UAAU;CACjB,OAAO,iBAAiB,EAAE,OAAO,KAAK,CAAC;CAEvC,OAAO,IAAI,cAAsB,CAAC,CAAC;AACrC;AAEA,eAAsB,SACpB,aACA,eACiB;CACjB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,SAAS,MAAM,qBAAqB,aAAa,eAAe;EACpE,SAAS;EACT,MAAM;CACR,CAAC;CACD,MAAM,KAAK,MAAM,MAAM;CACvB,OAAO;AACT"}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { t as tesseraPlugin } from "./plugin-
|
|
1
|
+
import { t as tesseraPlugin } from "./plugin-diNZaDJK.js";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
//#region src/plugin/inline-config.ts
|
|
6
|
-
function buildInlineConfig(projectRoot) {
|
|
6
|
+
function buildInlineConfig(projectRoot, workspaceRoot) {
|
|
7
7
|
return {
|
|
8
8
|
root: projectRoot,
|
|
9
9
|
configFile: false,
|
|
10
|
-
plugins: [tesseraPlugin()]
|
|
10
|
+
plugins: [tesseraPlugin()],
|
|
11
|
+
resolve: { alias: { $shared: resolve(workspaceRoot, "shared") } },
|
|
12
|
+
server: { fs: { allow: [workspaceRoot] } }
|
|
11
13
|
};
|
|
12
14
|
}
|
|
13
15
|
async function loadUserConfig(projectRoot, env) {
|
|
@@ -17,13 +19,13 @@ async function loadUserConfig(projectRoot, env) {
|
|
|
17
19
|
const config = mod.default ?? mod;
|
|
18
20
|
return typeof config === "function" ? await config(env) : config;
|
|
19
21
|
}
|
|
20
|
-
async function resolveTesseraConfig(projectRoot, env) {
|
|
22
|
+
async function resolveTesseraConfig(projectRoot, workspaceRoot, env) {
|
|
21
23
|
const vite = await import("vite");
|
|
22
|
-
const base = buildInlineConfig(projectRoot);
|
|
24
|
+
const base = buildInlineConfig(projectRoot, workspaceRoot);
|
|
23
25
|
const user = await loadUserConfig(projectRoot, env);
|
|
24
26
|
return user ? vite.mergeConfig(base, user) : base;
|
|
25
27
|
}
|
|
26
28
|
//#endregion
|
|
27
29
|
export { resolveTesseraConfig };
|
|
28
30
|
|
|
29
|
-
//# sourceMappingURL=inline-config-
|
|
31
|
+
//# sourceMappingURL=inline-config-Dudu5r8w.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-config-Dudu5r8w.js","names":[],"sources":["../src/plugin/inline-config.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { ConfigEnv, InlineConfig } from 'vite';\nimport { tesseraPlugin } from './index.js';\n\n// Base Vite config for every Tessera command (dev, export, a11y build).\n// configFile:false disables Vite's own discovery — there is no vite.config.js —\n// and tesseraPlugin() supplies the Svelte compiler, so this is the full plugin set.\n//\n// $shared points at the workspace-level design system, which lives outside the\n// per-course Vite root, so it is wired here (where workspaceRoot is known) rather\n// than next to $assets in the plugin. server.fs.allow must list workspaceRoot or\n// the dev server's fs.strict gate refuses to serve $shared files.\nexport function buildInlineConfig(\n projectRoot: string,\n workspaceRoot: string,\n): InlineConfig {\n return {\n root: projectRoot,\n configFile: false,\n plugins: [tesseraPlugin()],\n resolve: { alias: { $shared: resolve(workspaceRoot, 'shared') } },\n server: { fs: { allow: [workspaceRoot] } },\n };\n}\n\n// Optional author-owned escape hatch, never scaffolded or reconciled. A *partial*\n// Vite config the caller merges on top of buildInlineConfig(), so tesseraPlugin()\n// stays wired in and the author only writes the delta.\nexport async function loadUserConfig(\n projectRoot: string,\n env: ConfigEnv,\n): Promise<InlineConfig | null> {\n const configPath = resolve(projectRoot, 'tessera.config.js');\n if (!existsSync(configPath)) return null;\n const mod = await import(pathToFileURL(configPath).href);\n const config = mod.default ?? mod;\n // mergeConfig throws on a function, so resolve Vite's callback form first.\n return (\n typeof config === 'function' ? await config(env) : config\n ) as InlineConfig;\n}\n\nexport async function resolveTesseraConfig(\n projectRoot: string,\n workspaceRoot: string,\n env: ConfigEnv,\n): Promise<InlineConfig> {\n const vite = await import('vite');\n const base = buildInlineConfig(projectRoot, workspaceRoot);\n const user = await loadUserConfig(projectRoot, env);\n return user ? vite.mergeConfig(base, user) : base;\n}\n"],"mappings":";;;;;AAcA,SAAgB,kBACd,aACA,eACc;CACd,OAAO;EACL,MAAM;EACN,YAAY;EACZ,SAAS,CAAC,cAAc,CAAC;EACzB,SAAS,EAAE,OAAO,EAAE,SAAS,QAAQ,eAAe,QAAQ,EAAE,EAAE;EAChE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE;CAC3C;AACF;AAKA,eAAsB,eACpB,aACA,KAC8B;CAC9B,MAAM,aAAa,QAAQ,aAAa,mBAAmB;CAC3D,IAAI,CAAC,WAAW,UAAU,GAAG,OAAO;CACpC,MAAM,MAAM,MAAM,OAAO,cAAc,UAAU,EAAE;CACnD,MAAM,SAAS,IAAI,WAAW;CAE9B,OACE,OAAO,WAAW,aAAa,MAAM,OAAO,GAAG,IAAI;AAEvD;AAEA,eAAsB,qBACpB,aACA,eACA,KACuB;CACvB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,OAAO,kBAAkB,aAAa,aAAa;CACzD,MAAM,OAAO,MAAM,eAAe,aAAa,GAAG;CAClD,OAAO,OAAO,KAAK,YAAY,MAAM,IAAI,IAAI;AAC/C"}
|
package/dist/plugin/cli.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
//#region src/plugin/cli.d.ts
|
|
2
|
-
declare function
|
|
2
|
+
declare function splitCourseArg(rest: string[]): {
|
|
3
|
+
course?: string;
|
|
4
|
+
flags: string[];
|
|
5
|
+
};
|
|
6
|
+
declare function main(argv: string[], cwd?: string): Promise<number>;
|
|
3
7
|
//#endregion
|
|
4
|
-
export { main };
|
|
8
|
+
export { main, splitCourseArg };
|
|
5
9
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/plugin/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","names":[],"sources":["../../src/plugin/cli.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","names":[],"sources":["../../src/plugin/cli.ts"],"mappings":";iBA2BgB,cAAA,CAAe,IAAA;EAC7B,MAAA;EACA,KAAA;AAAA;AAAA,iBAQoB,IAAA,CACpB,IAAA,YACA,GAAA,YACC,OAAO"}
|